Merge "[Tethering] Add MTS method to TetheringManagerTest."
diff --git a/TEST_MAPPING b/TEST_MAPPING
index a5b97a1..4eeaf51 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -3,6 +3,10 @@
{
"name": "ConnectivityCoverageTests"
},
+ {
+ // In addition to ConnectivityCoverageTests, runs non-connectivity-module tests
+ "name": "FrameworksNetTests"
+ },
// Run in addition to mainline-presubmit as mainline-presubmit is not
// supported in every branch.
// CtsNetTestCasesLatestSdk uses stable API shims, so does not exercise
@@ -18,10 +22,40 @@
}
]
},
+ // Also run CtsNetTestCasesLatestSdk to ensure tests using older shims pass.
+ {
+ "name": "CtsNetTestCasesLatestSdk",
+ "options": [
+ {
+ "exclude-annotation": "com.android.testutils.SkipPresubmit"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.RequiresDevice"
+ }
+ ]
+ },
+ // CTS tests that target older SDKs.
+ {
+ "name": "CtsNetTestCasesMaxTargetSdk31",
+ "options": [
+ {
+ "exclude-annotation": "com.android.testutils.SkipPresubmit"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.RequiresDevice"
+ }
+ ]
+ },
{
"name": "bpf_existence_test"
},
{
+ "name": "connectivity_native_test"
+ },
+ {
+ "name": "libclat_test"
+ },
+ {
"name": "netd_updatable_unit_test"
},
{
@@ -49,16 +83,10 @@
"keywords": ["netd-device-kernel-4.9", "netd-device-kernel-4.14"]
},
{
- "name": "libclat_test"
- },
- {
"name": "traffic_controller_unit_test",
"keywords": ["netd-device-kernel-4.9", "netd-device-kernel-4.14"]
},
{
- "name": "libnetworkstats_test"
- },
- {
"name": "FrameworksNetDeflakeTest"
}
],
@@ -75,9 +103,23 @@
]
},
{
+ "name": "CtsNetTestCasesMaxTargetSdk31[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
+ "options": [
+ {
+ "exclude-annotation": "com.android.testutils.SkipPresubmit"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.RequiresDevice"
+ }
+ ]
+ },
+ {
"name": "bpf_existence_test[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
},
{
+ "name": "connectivity_native_test[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
+ },
+ {
"name": "netd_updatable_unit_test[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
},
{
@@ -127,9 +169,6 @@
"exclude-annotation": "androidx.test.filters.RequiresDevice"
}
]
- },
- {
- "name": "TetheringCoverageTests[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
}
],
"auto-postsubmit": [
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index 41a0651..adcc236 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -21,7 +21,7 @@
java_defaults {
name: "TetheringApiLevel",
sdk_version: "module_current",
- target_sdk_version: "31",
+ target_sdk_version: "33",
min_sdk_version: "30",
}
@@ -33,6 +33,7 @@
":framework-connectivity-shared-srcs",
":tethering-module-utils-srcs",
":services-tethering-shared-srcs",
+ ":statslog-tethering-java-gen",
],
static_libs: [
"androidx.annotation_annotation",
@@ -47,6 +48,7 @@
"net-utils-device-common-bpf",
"net-utils-device-common-netlink",
"netd-client",
+ "tetheringstatsprotos",
],
libs: [
"framework-connectivity",
@@ -211,3 +213,22 @@
bootclasspath_fragments: ["com.android.tethering-bootclasspath-fragment"],
systemserverclasspath_fragments: ["com.android.tethering-systemserverclasspath-fragment"],
}
+
+java_library_static {
+ name: "tetheringstatsprotos",
+ proto: {type: "lite"},
+ srcs: [
+ "src/com/android/networkstack/tethering/metrics/stats.proto",
+ ],
+ static_libs: ["tetheringprotos"],
+ apex_available: ["com.android.tethering"],
+ min_sdk_version: "30",
+}
+
+genrule {
+ name: "statslog-tethering-java-gen",
+ tools: ["stats-log-api-gen"],
+ cmd: "$(location stats-log-api-gen) --java $(out) --module network_tethering" +
+ " --javaPackage com.android.networkstack.tethering.metrics --javaClass TetheringStatsLog",
+ out: ["com/android/networkstack/tethering/metrics/TetheringStatsLog.java"],
+}
\ No newline at end of file
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index dd04d6c..76c5d5c 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -69,9 +69,10 @@
],
canned_fs_config: "canned_fs_config",
bpfs: [
- "clatd.o_mainline",
- "netd.o_mainline",
+ "block.o",
+ "clatd.o",
"dscp_policy.o",
+ "netd.o",
"offload.o",
"test.o",
],
@@ -89,6 +90,8 @@
compressible: true,
androidManifest: "AndroidManifest.xml",
+
+ compat_configs: ["connectivity-platform-compat-config"],
}
apex_key {
@@ -131,15 +134,41 @@
hidden_api: {
max_target_r_low_priority: [
"hiddenapi/hiddenapi-max-target-r-loprio.txt",
- ],
+ ],
max_target_o_low_priority: [
"hiddenapi/hiddenapi-max-target-o-low-priority.txt",
"hiddenapi/hiddenapi-max-target-o-low-priority-tiramisu.txt",
- ],
+ ],
unsupported: [
"hiddenapi/hiddenapi-unsupported.txt",
"hiddenapi/hiddenapi-unsupported-tiramisu.txt",
],
+
+ // The following packages contain classes from other modules on the
+ // bootclasspath. That means that the hidden API flags for this module
+ // has to explicitly list every single class this module provides in
+ // that package to differentiate them from the classes provided by other
+ // modules. That can include private classes that are not part of the
+ // API.
+ split_packages: [
+ "android.app.usage",
+ "android.net",
+ "android.net.netstats",
+ "android.net.util",
+ ],
+
+ // The following packages and all their subpackages currently only
+ // contain classes from this bootclasspath_fragment. Listing a package
+ // here won't prevent other bootclasspath modules from adding classes in
+ // any of those packages but it will prevent them from adding those
+ // classes into an API surface, e.g. public, system, etc.. Doing so will
+ // result in a build failure due to inconsistent flags.
+ package_prefixes: [
+ "android.net.apf",
+ "android.net.connectivity",
+ "android.net.netstats.provider",
+ "android.net.nsd",
+ ],
},
}
diff --git a/Tethering/apex/manifest.json b/Tethering/apex/manifest.json
index 88f13b2..3cb03ed 100644
--- a/Tethering/apex/manifest.json
+++ b/Tethering/apex/manifest.json
@@ -1,4 +1,4 @@
{
"name": "com.android.tethering",
- "version": 319999900
+ "version": 339990000
}
diff --git a/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java b/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java
index 22d2c5d..b865a8e 100644
--- a/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java
+++ b/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java
@@ -30,9 +30,9 @@
import com.android.net.module.util.IBpfMap.ThrowingBiConsumer;
import com.android.net.module.util.bpf.Tether4Key;
import com.android.net.module.util.bpf.Tether4Value;
+import com.android.net.module.util.bpf.TetherStatsValue;
import com.android.networkstack.tethering.BpfCoordinator.Dependencies;
import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
-import com.android.networkstack.tethering.TetherStatsValue;
/**
* Bpf coordinator class for API shims.
diff --git a/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java b/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
index 5afb862..0683e5e 100644
--- a/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
+++ b/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
@@ -33,6 +33,8 @@
import com.android.net.module.util.IBpfMap.ThrowingBiConsumer;
import com.android.net.module.util.bpf.Tether4Key;
import com.android.net.module.util.bpf.Tether4Value;
+import com.android.net.module.util.bpf.TetherStatsKey;
+import com.android.net.module.util.bpf.TetherStatsValue;
import com.android.networkstack.tethering.BpfCoordinator.Dependencies;
import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
import com.android.networkstack.tethering.BpfUtils;
@@ -42,8 +44,6 @@
import com.android.networkstack.tethering.TetherDownstream6Key;
import com.android.networkstack.tethering.TetherLimitKey;
import com.android.networkstack.tethering.TetherLimitValue;
-import com.android.networkstack.tethering.TetherStatsKey;
-import com.android.networkstack.tethering.TetherStatsValue;
import com.android.networkstack.tethering.TetherUpstream6Key;
import java.io.FileDescriptor;
diff --git a/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java b/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java
index 915e210..69cbab5 100644
--- a/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java
+++ b/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java
@@ -25,9 +25,9 @@
import com.android.net.module.util.IBpfMap.ThrowingBiConsumer;
import com.android.net.module.util.bpf.Tether4Key;
import com.android.net.module.util.bpf.Tether4Value;
+import com.android.net.module.util.bpf.TetherStatsValue;
import com.android.networkstack.tethering.BpfCoordinator.Dependencies;
import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
-import com.android.networkstack.tethering.TetherStatsValue;
/**
* Bpf coordinator class for API shims.
diff --git a/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl b/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl
index b4e3ba4..836761f 100644
--- a/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl
+++ b/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl
@@ -36,4 +36,5 @@
void onTetherStatesChanged(in TetherStatesParcel states);
void onTetherClientsChanged(in List<TetheredClient> clients);
void onOffloadStatusChanged(int status);
+ void onSupportedTetheringTypes(long supportedBitmap);
}
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl b/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl
index 253eacb..f33f846 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl
@@ -26,7 +26,7 @@
* @hide
*/
parcelable TetheringCallbackStartedParcel {
- boolean tetheringSupported;
+ long supportedTypes;
Network upstreamNetwork;
TetheringConfigurationParcel config;
TetherStatesParcel states;
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index 6f9b33e..b3f0cf2 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -183,6 +183,12 @@
*/
public static final int TETHERING_WIGIG = 6;
+ /**
+ * The int value of last tethering type.
+ * @hide
+ */
+ public static final int MAX_TETHERING_TYPE = TETHERING_WIGIG;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {
@@ -520,6 +526,9 @@
}
@Override
+ public void onSupportedTetheringTypes(long supportedBitmap) { }
+
+ @Override
public void onUpstreamChanged(Network network) { }
@Override
@@ -1033,15 +1042,29 @@
/**
* Called when tethering supported status changed.
*
+ * <p>This callback will be called immediately after the callback is
+ * registered, and never be called if there is changes afterward.
+ *
+ * <p>Tethering may be disabled via system properties, device configuration, or device
+ * policy restrictions.
+ *
+ * @param supported whether any tethering type is supported.
+ */
+ default void onTetheringSupported(boolean supported) {}
+
+ /**
+ * Called when tethering supported status changed.
+ *
* <p>This will be called immediately after the callback is registered, and may be called
* multiple times later upon changes.
*
* <p>Tethering may be disabled via system properties, device configuration, or device
* policy restrictions.
*
- * @param supported The new supported status
+ * @param supportedTypes a set of @TetheringType which is supported.
+ * @hide
*/
- default void onTetheringSupported(boolean supported) {}
+ default void onSupportedTetheringTypes(@NonNull Set<Integer> supportedTypes) {}
/**
* Called when tethering upstream changed.
@@ -1339,7 +1362,8 @@
@Override
public void onCallbackStarted(TetheringCallbackStartedParcel parcel) {
executor.execute(() -> {
- callback.onTetheringSupported(parcel.tetheringSupported);
+ callback.onSupportedTetheringTypes(unpackBits(parcel.supportedTypes));
+ callback.onTetheringSupported(parcel.supportedTypes != 0);
callback.onUpstreamChanged(parcel.upstreamNetwork);
sendErrorCallbacks(parcel.states);
sendRegexpsChanged(parcel.config);
@@ -1358,6 +1382,13 @@
});
}
+ @Override
+ public void onSupportedTetheringTypes(long supportedBitmap) {
+ executor.execute(() -> {
+ callback.onSupportedTetheringTypes(unpackBits(supportedBitmap));
+ });
+ }
+
private void sendRegexpsChanged(TetheringConfigurationParcel parcel) {
callback.onTetherableInterfaceRegexpsChanged(new TetheringInterfaceRegexps(
parcel.tetherableBluetoothRegexs,
@@ -1396,6 +1427,23 @@
}
/**
+ * Unpack bitmap to a set of bit position intergers.
+ * @hide
+ */
+ public static ArraySet<Integer> unpackBits(long val) {
+ final ArraySet<Integer> result = new ArraySet<>(Long.bitCount(val));
+ int bitPos = 0;
+ while (val != 0) {
+ if ((val & 1) == 1) result.add(bitPos);
+
+ val = val >>> 1;
+ bitPos++;
+ }
+
+ return result;
+ }
+
+ /**
* Remove tethering event callback previously registered with
* {@link #registerTetheringEventCallback}.
*
diff --git a/Tethering/jarjar-rules.txt b/Tethering/jarjar-rules.txt
index 5de4b97..904e491 100644
--- a/Tethering/jarjar-rules.txt
+++ b/Tethering/jarjar-rules.txt
@@ -4,6 +4,7 @@
# module will be overwritten by the ones in the framework.
rule com.android.internal.util.** com.android.networkstack.tethering.util.@1
rule android.util.LocalLog* com.android.networkstack.tethering.util.LocalLog@1
+rule android.util.IndentingPrintWriter* com.android.networkstack.tethering.util.AndroidUtilIndentingPrintWriter@1
rule android.net.shared.Inet4AddressUtils* com.android.networkstack.tethering.shared.Inet4AddressUtils@1
@@ -12,3 +13,8 @@
# Classes from net-utils-device-common
rule com.android.net.module.util.Struct* com.android.networkstack.tethering.util.Struct@1
+
+rule com.google.protobuf.** com.android.networkstack.tethering.protobuf@1
+
+# Classes for hardware offload hidl interface
+rule android.hidl.base.V1_0.DebugInfo* com.android.networkstack.tethering.hidl.base.V1_0.DebugInfo@1
diff --git a/Tethering/proguard.flags b/Tethering/proguard.flags
index 6735317..2905e28 100644
--- a/Tethering/proguard.flags
+++ b/Tethering/proguard.flags
@@ -12,6 +12,11 @@
native <methods>;
}
+# Ensure runtime-visible field annotations are kept when using R8 full mode.
+-keepattributes RuntimeVisibleAnnotations,AnnotationDefault
+-keep interface com.android.networkstack.tethering.util.Struct$Field {
+ *;
+}
-keepclassmembers public class * extends com.android.networkstack.tethering.util.Struct {
*;
}
@@ -19,3 +24,9 @@
-keepclassmembers class android.net.ip.IpServer {
static final int CMD_*;
}
+
+# The lite proto runtime uses reflection to access fields based on the names in
+# the schema, keep all the fields.
+-keepclassmembers class * extends com.android.networkstack.tethering.protobuf.MessageLite {
+ <fields>;
+}
\ No newline at end of file
diff --git a/Tethering/res/values/config.xml b/Tethering/res/values/config.xml
index 0412a49..bfec5bc 100644
--- a/Tethering/res/values/config.xml
+++ b/Tethering/res/values/config.xml
@@ -78,6 +78,12 @@
<!-- Use legacy wifi p2p dedicated address instead of randomize address. -->
<bool translatable="false" name="config_tether_enable_legacy_wifi_p2p_dedicated_ip">false</bool>
+ <!-- Use lease subnet prefix length to reserve the range outside of subnet prefix length.
+ This configuration only valid if its value larger than dhcp server address prefix length
+ and config_tether_enable_legacy_wifi_p2p_dedicated_ip is true.
+ -->
+ <integer translatable="false" name="config_p2p_leases_subnet_prefix_length">0</integer>
+
<!-- Dhcp range (min, max) to use for tethering purposes -->
<string-array translatable="false" name="config_tether_dhcp_range">
</string-array>
diff --git a/Tethering/res/values/overlayable.xml b/Tethering/res/values/overlayable.xml
index 91fbd7d..7bd905c 100644
--- a/Tethering/res/values/overlayable.xml
+++ b/Tethering/res/values/overlayable.xml
@@ -32,6 +32,7 @@
<item type="bool" name="config_tether_enable_bpf_offload"/>
<item type="bool" name="config_tether_enable_legacy_dhcp_server"/>
<item type="bool" name="config_tether_enable_legacy_wifi_p2p_dedicated_ip"/>
+ <item type="integer" name="config_p2p_leases_subnet_prefix_length"/>
<item type="integer" name="config_tether_offload_poll_interval"/>
<item type="array" name="config_tether_upstream_types"/>
<item type="bool" name="config_tether_upstream_automatic"/>
diff --git a/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java b/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java
index aaaec17..8d58945 100644
--- a/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java
+++ b/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java
@@ -185,6 +185,16 @@
return this;
}
+ /** Set leases subnet prefix length. If the value is smaller than server address prefix length,
+ * this configuration will be ignored.
+ *
+ * <p>If not set, the default value is zero.
+ */
+ public DhcpServingParamsParcelExt setLeasesSubnetPrefixLength(int prefixLength) {
+ this.leasesSubnetPrefixLength = prefixLength;
+ return this;
+ }
+
private static int[] toIntArray(@NonNull Collection<Inet4Address> addrs) {
int[] res = new int[addrs.size()];
int i = 0;
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index acd2625..437ed71 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -69,6 +69,7 @@
import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
import com.android.networkstack.tethering.PrivateAddressCoordinator;
import com.android.networkstack.tethering.TetheringConfiguration;
+import com.android.networkstack.tethering.metrics.TetheringMetrics;
import com.android.networkstack.tethering.util.InterfaceSet;
import com.android.networkstack.tethering.util.PrefixUtils;
@@ -241,6 +242,7 @@
private final LinkProperties mLinkProperties;
private final boolean mUsingLegacyDhcp;
private final boolean mUsingBpfOffload;
+ private final int mP2pLeasesSubnetPrefixLength;
private final Dependencies mDeps;
@@ -281,13 +283,15 @@
private LinkAddress mIpv4Address;
+ private final TetheringMetrics mTetheringMetrics;
+
// TODO: Add a dependency object to pass the data members or variables from the tethering
// object. It helps to reduce the arguments of the constructor.
public IpServer(
String ifaceName, Looper looper, int interfaceType, SharedLog log,
INetd netd, @NonNull BpfCoordinator coordinator, Callback callback,
TetheringConfiguration config, PrivateAddressCoordinator addressCoordinator,
- Dependencies deps) {
+ TetheringMetrics tetheringMetrics, Dependencies deps) {
super(ifaceName, looper);
mLog = log.forSubComponent(ifaceName);
mNetd = netd;
@@ -299,8 +303,10 @@
mLinkProperties = new LinkProperties();
mUsingLegacyDhcp = config.useLegacyDhcpServer();
mUsingBpfOffload = config.isBpfOffloadEnabled();
+ mP2pLeasesSubnetPrefixLength = config.getP2pLeasesSubnetPrefixLength();
mPrivateAddressCoordinator = addressCoordinator;
mDeps = deps;
+ mTetheringMetrics = tetheringMetrics;
resetLinkProperties();
mLastError = TetheringManager.TETHER_ERROR_NO_ERROR;
mServingMode = STATE_AVAILABLE;
@@ -527,6 +533,9 @@
@Nullable Inet4Address clientAddr) {
final boolean changePrefixOnDecline =
(mInterfaceType == TetheringManager.TETHERING_NCM && clientAddr == null);
+ final int subnetPrefixLength = mInterfaceType == TetheringManager.TETHERING_WIFI_P2P
+ ? mP2pLeasesSubnetPrefixLength : 0 /* default value */;
+
return new DhcpServingParamsParcelExt()
.setDefaultRouters(defaultRouter)
.setDhcpLeaseTimeSecs(DHCP_LEASE_TIME_SECS)
@@ -534,7 +543,8 @@
.setServerAddr(serverAddr)
.setMetered(true)
.setSingleClientAddr(clientAddr)
- .setChangePrefixOnDecline(changePrefixOnDecline);
+ .setChangePrefixOnDecline(changePrefixOnDecline)
+ .setLeasesSubnetPrefixLength(subnetPrefixLength);
// TODO: also advertise link MTU
}
@@ -1195,6 +1205,9 @@
stopConntrackMonitoring();
resetLinkProperties();
+
+ mTetheringMetrics.updateErrorCode(mInterfaceType, mLastError);
+ mTetheringMetrics.sendReport(mInterfaceType);
}
@Override
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index 225bd58..c403548 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -65,15 +65,19 @@
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.InterfaceParams;
import com.android.net.module.util.NetworkStackConstants;
+import com.android.net.module.util.Struct;
import com.android.net.module.util.Struct.U32;
import com.android.net.module.util.bpf.Tether4Key;
import com.android.net.module.util.bpf.Tether4Value;
+import com.android.net.module.util.bpf.TetherStatsKey;
+import com.android.net.module.util.bpf.TetherStatsValue;
import com.android.net.module.util.netlink.ConntrackMessage;
import com.android.net.module.util.netlink.NetlinkConstants;
import com.android.net.module.util.netlink.NetlinkSocket;
import com.android.networkstack.tethering.apishim.common.BpfCoordinatorShim;
import com.android.networkstack.tethering.util.TetheringUtils.ForwardedStats;
+import java.io.IOException;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
@@ -118,6 +122,8 @@
private static final String TETHER_LIMIT_MAP_PATH = makeMapPath("limit");
private static final String TETHER_ERROR_MAP_PATH = makeMapPath("error");
private static final String TETHER_DEV_MAP_PATH = makeMapPath("dev");
+ private static final String DUMPSYS_RAWMAP_ARG_STATS = "--stats";
+ private static final String DUMPSYS_RAWMAP_ARG_UPSTREAM4 = "--upstream4";
// Using "," as a separator is safe because base64 characters are [0-9a-zA-Z/=+].
private static final String DUMP_BASE64_DELIMITER = ",";
@@ -1019,7 +1025,7 @@
map.forEach((k, v) -> {
pw.println(String.format("%s: %s", k, v));
});
- } catch (ErrnoException e) {
+ } catch (ErrnoException | IOException e) {
pw.println("Error dumping BPF stats map: " + e);
}
}
@@ -1067,12 +1073,13 @@
return;
}
map.forEach((k, v) -> pw.println(ipv6UpstreamRuletoString(k, v)));
- } catch (ErrnoException e) {
+ } catch (ErrnoException | IOException e) {
pw.println("Error dumping IPv6 upstream map: " + e);
}
}
- private String ipv4RuleToBase64String(Tether4Key key, Tether4Value value) {
+ private <K extends Struct, V extends Struct> String bpfMapEntryToBase64String(
+ final K key, final V value) {
final byte[] keyBytes = key.writeToBytes();
final String keyBase64Str = Base64.encodeToString(keyBytes, Base64.DEFAULT)
.replace("\n", "");
@@ -1083,28 +1090,45 @@
return keyBase64Str + DUMP_BASE64_DELIMITER + valueBase64Str;
}
- private void dumpRawIpv4ForwardingRuleMap(
- BpfMap<Tether4Key, Tether4Value> map, IndentingPrintWriter pw) throws ErrnoException {
+ private <K extends Struct, V extends Struct> void dumpRawMap(BpfMap<K, V> map,
+ IndentingPrintWriter pw) throws ErrnoException {
if (map == null) {
- pw.println("No IPv4 support");
+ pw.println("No BPF support");
return;
}
if (map.isEmpty()) {
- pw.println("No rules");
+ pw.println("No entries");
return;
}
- map.forEach((k, v) -> pw.println(ipv4RuleToBase64String(k, v)));
+ map.forEach((k, v) -> pw.println(bpfMapEntryToBase64String(k, v)));
}
/**
* Dump raw BPF map in base64 encoded strings. For test only.
+ * Only allow to dump one map path once.
+ * Format:
+ * $ dumpsys tethering bpfRawMap --<map name>
*/
- public void dumpRawMap(@NonNull IndentingPrintWriter pw) {
- try (BpfMap<Tether4Key, Tether4Value> upstreamMap = mDeps.getBpfUpstream4Map()) {
- // TODO: dump downstream map.
- dumpRawIpv4ForwardingRuleMap(upstreamMap, pw);
- } catch (ErrnoException e) {
- pw.println("Error dumping IPv4 map: " + e);
+ public void dumpRawMap(@NonNull IndentingPrintWriter pw, @Nullable String[] args) {
+ // TODO: consider checking the arg order that <map name> is after "bpfRawMap". Probably
+ // it is okay for now because this is used by test only and test is supposed to use
+ // expected argument order.
+ // TODO: dump downstream4 map.
+ if (CollectionUtils.contains(args, DUMPSYS_RAWMAP_ARG_STATS)) {
+ try (BpfMap<TetherStatsKey, TetherStatsValue> statsMap = mDeps.getBpfStatsMap()) {
+ dumpRawMap(statsMap, pw);
+ } catch (ErrnoException | IOException e) {
+ pw.println("Error dumping stats map: " + e);
+ }
+ return;
+ }
+ if (CollectionUtils.contains(args, DUMPSYS_RAWMAP_ARG_UPSTREAM4)) {
+ try (BpfMap<Tether4Key, Tether4Value> upstreamMap = mDeps.getBpfUpstream4Map()) {
+ dumpRawMap(upstreamMap, pw);
+ } catch (ErrnoException | IOException e) {
+ pw.println("Error dumping IPv4 map: " + e);
+ }
+ return;
}
}
@@ -1172,7 +1196,7 @@
pw.increaseIndent();
dumpIpv4ForwardingRuleMap(now, DOWNSTREAM, downstreamMap, pw);
pw.decreaseIndent();
- } catch (ErrnoException e) {
+ } catch (ErrnoException | IOException e) {
pw.println("Error dumping IPv4 map: " + e);
}
}
@@ -1197,7 +1221,7 @@
}
if (v.val > 0) pw.println(String.format("%s: %d", counterName, v.val));
});
- } catch (ErrnoException e) {
+ } catch (ErrnoException | IOException e) {
pw.println("Error dumping counter map: " + e);
}
}
@@ -1221,7 +1245,7 @@
pw.println(String.format("%d (%s) -> %d (%s)", k.ifIndex, getIfName(k.ifIndex),
v.ifIndex, getIfName(v.ifIndex)));
});
- } catch (ErrnoException e) {
+ } catch (ErrnoException | IOException e) {
pw.println("Error dumping dev map: " + e);
}
pw.decreaseIndent();
@@ -2033,5 +2057,13 @@
return mBpfConntrackEventConsumer;
}
+ // Return tethering client information. This is used for testing only.
+ @NonNull
+ @VisibleForTesting
+ final HashMap<IpServer, HashMap<Inet4Address, ClientInfo>>
+ getTetherClientsForTesting() {
+ return mTetherClients;
+ }
+
private static native String[] getBpfCounterNames();
}
diff --git a/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java b/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
index 844efde..adc95ab 100644
--- a/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
+++ b/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
@@ -34,7 +34,6 @@
import static android.net.TetheringManager.TETHER_ERROR_PROVISIONING_FAILED;
import static com.android.networkstack.apishim.ConstantsShim.ACTION_TETHER_UNSUPPORTED_CARRIER_UI;
-import static com.android.networkstack.apishim.ConstantsShim.KEY_CARRIER_SUPPORTS_TETHERING_BOOL;
import android.app.AlarmManager;
import android.app.PendingIntent;
@@ -48,12 +47,10 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcel;
-import android.os.PersistableBundle;
import android.os.ResultReceiver;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.provider.Settings;
-import android.telephony.CarrierConfigManager;
import android.util.SparseIntArray;
import com.android.internal.annotations.VisibleForTesting;
@@ -307,13 +304,13 @@
if (SystemProperties.getBoolean(DISABLE_PROVISIONING_SYSPROP_KEY, false)) {
return TETHERING_PROVISIONING_NOT_REQUIRED;
}
- // TODO: Find a way to avoid get carrier config twice.
- if (carrierConfigAffirmsCarrierNotSupport(config)) {
+
+ if (!config.isCarrierSupportTethering) {
// To block tethering, behave as if running provisioning check and failed.
return TETHERING_PROVISIONING_CARRIER_UNSUPPORT;
}
- if (carrierConfigAffirmsEntitlementCheckNotRequired(config)) {
+ if (!config.isCarrierConfigAffirmsEntitlementCheckRequired) {
return TETHERING_PROVISIONING_NOT_REQUIRED;
}
return (config.provisioningApp.length == 2)
@@ -380,57 +377,6 @@
}
/**
- * Get carrier configuration bundle.
- * @param config an object that encapsulates the various tethering configuration elements.
- * */
- public PersistableBundle getCarrierConfig(final TetheringConfiguration config) {
- final CarrierConfigManager configManager = mContext
- .getSystemService(CarrierConfigManager.class);
- if (configManager == null) return null;
-
- final PersistableBundle carrierConfig = configManager.getConfigForSubId(
- config.activeDataSubId);
-
- if (CarrierConfigManager.isConfigForIdentifiedCarrier(carrierConfig)) {
- return carrierConfig;
- }
-
- return null;
- }
-
- // The logic here is aimed solely at confirming that a CarrierConfig exists
- // and affirms that entitlement checks are not required.
- //
- // TODO: find a better way to express this, or alter the checking process
- // entirely so that this is more intuitive.
- // TODO: Find a way to avoid using getCarrierConfig everytime.
- private boolean carrierConfigAffirmsEntitlementCheckNotRequired(
- final TetheringConfiguration config) {
- // Check carrier config for entitlement checks
- final PersistableBundle carrierConfig = getCarrierConfig(config);
- if (carrierConfig == null) return false;
-
- // A CarrierConfigManager was found and it has a config.
- final boolean isEntitlementCheckRequired = carrierConfig.getBoolean(
- CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL);
- return !isEntitlementCheckRequired;
- }
-
- private boolean carrierConfigAffirmsCarrierNotSupport(final TetheringConfiguration config) {
- if (!SdkLevel.isAtLeastT()) {
- return false;
- }
- // Check carrier config for entitlement checks
- final PersistableBundle carrierConfig = getCarrierConfig(config);
- if (carrierConfig == null) return false;
-
- // A CarrierConfigManager was found and it has a config.
- final boolean mIsCarrierSupport = carrierConfig.getBoolean(
- KEY_CARRIER_SUPPORTS_TETHERING_BOOL, true);
- return !mIsCarrierSupport;
- }
-
- /**
* Run no UI tethering provisioning check.
* @param type tethering type from TetheringManager.TETHERING_{@code *}
* @param subId default data subscription ID.
@@ -479,7 +425,7 @@
private void runTetheringProvisioning(
boolean showProvisioningUi, int downstreamType, final TetheringConfiguration config) {
- if (carrierConfigAffirmsCarrierNotSupport(config)) {
+ if (!config.isCarrierSupportTethering) {
mListener.onTetherProvisioningFailed(downstreamType, "Carrier does not support.");
if (showProvisioningUi) {
showCarrierUnsupportedDialog();
@@ -497,7 +443,7 @@
}
private void showCarrierUnsupportedDialog() {
- // This is only used when carrierConfigAffirmsCarrierNotSupport() is true.
+ // This is only used when TetheringConfiguration.isCarrierSupportTethering is false.
if (!SdkLevel.isAtLeastT()) {
return;
}
diff --git a/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java b/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
index cc2422f..41a10ae 100644
--- a/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
@@ -172,6 +172,9 @@
return new LinkAddress(LEGACY_WIFI_P2P_IFACE_ADDRESS);
}
+ // This ensures that tethering isn't started on 2 different interfaces with the same type.
+ // Once tethering could support multiple interface with the same type,
+ // TetheringSoftApCallback would need to handle it among others.
final LinkAddress cachedAddress = mCachedAddresses.get(ipServer.interfaceType());
if (useLastAddress && cachedAddress != null
&& !isConflictWithUpstream(asIpPrefix(cachedAddress))) {
diff --git a/Tethering/src/com/android/networkstack/tethering/TetherStatsKey.java b/Tethering/src/com/android/networkstack/tethering/TetherStatsKey.java
deleted file mode 100644
index 5442480..0000000
--- a/Tethering/src/com/android/networkstack/tethering/TetherStatsKey.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.networkstack.tethering;
-
-import com.android.net.module.util.Struct;
-import com.android.net.module.util.Struct.Field;
-import com.android.net.module.util.Struct.Type;
-
-/** The key of BpfMap which is used for tethering stats. */
-public class TetherStatsKey extends Struct {
- @Field(order = 0, type = Type.U32)
- public final long ifindex; // upstream interface index
-
- public TetherStatsKey(final long ifindex) {
- this.ifindex = ifindex;
- }
-
- // TODO: remove equals, hashCode and toString once aosp/1536721 is merged.
- @Override
- public boolean equals(Object obj) {
- if (this == obj) return true;
-
- if (!(obj instanceof TetherStatsKey)) return false;
-
- final TetherStatsKey that = (TetherStatsKey) obj;
-
- return ifindex == that.ifindex;
- }
-
- @Override
- public int hashCode() {
- return Long.hashCode(ifindex);
- }
-
- @Override
- public String toString() {
- return String.format("ifindex: %d", ifindex);
- }
-}
diff --git a/Tethering/src/com/android/networkstack/tethering/TetherStatsValue.java b/Tethering/src/com/android/networkstack/tethering/TetherStatsValue.java
deleted file mode 100644
index 844d2e8..0000000
--- a/Tethering/src/com/android/networkstack/tethering/TetherStatsValue.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.networkstack.tethering;
-
-import com.android.net.module.util.Struct;
-import com.android.net.module.util.Struct.Field;
-import com.android.net.module.util.Struct.Type;
-
-/** The key of BpfMap which is used for tethering stats. */
-public class TetherStatsValue extends Struct {
- // Use the signed long variable to store the uint64 stats from stats BPF map.
- // U63 is enough for each data element even at 5Gbps for ~468 years.
- // 2^63 / (5 * 1000 * 1000 * 1000) * 8 / 86400 / 365 = 468.
- @Field(order = 0, type = Type.U63)
- public final long rxPackets;
- @Field(order = 1, type = Type.U63)
- public final long rxBytes;
- @Field(order = 2, type = Type.U63)
- public final long rxErrors;
- @Field(order = 3, type = Type.U63)
- public final long txPackets;
- @Field(order = 4, type = Type.U63)
- public final long txBytes;
- @Field(order = 5, type = Type.U63)
- public final long txErrors;
-
- public TetherStatsValue(final long rxPackets, final long rxBytes, final long rxErrors,
- final long txPackets, final long txBytes, final long txErrors) {
- this.rxPackets = rxPackets;
- this.rxBytes = rxBytes;
- this.rxErrors = rxErrors;
- this.txPackets = txPackets;
- this.txBytes = txBytes;
- this.txErrors = txErrors;
- }
-
- // TODO: remove equals, hashCode and toString once aosp/1536721 is merged.
- @Override
- public boolean equals(Object obj) {
- if (this == obj) return true;
-
- if (!(obj instanceof TetherStatsValue)) return false;
-
- final TetherStatsValue that = (TetherStatsValue) obj;
-
- return rxPackets == that.rxPackets
- && rxBytes == that.rxBytes
- && rxErrors == that.rxErrors
- && txPackets == that.txPackets
- && txBytes == that.txBytes
- && txErrors == that.txErrors;
- }
-
- @Override
- public int hashCode() {
- return Long.hashCode(rxPackets) ^ Long.hashCode(rxBytes) ^ Long.hashCode(rxErrors)
- ^ Long.hashCode(txPackets) ^ Long.hashCode(txBytes) ^ Long.hashCode(txErrors);
- }
-
- @Override
- public String toString() {
- return String.format("rxPackets: %s, rxBytes: %s, rxErrors: %s, txPackets: %s, "
- + "txBytes: %s, txErrors: %s", rxPackets, rxBytes, rxErrors, txPackets,
- txBytes, txErrors);
- }
-}
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 0b607bd..af017f3 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -135,10 +135,12 @@
import com.android.internal.util.StateMachine;
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
+import com.android.net.module.util.CollectionUtils;
import com.android.networkstack.apishim.common.BluetoothPanShim;
import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceCallbackShim;
import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceRequestShim;
import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
+import com.android.networkstack.tethering.metrics.TetheringMetrics;
import com.android.networkstack.tethering.util.InterfaceSet;
import com.android.networkstack.tethering.util.PrefixUtils;
import com.android.networkstack.tethering.util.TetheringUtils;
@@ -253,6 +255,7 @@
private final UserManager mUserManager;
private final BpfCoordinator mBpfCoordinator;
private final PrivateAddressCoordinator mPrivateAddressCoordinator;
+ private final TetheringMetrics mTetheringMetrics;
private int mActiveDataSubId = INVALID_SUBSCRIPTION_ID;
private volatile TetheringConfiguration mConfig;
@@ -278,6 +281,11 @@
private BluetoothPan mBluetoothPan;
private PanServiceListener mBluetoothPanListener;
private ArrayList<Pair<Boolean, IIntResultListener>> mPendingPanRequests;
+ // AIDL doesn't support Set<Integer>. Maintain a int bitmap here. When the bitmap is passed to
+ // TetheringManager, TetheringManager would convert it to a set of Integer types.
+ // mSupportedTypeBitmap should always be updated inside tethering internal thread but it may be
+ // read from binder thread which called TetheringService directly.
+ private volatile long mSupportedTypeBitmap;
public Tethering(TetheringDependencies deps) {
mLog.mark("Tethering.constructed");
@@ -286,6 +294,7 @@
mNetd = mDeps.getINetd(mContext);
mLooper = mDeps.getTetheringLooper();
mNotificationUpdater = mDeps.getNotificationUpdater(mContext, mLooper);
+ mTetheringMetrics = mDeps.getTetheringMetrics();
// This is intended to ensrure that if something calls startTethering(bluetooth) just after
// bluetooth is enabled. Before onServiceConnected is called, store the calls into this
@@ -439,8 +448,22 @@
mStateReceiver, noUpstreamFilter, PERMISSION_MAINLINE_NETWORK_STACK, mHandler);
final WifiManager wifiManager = getWifiManager();
+ TetheringSoftApCallback softApCallback = new TetheringSoftApCallback();
if (wifiManager != null) {
- wifiManager.registerSoftApCallback(mExecutor, new TetheringSoftApCallback());
+ wifiManager.registerSoftApCallback(mExecutor, softApCallback);
+ }
+ if (SdkLevel.isAtLeastT() && wifiManager != null) {
+ try {
+ // Although WifiManager#registerLocalOnlyHotspotSoftApCallback document that it need
+ // NEARBY_WIFI_DEVICES permission, but actually a caller who have NETWORK_STACK
+ // or MAINLINE_NETWORK_STACK permission would also able to use this API.
+ wifiManager.registerLocalOnlyHotspotSoftApCallback(mExecutor, softApCallback);
+ } catch (UnsupportedOperationException e) {
+ // Since wifi module development in internal branch,
+ // #registerLocalOnlyHotspotSoftApCallback currently doesn't supported in AOSP
+ // before AOSP switch to Android T + 1.
+ Log.wtf(TAG, "registerLocalOnlyHotspotSoftApCallback API is not supported");
+ }
}
startTrackDefaultNetwork();
@@ -476,7 +499,7 @@
// To avoid launching unexpected provisioning checks, ignore re-provisioning
// when no CarrierConfig loaded yet. Assume reevaluateSimCardProvisioning()
// will be triggered again when CarrierConfig is loaded.
- if (mEntitlementMgr.getCarrierConfig(mConfig) != null) {
+ if (TetheringConfiguration.getCarrierConfig(mContext, subId) != null) {
mEntitlementMgr.reevaluateSimCardProvisioning(mConfig);
} else {
mLog.log("IGNORED reevaluate provisioning, no carrier config loaded");
@@ -494,6 +517,8 @@
mUpstreamNetworkMonitor.setUpstreamConfig(mConfig.chooseUpstreamAutomatically,
mConfig.isDunRequired);
reportConfigurationChanged(mConfig.toStableParcelable());
+
+ updateSupportedDownstreams(mConfig);
}
private void maybeDunSettingChanged() {
@@ -534,6 +559,13 @@
}
// Called by wifi when the number of soft AP clients changed.
+ // Currently multiple softAp would not behave well in PrivateAddressCoordinator
+ // (where it gets the address from cache), it ensure tethering only support one ipServer for
+ // TETHERING_WIFI. Once tethering support multiple softAp enabled simultaneously,
+ // onConnectedClientsChanged should also be updated to support tracking different softAp's
+ // clients individually.
+ // TODO: Add wtf log and have check to reject request duplicated type with different
+ // interface.
@Override
public void onConnectedClientsChanged(final List<WifiClient> clients) {
updateConnectedClients(clients);
@@ -608,7 +640,8 @@
processInterfaceStateChange(iface, false /* enabled */);
}
- void startTethering(final TetheringRequestParcel request, final IIntResultListener listener) {
+ void startTethering(final TetheringRequestParcel request, final String callerPkg,
+ final IIntResultListener listener) {
mHandler.post(() -> {
final TetheringRequestParcel unfinishedRequest = mActiveTetheringRequests.get(
request.tetheringType);
@@ -628,6 +661,7 @@
request.showProvisioningUi);
}
enableTetheringInternal(request.tetheringType, true /* enabled */, listener);
+ mTetheringMetrics.createBuilder(request.tetheringType, callerPkg);
});
}
@@ -687,7 +721,11 @@
// If changing tethering fail, remove corresponding request
// no matter who trigger the start/stop.
- if (result != TETHER_ERROR_NO_ERROR) mActiveTetheringRequests.remove(type);
+ if (result != TETHER_ERROR_NO_ERROR) {
+ mActiveTetheringRequests.remove(type);
+ mTetheringMetrics.updateErrorCode(type, result);
+ mTetheringMetrics.sendReport(type);
+ }
}
private int setWifiTethering(final boolean enable) {
@@ -1280,7 +1318,7 @@
// Finally bring up serving on the new interface
mWifiP2pTetherInterface = group.getInterface();
- enableWifiIpServing(mWifiP2pTetherInterface, IFACE_IP_MODE_LOCAL_ONLY);
+ enableWifiP2pIpServing(mWifiP2pTetherInterface);
}
private void handleUserRestrictionAction() {
@@ -1371,20 +1409,22 @@
changeInterfaceState(ifname, ipServingMode);
}
- private void disableWifiIpServingCommon(int tetheringType, String ifname, int apState) {
- mLog.log("Canceling WiFi tethering request -"
- + " type=" + tetheringType
- + " interface=" + ifname
- + " state=" + apState);
-
- if (!TextUtils.isEmpty(ifname)) {
- final TetherState ts = mTetherStates.get(ifname);
- if (ts != null) {
- ts.ipServer.unwanted();
- return;
- }
+ private void disableWifiIpServingCommon(int tetheringType, String ifname) {
+ if (!TextUtils.isEmpty(ifname) && mTetherStates.containsKey(ifname)) {
+ mTetherStates.get(ifname).ipServer.unwanted();
+ return;
}
+ if (SdkLevel.isAtLeastT()) {
+ mLog.e("Tethering no longer handle untracked interface after T: " + ifname);
+ return;
+ }
+
+ // Attempt to guess the interface name before T. Pure AOSP code should never enter here
+ // because WIFI_AP_STATE_CHANGED intent always include ifname and it should be tracked
+ // by mTetherStates. In case OEMs have some modification in wifi side which pass null
+ // or empty ifname. Before T, tethering allow to disable the first wifi ipServer if
+ // given ifname don't match any tracking ipServer.
for (int i = 0; i < mTetherStates.size(); i++) {
final IpServer ipServer = mTetherStates.valueAt(i).ipServer;
if (ipServer.interfaceType() == tetheringType) {
@@ -1392,7 +1432,6 @@
return;
}
}
-
mLog.log("Error disabling Wi-Fi IP serving; "
+ (TextUtils.isEmpty(ifname) ? "no interface name specified"
: "specified interface: " + ifname));
@@ -1401,20 +1440,39 @@
private void disableWifiIpServing(String ifname, int apState) {
// Regardless of whether we requested this transition, the AP has gone
// down. Don't try to tether again unless we're requested to do so.
- // TODO: Remove this altogether, once Wi-Fi reliably gives us an
- // interface name with every broadcast.
mWifiTetherRequested = false;
- disableWifiIpServingCommon(TETHERING_WIFI, ifname, apState);
+ mLog.log("Canceling WiFi tethering request - interface=" + ifname + " state=" + apState);
+
+ disableWifiIpServingCommon(TETHERING_WIFI, ifname);
+ }
+
+ private void enableWifiP2pIpServing(String ifname) {
+ if (TextUtils.isEmpty(ifname)) {
+ mLog.e("Cannot enable P2P IP serving with invalid interface");
+ return;
+ }
+
+ // After T, tethering always trust the iface pass by state change intent. This allow
+ // tethering to deprecate tetherable p2p regexs after T.
+ final int type = SdkLevel.isAtLeastT() ? TETHERING_WIFI_P2P : ifaceNameToType(ifname);
+ if (!checkTetherableType(type)) {
+ mLog.e(ifname + " is not a tetherable iface, ignoring");
+ return;
+ }
+ enableIpServing(type, ifname, IpServer.STATE_LOCAL_ONLY);
}
private void disableWifiP2pIpServingIfNeeded(String ifname) {
if (TextUtils.isEmpty(ifname)) return;
- disableWifiIpServingCommon(TETHERING_WIFI_P2P, ifname, /* fake */ 0);
+ mLog.log("Canceling P2P tethering request - interface=" + ifname);
+ disableWifiIpServingCommon(TETHERING_WIFI_P2P, ifname);
}
private void enableWifiIpServing(String ifname, int wifiIpMode) {
+ mLog.log("request WiFi tethering - interface=" + ifname + " state=" + wifiIpMode);
+
// Map wifiIpMode values to IpServer.Callback serving states, inferring
// from mWifiTetherRequested as a final "best guess".
final int ipServingMode;
@@ -1430,13 +1488,18 @@
return;
}
+ // After T, tethering always trust the iface pass by state change intent. This allow
+ // tethering to deprecate tetherable wifi regexs after T.
+ final int type = SdkLevel.isAtLeastT() ? TETHERING_WIFI : ifaceNameToType(ifname);
+ if (!checkTetherableType(type)) {
+ mLog.e(ifname + " is not a tetherable iface, ignoring");
+ return;
+ }
+
if (!TextUtils.isEmpty(ifname)) {
- ensureIpServerStarted(ifname);
- changeInterfaceState(ifname, ipServingMode);
+ enableIpServing(type, ifname, ipServingMode);
} else {
- mLog.e(String.format(
- "Cannot enable IP serving in mode %s on missing interface name",
- ipServingMode));
+ mLog.e("Cannot enable IP serving on missing interface name");
}
}
@@ -1513,26 +1576,6 @@
return mConfig;
}
- boolean hasAnySupportedDownstream() {
- if ((mConfig.tetherableUsbRegexs.length != 0)
- || (mConfig.tetherableWifiRegexs.length != 0)
- || (mConfig.tetherableBluetoothRegexs.length != 0)) {
- return true;
- }
-
- // Before T, isTetheringSupported would return true if wifi, usb and bluetooth tethering are
- // disabled (whole tethering settings would be hidden). This means tethering would also not
- // support wifi p2p, ethernet tethering and mirrorlink. This is wrong but probably there are
- // some devices in the field rely on this to disable tethering entirely.
- if (!SdkLevel.isAtLeastT()) return false;
-
- return (mConfig.tetherableWifiP2pRegexs.length != 0)
- || (mConfig.tetherableNcmRegexs.length != 0)
- || isEthernetSupported();
- }
-
- // TODO: using EtherentManager new API to check whether ethernet is supported when the API is
- // ready to use.
private boolean isEthernetSupported() {
return mContext.getSystemService(Context.ETHERNET_SERVICE) != null;
}
@@ -2322,7 +2365,7 @@
mHandler.post(() -> {
mTetheringEventCallbacks.register(callback, new CallbackCookie(hasListPermission));
final TetheringCallbackStartedParcel parcel = new TetheringCallbackStartedParcel();
- parcel.tetheringSupported = isTetheringSupported();
+ parcel.supportedTypes = mSupportedTypeBitmap;
parcel.upstreamNetwork = mTetherUpstream;
parcel.config = mConfig.toStableParcelable();
parcel.states =
@@ -2361,6 +2404,22 @@
});
}
+ private void reportTetheringSupportedChange(final long supportedBitmap) {
+ final int length = mTetheringEventCallbacks.beginBroadcast();
+ try {
+ for (int i = 0; i < length; i++) {
+ try {
+ mTetheringEventCallbacks.getBroadcastItem(i).onSupportedTetheringTypes(
+ supportedBitmap);
+ } catch (RemoteException e) {
+ // Not really very much to do here.
+ }
+ }
+ } finally {
+ mTetheringEventCallbacks.finishBroadcast();
+ }
+ }
+
private void reportUpstreamChanged(UpstreamNetworkState ns) {
final int length = mTetheringEventCallbacks.beginBroadcast();
final Network network = (ns != null) ? ns.network : null;
@@ -2445,18 +2504,56 @@
}
}
+ private void updateSupportedDownstreams(final TetheringConfiguration config) {
+ final long preSupportedBitmap = mSupportedTypeBitmap;
+
+ if (!isTetheringAllowed() || mEntitlementMgr.isProvisioningNeededButUnavailable()) {
+ mSupportedTypeBitmap = 0;
+ } else {
+ mSupportedTypeBitmap = makeSupportedDownstreams(config);
+ }
+
+ if (preSupportedBitmap != mSupportedTypeBitmap) {
+ reportTetheringSupportedChange(mSupportedTypeBitmap);
+ }
+ }
+
+ private long makeSupportedDownstreams(final TetheringConfiguration config) {
+ long types = 0;
+ if (config.tetherableUsbRegexs.length != 0) types |= (1 << TETHERING_USB);
+
+ if (config.tetherableWifiRegexs.length != 0) types |= (1 << TETHERING_WIFI);
+
+ if (config.tetherableBluetoothRegexs.length != 0) types |= (1 << TETHERING_BLUETOOTH);
+
+ // Before T, isTetheringSupported would return true if wifi, usb and bluetooth tethering are
+ // disabled (whole tethering settings would be hidden). This means tethering would also not
+ // support wifi p2p, ethernet tethering and mirrorlink. This is wrong but probably there are
+ // some devices in the field rely on this to disable tethering entirely.
+ if (!SdkLevel.isAtLeastT() && types == 0) return types;
+
+ if (config.tetherableNcmRegexs.length != 0) types |= (1 << TETHERING_NCM);
+
+ if (config.tetherableWifiP2pRegexs.length != 0) types |= (1 << TETHERING_WIFI_P2P);
+
+ if (isEthernetSupported()) types |= (1 << TETHERING_ETHERNET);
+
+ return types;
+ }
+
// if ro.tether.denied = true we default to no tethering
// gservices could set the secure setting to 1 though to enable it on a build where it
// had previously been turned off.
- boolean isTetheringSupported() {
+ private boolean isTetheringAllowed() {
final int defaultVal = mDeps.isTetheringDenied() ? 0 : 1;
final boolean tetherSupported = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.TETHER_SUPPORTED, defaultVal) != 0;
- final boolean tetherEnabledInSettings = tetherSupported
+ return tetherSupported
&& !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING);
+ }
- return tetherEnabledInSettings && hasAnySupportedDownstream()
- && !mEntitlementMgr.isProvisioningNeededButUnavailable();
+ boolean isTetheringSupported() {
+ return mSupportedTypeBitmap > 0;
}
private void dumpBpf(IndentingPrintWriter pw) {
@@ -2472,13 +2569,12 @@
writer, " ");
// Used for testing instead of human debug.
- // TODO: add options to choose which map to dump.
- if (argsContain(args, "bpfRawMap")) {
- mBpfCoordinator.dumpRawMap(pw);
+ if (CollectionUtils.contains(args, "bpfRawMap")) {
+ mBpfCoordinator.dumpRawMap(pw, args);
return;
}
- if (argsContain(args, "bpf")) {
+ if (CollectionUtils.contains(args, "bpf")) {
dumpBpf(pw);
return;
}
@@ -2544,7 +2640,7 @@
pw.println("Log:");
pw.increaseIndent();
- if (argsContain(args, "--short")) {
+ if (CollectionUtils.contains(args, "--short")) {
pw.println("<log removed for brevity>");
} else {
mLog.dump(fd, pw, args);
@@ -2588,13 +2684,6 @@
if (e != null) throw e;
}
- private static boolean argsContain(String[] args, String target) {
- for (String arg : args) {
- if (target.equals(arg)) return true;
- }
- return false;
- }
-
private void updateConnectedClients(final List<WifiClient> wifiClients) {
if (mConnectedClientsTracker.updateConnectedClients(mTetherMainSM.getAllDownstreams(),
wifiClients)) {
@@ -2681,23 +2770,28 @@
mTetherMainSM.sendMessage(which, state, 0, newLp);
}
+ private boolean hasSystemFeature(final String feature) {
+ return mContext.getPackageManager().hasSystemFeature(feature);
+ }
+
+ private boolean checkTetherableType(int type) {
+ if ((type == TETHERING_WIFI || type == TETHERING_WIGIG)
+ && !hasSystemFeature(PackageManager.FEATURE_WIFI)) {
+ return false;
+ }
+
+ if (type == TETHERING_WIFI_P2P && !hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)) {
+ return false;
+ }
+
+ return type != TETHERING_INVALID;
+ }
+
private void ensureIpServerStarted(final String iface) {
// If we don't care about this type of interface, ignore.
final int interfaceType = ifaceNameToType(iface);
- if (interfaceType == TETHERING_INVALID) {
- mLog.log(iface + " is not a tetherable iface, ignoring");
- return;
- }
-
- final PackageManager pm = mContext.getPackageManager();
- if ((interfaceType == TETHERING_WIFI || interfaceType == TETHERING_WIGIG)
- && !pm.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
- mLog.log(iface + " is not tetherable, because WiFi feature is disabled");
- return;
- }
- if (interfaceType == TETHERING_WIFI_P2P
- && !pm.hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)) {
- mLog.log(iface + " is not tetherable, because WiFi Direct feature is disabled");
+ if (!checkTetherableType(interfaceType)) {
+ mLog.log(iface + " is used for " + interfaceType + " which is not tetherable");
return;
}
@@ -2715,7 +2809,7 @@
final TetherState tetherState = new TetherState(
new IpServer(iface, mLooper, interfaceType, mLog, mNetd, mBpfCoordinator,
makeControlCallback(), mConfig, mPrivateAddressCoordinator,
- mDeps.getIpServerDependencies()), isNcm);
+ mTetheringMetrics, mDeps.getIpServerDependencies()), isNcm);
mTetherStates.put(iface, tetherState);
tetherState.ipServer.start();
}
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
index eaf8589..7c36054 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
@@ -24,14 +24,17 @@
import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
import static com.android.net.module.util.DeviceConfigUtils.TETHERING_MODULE_NAME;
+import static com.android.networkstack.apishim.ConstantsShim.KEY_CARRIER_SUPPORTS_TETHERING_BOOL;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.net.TetheringConfigurationParcel;
import android.net.util.SharedLog;
+import android.os.PersistableBundle;
import android.provider.DeviceConfig;
import android.provider.Settings;
+import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
@@ -142,6 +145,9 @@
public final int provisioningCheckPeriod;
public final String provisioningResponse;
+ public final boolean isCarrierSupportTethering;
+ public final boolean isCarrierConfigAffirmsEntitlementCheckRequired;
+
public final int activeDataSubId;
private final boolean mEnableLegacyDhcpServer;
@@ -149,6 +155,7 @@
// TODO: Add to TetheringConfigurationParcel if required.
private final boolean mEnableBpfOffload;
private final boolean mEnableWifiP2pDedicatedIp;
+ private final int mP2pLeasesSubnetPrefixLength;
private final int mUsbTetheringFunction;
protected final ContentResolver mContentResolver;
@@ -206,6 +213,11 @@
provisioningResponse = getResourceString(res,
R.string.config_mobile_hotspot_provision_response);
+ PersistableBundle carrierConfigs = getCarrierConfig(ctx, activeDataSubId);
+ isCarrierSupportTethering = carrierConfigAffirmsCarrierSupport(carrierConfigs);
+ isCarrierConfigAffirmsEntitlementCheckRequired =
+ carrierConfigAffirmsEntitlementCheckRequired(carrierConfigs);
+
mOffloadPollInterval = getResourceInteger(res,
R.integer.config_tether_offload_poll_interval,
DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS);
@@ -214,9 +226,27 @@
R.bool.config_tether_enable_legacy_wifi_p2p_dedicated_ip,
false /* defaultValue */);
+ mP2pLeasesSubnetPrefixLength = getP2pLeasesSubnetPrefixLengthFromRes(res, configLog);
+
configLog.log(toString());
}
+ private int getP2pLeasesSubnetPrefixLengthFromRes(final Resources res, final SharedLog log) {
+ if (!mEnableWifiP2pDedicatedIp) return 0;
+
+ int prefixLength = getResourceInteger(res,
+ R.integer.config_p2p_leases_subnet_prefix_length, 0 /* default value */);
+
+ // DhcpLeaseRepository ignores the first and last addresses of the range so the max prefix
+ // length is 30.
+ if (prefixLength < 0 || prefixLength > 30) {
+ log.e("Invalid p2p leases subnet prefix length configuration: " + prefixLength);
+ return 0;
+ }
+
+ return prefixLength;
+ }
+
/** Check whether using legacy dhcp server. */
public boolean useLegacyDhcpServer() {
return mEnableLegacyDhcpServer;
@@ -272,6 +302,15 @@
return mEnableWifiP2pDedicatedIp;
}
+ /**
+ * Get subnet prefix length of dhcp leases for wifi p2p.
+ * This feature only support when wifi p2p use dedicated address. If
+ * #shouldEnableWifiP2pDedicatedIp is false, this method would always return 0.
+ */
+ public int getP2pLeasesSubnetPrefixLength() {
+ return mP2pLeasesSubnetPrefixLength;
+ }
+
/** Does the dumping.*/
public void dump(PrintWriter pw) {
pw.print("activeDataSubId: ");
@@ -301,6 +340,10 @@
pw.print("provisioningAppNoUi: ");
pw.println(provisioningAppNoUi);
+ pw.println("isCarrierSupportTethering: " + isCarrierSupportTethering);
+ pw.println("isCarrierConfigAffirmsEntitlementCheckRequired: "
+ + isCarrierConfigAffirmsEntitlementCheckRequired);
+
pw.print("enableBpfOffload: ");
pw.println(mEnableBpfOffload);
@@ -310,6 +353,9 @@
pw.print("enableWifiP2pDedicatedIp: ");
pw.println(mEnableWifiP2pDedicatedIp);
+ pw.print("p2pLeasesSubnetPrefixLength: ");
+ pw.println(mP2pLeasesSubnetPrefixLength);
+
pw.print("mUsbTetheringFunction: ");
pw.println(isUsingNcm() ? "NCM" : "RNDIS");
}
@@ -330,6 +376,9 @@
toIntArray(preferredUpstreamIfaceTypes)));
sj.add(String.format("provisioningApp:%s", makeString(provisioningApp)));
sj.add(String.format("provisioningAppNoUi:%s", provisioningAppNoUi));
+ sj.add(String.format("isCarrierSupportTethering:%s", isCarrierSupportTethering));
+ sj.add(String.format("isCarrierConfigAffirmsEntitlementCheckRequired:%s",
+ isCarrierConfigAffirmsEntitlementCheckRequired));
sj.add(String.format("enableBpfOffload:%s", mEnableBpfOffload));
sj.add(String.format("enableLegacyDhcpServer:%s", mEnableLegacyDhcpServer));
return String.format("TetheringConfiguration{%s}", sj.toString());
@@ -565,6 +614,39 @@
return result;
}
+ private static boolean carrierConfigAffirmsEntitlementCheckRequired(
+ PersistableBundle carrierConfig) {
+ if (carrierConfig == null) {
+ return true;
+ }
+ return carrierConfig.getBoolean(
+ CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL, true);
+ }
+
+ private static boolean carrierConfigAffirmsCarrierSupport(PersistableBundle carrierConfig) {
+ if (!SdkLevel.isAtLeastT() || carrierConfig == null) {
+ return true;
+ }
+ return carrierConfig.getBoolean(KEY_CARRIER_SUPPORTS_TETHERING_BOOL, true);
+ }
+
+ /**
+ * Get carrier configuration bundle.
+ */
+ public static PersistableBundle getCarrierConfig(Context context, int activeDataSubId) {
+ final CarrierConfigManager configManager =
+ context.getSystemService(CarrierConfigManager.class);
+ if (configManager == null) {
+ return null;
+ }
+
+ final PersistableBundle carrierConfig = configManager.getConfigForSubId(activeDataSubId);
+ if (CarrierConfigManager.isConfigForIdentifiedCarrier(carrierConfig)) {
+ return carrierConfig;
+ }
+ return null;
+ }
+
/**
* Convert this TetheringConfiguration to a TetheringConfigurationParcel.
*/
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
index 9224213..8e0354d 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
@@ -34,6 +34,7 @@
import com.android.internal.util.StateMachine;
import com.android.networkstack.apishim.BluetoothPanShimImpl;
import com.android.networkstack.apishim.common.BluetoothPanShim;
+import com.android.networkstack.tethering.metrics.TetheringMetrics;
import java.util.ArrayList;
@@ -163,4 +164,11 @@
public BluetoothPanShim getBluetoothPanShim(BluetoothPan pan) {
return BluetoothPanShimImpl.newInstance(pan);
}
+
+ /**
+ * Get a reference to the TetheringMetrics to be used by tethering.
+ */
+ public TetheringMetrics getTetheringMetrics() {
+ return new TetheringMetrics();
+ }
}
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringService.java b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
index 9fb61fe..f147e10 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringService.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
@@ -137,7 +137,7 @@
return;
}
- mTethering.startTethering(request, listener);
+ mTethering.startTethering(request, callerPkg, listener);
}
@Override
diff --git a/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java b/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java
new file mode 100644
index 0000000..e25f2ae
--- /dev/null
+++ b/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.tethering.metrics;
+
+import static android.net.TetheringManager.TETHERING_BLUETOOTH;
+import static android.net.TetheringManager.TETHERING_ETHERNET;
+import static android.net.TetheringManager.TETHERING_NCM;
+import static android.net.TetheringManager.TETHERING_USB;
+import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.net.TetheringManager.TETHERING_WIFI_P2P;
+import static android.net.TetheringManager.TETHER_ERROR_DHCPSERVER_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_DISABLE_FORWARDING_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_ENABLE_FORWARDING_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN;
+import static android.net.TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_INTERNAL_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION;
+import static android.net.TetheringManager.TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION;
+import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_PROVISIONING_FAILED;
+import static android.net.TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL;
+import static android.net.TetheringManager.TETHER_ERROR_TETHER_IFACE_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_UNAVAIL_IFACE;
+import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_IFACE;
+import static android.net.TetheringManager.TETHER_ERROR_UNSUPPORTED;
+import static android.net.TetheringManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
+
+import android.stats.connectivity.DownstreamType;
+import android.stats.connectivity.ErrorCode;
+import android.stats.connectivity.UpstreamType;
+import android.stats.connectivity.UserType;
+import android.util.Log;
+import android.util.SparseArray;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+/**
+ * Collection of utilities for tethering metrics.
+ *
+ * To see if the logs are properly sent to statsd, execute following commands
+ *
+ * $ adb shell cmd stats print-logs
+ * $ adb logcat | grep statsd OR $ adb logcat -b stats
+ *
+ * @hide
+ */
+public class TetheringMetrics {
+ private static final String TAG = TetheringMetrics.class.getSimpleName();
+ private static final boolean DBG = false;
+ private static final String SETTINGS_PKG_NAME = "com.android.settings";
+ private static final String SYSTEMUI_PKG_NAME = "com.android.systemui";
+ private static final String GMS_PKG_NAME = "com.google.android.gms";
+ private final SparseArray<NetworkTetheringReported.Builder> mBuilderMap = new SparseArray<>();
+
+ /** Update Tethering stats about caller's package name and downstream type. */
+ public void createBuilder(final int downstreamType, final String callerPkg) {
+ mBuilderMap.clear();
+ NetworkTetheringReported.Builder statsBuilder =
+ NetworkTetheringReported.newBuilder();
+ statsBuilder.setDownstreamType(downstreamTypeToEnum(downstreamType))
+ .setUserType(userTypeToEnum(callerPkg))
+ .setUpstreamType(UpstreamType.UT_UNKNOWN)
+ .setErrorCode(ErrorCode.EC_NO_ERROR)
+ .build();
+ mBuilderMap.put(downstreamType, statsBuilder);
+ }
+
+ /** Update error code of given downstreamType. */
+ public void updateErrorCode(final int downstreamType, final int errCode) {
+ NetworkTetheringReported.Builder statsBuilder = mBuilderMap.get(downstreamType);
+ if (statsBuilder == null) {
+ Log.e(TAG, "Given downstreamType does not exist, this is a bug!");
+ return;
+ }
+ statsBuilder.setErrorCode(errorCodeToEnum(errCode));
+ }
+
+ /** Remove Tethering stats.
+ * If Tethering stats is ready to write then write it before removing.
+ */
+ public void sendReport(final int downstreamType) {
+ final NetworkTetheringReported.Builder statsBuilder =
+ mBuilderMap.get(downstreamType);
+ if (statsBuilder == null) {
+ Log.e(TAG, "Given downstreamType does not exist, this is a bug!");
+ return;
+ }
+ write(statsBuilder.build());
+ mBuilderMap.remove(downstreamType);
+ }
+
+ /** Collect Tethering stats and write metrics data to statsd pipeline. */
+ @VisibleForTesting
+ public void write(@NonNull final NetworkTetheringReported reported) {
+ TetheringStatsLog.write(TetheringStatsLog.NETWORK_TETHERING_REPORTED,
+ reported.getErrorCode().getNumber(),
+ reported.getDownstreamType().getNumber(),
+ reported.getUpstreamType().getNumber(),
+ reported.getUserType().getNumber());
+ if (DBG) {
+ Log.d(TAG, "Write errorCode: " + reported.getErrorCode().getNumber()
+ + ", downstreamType: " + reported.getDownstreamType().getNumber()
+ + ", upstreamType: " + reported.getUpstreamType().getNumber()
+ + ", userType: " + reported.getUserType().getNumber());
+ }
+ }
+
+ /** Map {@link TetheringType} to {@link DownstreamType} */
+ private DownstreamType downstreamTypeToEnum(final int ifaceType) {
+ switch(ifaceType) {
+ case TETHERING_WIFI:
+ return DownstreamType.DS_TETHERING_WIFI;
+ case TETHERING_WIFI_P2P:
+ return DownstreamType.DS_TETHERING_WIFI_P2P;
+ case TETHERING_USB:
+ return DownstreamType.DS_TETHERING_USB;
+ case TETHERING_BLUETOOTH:
+ return DownstreamType.DS_TETHERING_BLUETOOTH;
+ case TETHERING_NCM:
+ return DownstreamType.DS_TETHERING_NCM;
+ case TETHERING_ETHERNET:
+ return DownstreamType.DS_TETHERING_ETHERNET;
+ default:
+ return DownstreamType.DS_UNSPECIFIED;
+ }
+ }
+
+ /** Map {@link StartTetheringError} to {@link ErrorCode} */
+ private ErrorCode errorCodeToEnum(final int lastError) {
+ switch(lastError) {
+ case TETHER_ERROR_NO_ERROR:
+ return ErrorCode.EC_NO_ERROR;
+ case TETHER_ERROR_UNKNOWN_IFACE:
+ return ErrorCode.EC_UNKNOWN_IFACE;
+ case TETHER_ERROR_SERVICE_UNAVAIL:
+ return ErrorCode.EC_SERVICE_UNAVAIL;
+ case TETHER_ERROR_UNSUPPORTED:
+ return ErrorCode.EC_UNSUPPORTED;
+ case TETHER_ERROR_UNAVAIL_IFACE:
+ return ErrorCode.EC_UNAVAIL_IFACE;
+ case TETHER_ERROR_INTERNAL_ERROR:
+ return ErrorCode.EC_INTERNAL_ERROR;
+ case TETHER_ERROR_TETHER_IFACE_ERROR:
+ return ErrorCode.EC_TETHER_IFACE_ERROR;
+ case TETHER_ERROR_UNTETHER_IFACE_ERROR:
+ return ErrorCode.EC_UNTETHER_IFACE_ERROR;
+ case TETHER_ERROR_ENABLE_FORWARDING_ERROR:
+ return ErrorCode.EC_ENABLE_FORWARDING_ERROR;
+ case TETHER_ERROR_DISABLE_FORWARDING_ERROR:
+ return ErrorCode.EC_DISABLE_FORWARDING_ERROR;
+ case TETHER_ERROR_IFACE_CFG_ERROR:
+ return ErrorCode.EC_IFACE_CFG_ERROR;
+ case TETHER_ERROR_PROVISIONING_FAILED:
+ return ErrorCode.EC_PROVISIONING_FAILED;
+ case TETHER_ERROR_DHCPSERVER_ERROR:
+ return ErrorCode.EC_DHCPSERVER_ERROR;
+ case TETHER_ERROR_ENTITLEMENT_UNKNOWN:
+ return ErrorCode.EC_ENTITLEMENT_UNKNOWN;
+ case TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION:
+ return ErrorCode.EC_NO_CHANGE_TETHERING_PERMISSION;
+ case TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION:
+ return ErrorCode.EC_NO_ACCESS_TETHERING_PERMISSION;
+ default:
+ return ErrorCode.EC_UNKNOWN_TYPE;
+ }
+ }
+
+ /** Map callerPkg to {@link UserType} */
+ private UserType userTypeToEnum(final String callerPkg) {
+ if (callerPkg.equals(SETTINGS_PKG_NAME)) {
+ return UserType.USER_SETTINGS;
+ } else if (callerPkg.equals(SYSTEMUI_PKG_NAME)) {
+ return UserType.USER_SYSTEMUI;
+ } else if (callerPkg.equals(GMS_PKG_NAME)) {
+ return UserType.USER_GMS;
+ } else {
+ return UserType.USER_UNKNOWN;
+ }
+ }
+}
diff --git a/Tethering/src/com/android/networkstack/tethering/metrics/stats.proto b/Tethering/src/com/android/networkstack/tethering/metrics/stats.proto
new file mode 100644
index 0000000..46a47af
--- /dev/null
+++ b/Tethering/src/com/android/networkstack/tethering/metrics/stats.proto
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+option java_multiple_files = true;
+
+package com.android.networkstack.tethering.metrics;
+
+import "frameworks/proto_logging/stats/enums/stats/connectivity/tethering.proto";
+
+/**
+ * Logs Tethering events
+ */
+message NetworkTetheringReported {
+ optional .android.stats.connectivity.ErrorCode error_code = 1;
+ optional .android.stats.connectivity.DownstreamType downstream_type = 2;
+ optional .android.stats.connectivity.UpstreamType upstream_type = 3;
+ optional .android.stats.connectivity.UserType user_type = 4;
+}
diff --git a/Tethering/src/com/android/networkstack/tethering/util/TetheringUtils.java b/Tethering/src/com/android/networkstack/tethering/util/TetheringUtils.java
index 66d67a1..e6236df 100644
--- a/Tethering/src/com/android/networkstack/tethering/util/TetheringUtils.java
+++ b/Tethering/src/com/android/networkstack/tethering/util/TetheringUtils.java
@@ -22,7 +22,7 @@
import androidx.annotation.NonNull;
import com.android.net.module.util.JniUtil;
-import com.android.networkstack.tethering.TetherStatsValue;
+import com.android.net.module.util.bpf.TetherStatsValue;
import java.io.FileDescriptor;
import java.net.Inet6Address;
diff --git a/Tethering/tests/integration/Android.bp b/Tethering/tests/integration/Android.bp
index 6eaf68b..31c3df3 100644
--- a/Tethering/tests/integration/Android.bp
+++ b/Tethering/tests/integration/Android.bp
@@ -49,7 +49,7 @@
// Use with NetworkStackJarJarRules.
android_library {
name: "TetheringIntegrationTestsLatestSdkLib",
- target_sdk_version: "31",
+ target_sdk_version: "33",
platform_apis: true,
defaults: ["TetheringIntegrationTestsDefaults"],
visibility: [
@@ -83,68 +83,3 @@
compile_multilib: "both",
jarjar_rules: ":NetworkStackJarJarRules",
}
-
-android_library {
- name: "TetheringCoverageTestsLib",
- min_sdk_version: "30",
- static_libs: [
- "NetdStaticLibTestsLib",
- "NetworkStaticLibTestsLib",
- "NetworkStackTestsLib",
- "TetheringTestsLatestSdkLib",
- "TetheringIntegrationTestsLatestSdkLib",
- ],
- // Jarjar rules should normally be applied on final artifacts and not intermediate libraries as
- // applying different rules on intermediate libraries can cause conflicts when combining them
- // (the resulting artifact can end up with multiple incompatible implementations of the same
- // classes). But this library is used to combine tethering coverage tests with connectivity
- // coverage tests into a single coverage target. The tests need to use the same jarjar rules as
- // covered production code for coverage to be calculated properly, so jarjar is applied
- // separately on each set of tests.
- jarjar_rules: ":TetheringCoverageJarJarRules",
- manifest: "AndroidManifest_coverage.xml",
- visibility: [
- "//packages/modules/Connectivity/tests:__subpackages__"
- ],
-}
-
-// Combine NetworkStack and Tethering jarjar rules for coverage target. The jarjar files are
-// simply concatenated in the order specified in srcs.
-genrule {
- name: "TetheringCoverageJarJarRules",
- srcs: [
- ":TetheringTestsJarJarRules",
- ":NetworkStackJarJarRules",
- ],
- out: ["jarjar-rules-tethering-coverage.txt"],
- cmd: "cat $(in) > $(out)",
- visibility: ["//visibility:private"],
-}
-
-// Special version of the tethering tests that includes all tests necessary for code coverage
-// purposes. This is currently the union of TetheringTests, TetheringIntegrationTests and
-// NetworkStackTests.
-// TODO: remove in favor of ConnectivityCoverageTests, which includes below tests and more
-android_test {
- name: "TetheringCoverageTests",
- platform_apis: true,
- min_sdk_version: "30",
- target_sdk_version: "31",
- test_suites: ["device-tests", "mts-tethering"],
- test_config: "AndroidTest_Coverage.xml",
- defaults: ["libnetworkstackutilsjni_deps"],
- static_libs: [
- "modules-utils-native-coverage-listener",
- "TetheringCoverageTestsLib",
- ],
- jni_libs: [
- // For mockito extended
- "libdexmakerjvmtiagent",
- "libstaticjvmtiagent",
- // For NetworkStackUtils included in NetworkStackBase
- "libnetworkstackutilsjni",
- "libcom_android_networkstack_tethering_util_jni",
- ],
- compile_multilib: "both",
- manifest: "AndroidManifest_coverage.xml",
-}
diff --git a/Tethering/tests/integration/AndroidManifest_coverage.xml b/Tethering/tests/integration/AndroidManifest_coverage.xml
deleted file mode 100644
index 06de00d..0000000
--- a/Tethering/tests/integration/AndroidManifest_coverage.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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.
--->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- package="com.android.networkstack.tethering.tests.coverage">
-
- <application tools:replace="android:label"
- android:debuggable="true"
- android:label="Tethering coverage tests">
- <uses-library android:name="android.test.runner" />
- </application>
- <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.networkstack.tethering.tests.coverage"
- android:label="Tethering coverage tests">
- </instrumentation>
-</manifest>
diff --git a/Tethering/tests/integration/AndroidTest_Coverage.xml b/Tethering/tests/integration/AndroidTest_Coverage.xml
deleted file mode 100644
index 33c5b3d..0000000
--- a/Tethering/tests/integration/AndroidTest_Coverage.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<configuration description="Runs coverage tests for Tethering">
- <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
- <option name="test-file-name" value="TetheringCoverageTests.apk" />
- </target_preparer>
-
- <option name="test-tag" value="TetheringCoverageTests" />
- <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
- <option name="package" value="com.android.networkstack.tethering.tests.coverage" />
- <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
- <option name="hidden-api-checks" value="false"/>
- <option name="device-listeners" value="com.android.modules.utils.testing.NativeCoverageHackInstrumentationListener" />
- </test>
-</configuration>
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index de81a38..92be84d 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -27,18 +27,24 @@
import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
import static android.net.TetheringManager.TETHERING_ETHERNET;
import static android.net.TetheringTester.RemoteResponder;
+import static android.net.TetheringTester.isIcmpv6Type;
import static android.system.OsConstants.IPPROTO_ICMPV6;
import static android.system.OsConstants.IPPROTO_IP;
+import static android.system.OsConstants.IPPROTO_IPV6;
import static android.system.OsConstants.IPPROTO_UDP;
import static com.android.net.module.util.ConnectivityUtils.isIPv6ULA;
import static com.android.net.module.util.HexDump.dumpHexString;
import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV4;
import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REPLY_TYPE;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
+import static com.android.testutils.DeviceInfoUtils.KVersion;
import static com.android.testutils.TestNetworkTrackerKt.initTestNetwork;
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;
@@ -59,6 +65,7 @@
import android.os.HandlerThread;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.os.VintfRuntimeInfo;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
@@ -70,10 +77,13 @@
import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.net.module.util.Ipv6Utils;
import com.android.net.module.util.PacketBuilder;
import com.android.net.module.util.Struct;
import com.android.net.module.util.bpf.Tether4Key;
import com.android.net.module.util.bpf.Tether4Value;
+import com.android.net.module.util.bpf.TetherStatsKey;
+import com.android.net.module.util.bpf.TetherStatsValue;
import com.android.net.module.util.structs.EthernetHeader;
import com.android.net.module.util.structs.Icmpv6Header;
import com.android.net.module.util.structs.Ipv4Header;
@@ -82,6 +92,7 @@
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.DeviceInfoUtils;
import com.android.testutils.DumpTestUtils;
import com.android.testutils.HandlerUtils;
import com.android.testutils.TapPacketReader;
@@ -95,6 +106,7 @@
import java.io.FileDescriptor;
import java.net.Inet4Address;
+import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
@@ -128,17 +140,33 @@
// Kernel treats a confirmed UDP connection which active after two seconds as stream mode.
// See upstream commit b7b1d02fc43925a4d569ec221715db2dfa1ce4f5.
private static final int UDP_STREAM_TS_MS = 2000;
+ // Per RX UDP packet size: iphdr (20) + udphdr (8) + payload (2) = 30 bytes.
+ private static final int RX_UDP_PACKET_SIZE = 30;
+ private static final int RX_UDP_PACKET_COUNT = 456;
+ // Per TX UDP packet size: ethhdr (14) + iphdr (20) + udphdr (8) + payload (2) = 44 bytes.
+ private static final int TX_UDP_PACKET_SIZE = 44;
+ private static final int TX_UDP_PACKET_COUNT = 123;
+
private static final LinkAddress TEST_IP4_ADDR = new LinkAddress("10.0.0.1/8");
private static final LinkAddress TEST_IP6_ADDR = new LinkAddress("2001:db8:1::101/64");
private static final InetAddress TEST_IP4_DNS = parseNumericAddress("8.8.8.8");
private static final InetAddress TEST_IP6_DNS = parseNumericAddress("2001:db8:1::888");
+ private static final IpPrefix TEST_NAT64PREFIX = new IpPrefix("64:ff9b::/96");
+ private static final Inet6Address REMOTE_NAT64_ADDR =
+ (Inet6Address) parseNumericAddress("64:ff9b::808:808");
private static final ByteBuffer TEST_REACHABILITY_PAYLOAD =
ByteBuffer.wrap(new byte[] { (byte) 0x55, (byte) 0xaa });
private static final String DUMPSYS_TETHERING_RAWMAP_ARG = "bpfRawMap";
+ private static final String DUMPSYS_RAWMAP_ARG_STATS = "--stats";
+ private static final String DUMPSYS_RAWMAP_ARG_UPSTREAM4 = "--upstream4";
private static final String BASE64_DELIMITER = ",";
private static final String LINE_DELIMITER = "\\n";
+ // version=6, traffic class=0x0, flowlabel=0x0;
+ private static final int VERSION_TRAFFICCLASS_FLOWLABEL = 0x60000000;
+ private static final short HOP_LIMIT = 0x40;
+
private final Context mContext = InstrumentationRegistry.getContext();
private final EthernetManager mEm = mContext.getSystemService(EthernetManager.class);
private final TetheringManager mTm = mContext.getSystemService(TetheringManager.class);
@@ -168,12 +196,13 @@
mUiAutomation.adoptShellPermissionIdentity(
MANAGE_TEST_NETWORKS, NETWORK_SETTINGS, TETHER_PRIVILEGED, ACCESS_NETWORK_STATE,
CONNECTIVITY_USE_RESTRICTED_NETWORKS, DUMP);
- mRunTests = mTm.isTetheringSupported() && mEm != null;
- assumeTrue(mRunTests);
-
mHandlerThread = new HandlerThread(getClass().getSimpleName());
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
+
+ mRunTests = isEthernetTetheringSupported();
+ assumeTrue(mRunTests);
+
mTetheredInterfaceRequester = new TetheredInterfaceRequester(mHandler, mEm);
}
@@ -201,7 +230,6 @@
mHandler.post(() -> reader.stop());
mDownstreamReader = null;
}
- mHandlerThread.quitSafely();
mTetheredInterfaceRequester.release();
mEm.setIncludeTestInterfaces(false);
maybeDeleteTestInterface();
@@ -212,6 +240,7 @@
try {
if (mRunTests) cleanUp();
} finally {
+ mHandlerThread.quitSafely();
mUiAutomation.dropShellPermissionIdentity();
}
}
@@ -400,6 +429,23 @@
// client, which is not possible in this test.
}
+ private boolean isEthernetTetheringSupported() throws Exception {
+ final CompletableFuture<Boolean> future = new CompletableFuture<>();
+ final TetheringEventCallback callback = new TetheringEventCallback() {
+ @Override
+ public void onSupportedTetheringTypes(Set<Integer> supportedTypes) {
+ future.complete(supportedTypes.contains(TETHERING_ETHERNET));
+ }
+ };
+
+ try {
+ mTm.registerTetheringEventCallback(mHandler::post, callback);
+ return future.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ } finally {
+ mTm.unregisterTetheringEventCallback(callback);
+ }
+ }
+
private static final class MyTetheringEventCallback implements TetheringEventCallback {
private final TetheringManager mTm;
private final CountDownLatch mTetheringStartedLatch = new CountDownLatch(1);
@@ -748,21 +794,27 @@
}
}
- private TestNetworkTracker createTestUpstream(final List<LinkAddress> addresses)
- throws Exception {
+ private TestNetworkTracker createTestUpstream(final List<LinkAddress> addresses,
+ final List<InetAddress> dnses) throws Exception {
mTm.setPreferTestNetworks(true);
- return initTestNetwork(mContext, addresses, TIMEOUT_MS);
+ final LinkProperties lp = new LinkProperties();
+ lp.setLinkAddresses(addresses);
+ lp.setDnsServers(dnses);
+ lp.setNat64Prefix(TEST_NAT64PREFIX);
+
+ return initTestNetwork(mContext, lp, TIMEOUT_MS);
}
@Test
- public void testTestNetworkUpstream() throws Exception {
+ public void testIcmpv6Echo() throws Exception {
assumeFalse(mEm.isAvailable());
// MyTetheringEventCallback currently only support await first available upstream. Tethering
// may select internet network as upstream if test network is not available and not be
// preferred yet. Create test upstream network before enable tethering.
- mUpstreamTracker = createTestUpstream(toList(TEST_IP4_ADDR, TEST_IP6_ADDR));
+ mUpstreamTracker = createTestUpstream(toList(TEST_IP4_ADDR, TEST_IP6_ADDR),
+ toList(TEST_IP4_DNS, TEST_IP6_DNS));
mDownstreamIface = createTestInterface();
mEm.setIncludeTestInterfaces(true);
@@ -777,7 +829,40 @@
mTetheringEventCallback.awaitUpstreamChanged());
mDownstreamReader = makePacketReader(mDownstreamIface);
- // TODO: do basic forwarding test here.
+ mUpstreamReader = makePacketReader(mUpstreamTracker.getTestIface());
+
+ runPing6Test(new TetheringTester(mDownstreamReader), new RemoteResponder(mUpstreamReader));
+ }
+
+ private void runPing6Test(TetheringTester tester, RemoteResponder remote) throws Exception {
+ // Currently tethering don't have API to tell when ipv6 tethering is available. Thus, let
+ // TetheringTester test ipv6 tethering connectivity before testing ipv6.
+ // TODO: move to a common place to avoid that every IPv6 test needs to call this function.
+ tester.waitForIpv6TetherConnectivityVerified();
+
+ TetheredDevice tethered = tester.createTetheredDevice(MacAddress.fromString("1:2:3:4:5:6"),
+ true /* hasIpv6 */);
+ Inet6Address remoteIp6Addr = (Inet6Address) parseNumericAddress("2400:222:222::222");
+ ByteBuffer request = Ipv6Utils.buildEchoRequestPacket(tethered.macAddr,
+ tethered.routerMacAddr, tethered.ipv6Addr, remoteIp6Addr);
+ tester.sendPacket(request);
+
+ final byte[] echoRequest = remote.getNextMatchedPacket((p) -> {
+ Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
+
+ return isIcmpv6Type(p, false /* hasEth */, ICMPV6_ECHO_REQUEST_TYPE);
+ });
+ assertNotNull("No icmpv6 echo request in upstream", echoRequest);
+
+ ByteBuffer reply = Ipv6Utils.buildEchoReplyPacket(remoteIp6Addr, tethered.ipv6Addr);
+ remote.sendPacket(reply);
+
+ final byte[] echoReply = tester.getNextMatchedPacket((p) -> {
+ Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
+
+ return isIcmpv6Type(p, true /* hasEth */, ICMPV6_ECHO_REPLY_TYPE);
+ });
+ assertNotNull("No icmpv6 echo reply in downstream", echoReply);
}
// Test network topology:
@@ -795,12 +880,11 @@
// Used by public port and private port. Assume port 9876 has not been used yet before the
// testing that public port and private port are the same in the testing. Note that NAT port
// forwarding could be different between private port and public port.
+ // TODO: move to the start of test class.
private static final short LOCAL_PORT = 9876;
private static final short REMOTE_PORT = 433;
private static final byte TYPE_OF_SERVICE = 0;
private static final short ID = 27149;
- private static final short ID2 = 27150;
- private static final short ID3 = 27151;
private static final short FLAGS_AND_FRAGMENT_OFFSET = (short) 0x4000; // flags=DF, offset=0
private static final byte TIME_TO_LIVE = (byte) 0x40;
private static final ByteBuffer PAYLOAD =
@@ -811,19 +895,20 @@
ByteBuffer.wrap(new byte[] { (byte) 0x9a, (byte) 0xbc });
private boolean isExpectedUdpPacket(@NonNull final byte[] rawPacket, boolean hasEther,
- @NonNull final ByteBuffer payload) {
+ boolean isIpv4, @NonNull final ByteBuffer payload) {
final ByteBuffer buf = ByteBuffer.wrap(rawPacket);
if (hasEther) {
- final EthernetHeader etherHeader = Struct.parse(EthernetHeader.class, buf);
- if (etherHeader == null) return false;
+ if (Struct.parse(EthernetHeader.class, buf) == null) return false;
}
- final Ipv4Header ipv4Header = Struct.parse(Ipv4Header.class, buf);
- if (ipv4Header == null) return false;
+ if (isIpv4) {
+ if (Struct.parse(Ipv4Header.class, buf) == null) return false;
+ } else {
+ if (Struct.parse(Ipv6Header.class, buf) == null) return false;
+ }
- final UdpHeader udpHeader = Struct.parse(UdpHeader.class, buf);
- if (udpHeader == null) return false;
+ if (Struct.parse(UdpHeader.class, buf) == null) return false;
if (buf.remaining() != payload.limit()) return false;
@@ -832,21 +917,47 @@
}
@NonNull
- private ByteBuffer buildUdpv4Packet(@Nullable final MacAddress srcMac,
- @Nullable final MacAddress dstMac, short id,
- @NonNull final Inet4Address srcIp, @NonNull final Inet4Address dstIp,
+ private ByteBuffer buildUdpPacket(
+ @Nullable final MacAddress srcMac, @Nullable final MacAddress dstMac,
+ @NonNull final InetAddress srcIp, @NonNull final InetAddress dstIp,
short srcPort, short dstPort, @Nullable final ByteBuffer payload)
throws Exception {
+ int ipProto;
+ short ethType;
+ if (srcIp instanceof Inet4Address && dstIp instanceof Inet4Address) {
+ ipProto = IPPROTO_IP;
+ ethType = (short) ETHER_TYPE_IPV4;
+ } else if (srcIp instanceof Inet6Address && dstIp instanceof Inet6Address) {
+ ipProto = IPPROTO_IPV6;
+ ethType = (short) ETHER_TYPE_IPV6;
+ } else {
+ fail("Unsupported conditions: srcIp " + srcIp + ", dstIp " + dstIp);
+ // Make compiler happy to the uninitialized ipProto and ethType.
+ return null; // unreachable, the annotation @NonNull of function return value is true.
+ }
+
final boolean hasEther = (srcMac != null && dstMac != null);
final int payloadLen = (payload == null) ? 0 : payload.limit();
- final ByteBuffer buffer = PacketBuilder.allocate(hasEther, IPPROTO_IP, IPPROTO_UDP,
+ final ByteBuffer buffer = PacketBuilder.allocate(hasEther, ipProto, IPPROTO_UDP,
payloadLen);
final PacketBuilder packetBuilder = new PacketBuilder(buffer);
+ // [1] Ethernet header
if (hasEther) packetBuilder.writeL2Header(srcMac, dstMac, (short) ETHER_TYPE_IPV4);
- packetBuilder.writeIpv4Header(TYPE_OF_SERVICE, ID, FLAGS_AND_FRAGMENT_OFFSET,
- TIME_TO_LIVE, (byte) IPPROTO_UDP, srcIp, dstIp);
+
+ // [2] IP header
+ if (ipProto == IPPROTO_IP) {
+ packetBuilder.writeIpv4Header(TYPE_OF_SERVICE, ID, FLAGS_AND_FRAGMENT_OFFSET,
+ TIME_TO_LIVE, (byte) IPPROTO_UDP, (Inet4Address) srcIp, (Inet4Address) dstIp);
+ } else {
+ packetBuilder.writeIpv6Header(VERSION_TRAFFICCLASS_FLOWLABEL, (byte) IPPROTO_UDP,
+ HOP_LIMIT, (Inet6Address) srcIp, (Inet6Address) dstIp);
+ }
+
+ // [3] UDP header
packetBuilder.writeUdpHeader(srcPort, dstPort);
+
+ // [4] Payload
if (payload != null) {
buffer.put(payload);
// in case data might be reused by caller, restore the position and
@@ -858,10 +969,10 @@
}
@NonNull
- private ByteBuffer buildUdpv4Packet(short id, @NonNull final Inet4Address srcIp,
- @NonNull final Inet4Address dstIp, short srcPort, short dstPort,
+ private ByteBuffer buildUdpPacket(@NonNull final InetAddress srcIp,
+ @NonNull final InetAddress dstIp, short srcPort, short dstPort,
@Nullable final ByteBuffer payload) throws Exception {
- return buildUdpv4Packet(null /* srcMac */, null /* dstMac */, id, srcIp, dstIp, srcPort,
+ return buildUdpPacket(null /* srcMac */, null /* dstMac */, srcIp, dstIp, srcPort,
dstPort, payload);
}
@@ -869,9 +980,9 @@
// See #runUdp4Test.
private boolean isIpv4TetherConnectivityVerified(TetheringTester tester,
RemoteResponder remote, TetheredDevice tethered) throws Exception {
- final ByteBuffer probePacket = buildUdpv4Packet(tethered.macAddr,
- tethered.routerMacAddr, ID, tethered.ipv4Addr /* srcIp */,
- REMOTE_IP4_ADDR /* dstIp */, LOCAL_PORT /* srcPort */, REMOTE_PORT /*dstPort */,
+ final ByteBuffer probePacket = buildUdpPacket(tethered.macAddr,
+ tethered.routerMacAddr, tethered.ipv4Addr /* srcIp */,
+ REMOTE_IP4_ADDR /* dstIp */, LOCAL_PORT /* srcPort */, REMOTE_PORT /* dstPort */,
TEST_REACHABILITY_PAYLOAD);
// Send a UDP packet from client and check the packet can be found on upstream interface.
@@ -879,7 +990,8 @@
tester.sendPacket(probePacket);
byte[] expectedPacket = remote.getNextMatchedPacket(p -> {
Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
- return isExpectedUdpPacket(p, false /* hasEther */, TEST_REACHABILITY_PAYLOAD);
+ return isExpectedUdpPacket(p, false /* hasEther */, true /* isIpv4 */,
+ TEST_REACHABILITY_PAYLOAD);
});
if (expectedPacket != null) return true;
}
@@ -889,7 +1001,7 @@
private void runUdp4Test(TetheringTester tester, RemoteResponder remote, boolean usingBpf)
throws Exception {
final TetheredDevice tethered = tester.createTetheredDevice(MacAddress.fromString(
- "1:2:3:4:5:6"));
+ "1:2:3:4:5:6"), false /* hasIpv6 */);
// TODO: remove the connectivity verification for upstream connected notification race.
// Because async upstream connected notification can't guarantee the tethering routing is
@@ -900,23 +1012,23 @@
assertTrue(isIpv4TetherConnectivityVerified(tester, remote, tethered));
// Send a UDP packet in original direction.
- final ByteBuffer originalPacket = buildUdpv4Packet(tethered.macAddr,
- tethered.routerMacAddr, ID, tethered.ipv4Addr /* srcIp */,
- REMOTE_IP4_ADDR /* dstIp */, LOCAL_PORT /* srcPort */, REMOTE_PORT /*dstPort */,
+ final ByteBuffer originalPacket = buildUdpPacket(tethered.macAddr,
+ tethered.routerMacAddr, tethered.ipv4Addr /* srcIp */,
+ REMOTE_IP4_ADDR /* dstIp */, LOCAL_PORT /* srcPort */, REMOTE_PORT /* dstPort */,
PAYLOAD /* payload */);
tester.verifyUpload(remote, originalPacket, p -> {
Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
- return isExpectedUdpPacket(p, false /* hasEther */, PAYLOAD);
+ return isExpectedUdpPacket(p, false /* hasEther */, true /* isIpv4 */, PAYLOAD);
});
// Send a UDP packet in reply direction.
final Inet4Address publicIp4Addr = (Inet4Address) TEST_IP4_ADDR.getAddress();
- final ByteBuffer replyPacket = buildUdpv4Packet(ID2, REMOTE_IP4_ADDR /* srcIp */,
- publicIp4Addr /* dstIp */, REMOTE_PORT /* srcPort */, LOCAL_PORT /*dstPort */,
+ final ByteBuffer replyPacket = buildUdpPacket(REMOTE_IP4_ADDR /* srcIp */,
+ publicIp4Addr /* dstIp */, REMOTE_PORT /* srcPort */, LOCAL_PORT /* dstPort */,
PAYLOAD2 /* payload */);
remote.verifyDownload(tester, replyPacket, p -> {
Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
- return isExpectedUdpPacket(p, true/* hasEther */, PAYLOAD2);
+ return isExpectedUdpPacket(p, true /* hasEther */, true /* isIpv4 */, PAYLOAD2);
});
if (usingBpf) {
@@ -929,46 +1041,89 @@
// See kernel upstream commit b7b1d02fc43925a4d569ec221715db2dfa1ce4f5 and
// nf_conntrack_udp_packet in net/netfilter/nf_conntrack_proto_udp.c
Thread.sleep(UDP_STREAM_TS_MS);
- final ByteBuffer originalPacket2 = buildUdpv4Packet(tethered.macAddr,
- tethered.routerMacAddr, ID, tethered.ipv4Addr /* srcIp */,
+ final ByteBuffer originalPacket2 = buildUdpPacket(tethered.macAddr,
+ tethered.routerMacAddr, tethered.ipv4Addr /* srcIp */,
REMOTE_IP4_ADDR /* dstIp */, LOCAL_PORT /* srcPort */,
- REMOTE_PORT /*dstPort */, PAYLOAD3 /* payload */);
+ REMOTE_PORT /* dstPort */, PAYLOAD3 /* payload */);
tester.verifyUpload(remote, originalPacket2, p -> {
Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
- return isExpectedUdpPacket(p, false /* hasEther */, PAYLOAD3);
+ return isExpectedUdpPacket(p, false /* hasEther */, true /* isIpv4 */, PAYLOAD3);
});
- final HashMap<Tether4Key, Tether4Value> upstreamMap = pollIpv4UpstreamMapFromDump();
+ // [1] Verify IPv4 upstream rule map.
+ final HashMap<Tether4Key, Tether4Value> upstreamMap = pollRawMapFromDump(
+ Tether4Key.class, Tether4Value.class, DUMPSYS_RAWMAP_ARG_UPSTREAM4);
assertNotNull(upstreamMap);
assertEquals(1, upstreamMap.size());
final Map.Entry<Tether4Key, Tether4Value> rule =
upstreamMap.entrySet().iterator().next();
- final Tether4Key key = rule.getKey();
- assertEquals(IPPROTO_UDP, key.l4proto);
- assertTrue(Arrays.equals(tethered.ipv4Addr.getAddress(), key.src4));
- assertEquals(LOCAL_PORT, key.srcPort);
- assertTrue(Arrays.equals(REMOTE_IP4_ADDR.getAddress(), key.dst4));
- assertEquals(REMOTE_PORT, key.dstPort);
+ final Tether4Key upstream4Key = rule.getKey();
+ assertEquals(IPPROTO_UDP, upstream4Key.l4proto);
+ assertTrue(Arrays.equals(tethered.ipv4Addr.getAddress(), upstream4Key.src4));
+ assertEquals(LOCAL_PORT, upstream4Key.srcPort);
+ assertTrue(Arrays.equals(REMOTE_IP4_ADDR.getAddress(), upstream4Key.dst4));
+ assertEquals(REMOTE_PORT, upstream4Key.dstPort);
- final Tether4Value value = rule.getValue();
+ final Tether4Value upstream4Value = rule.getValue();
assertTrue(Arrays.equals(publicIp4Addr.getAddress(),
- InetAddress.getByAddress(value.src46).getAddress()));
- assertEquals(LOCAL_PORT, value.srcPort);
+ InetAddress.getByAddress(upstream4Value.src46).getAddress()));
+ assertEquals(LOCAL_PORT, upstream4Value.srcPort);
assertTrue(Arrays.equals(REMOTE_IP4_ADDR.getAddress(),
- InetAddress.getByAddress(value.dst46).getAddress()));
- assertEquals(REMOTE_PORT, value.dstPort);
+ InetAddress.getByAddress(upstream4Value.dst46).getAddress()));
+ assertEquals(REMOTE_PORT, upstream4Value.dstPort);
+
+ // [2] Verify stats map.
+ // Transmit packets on both direction for verifying stats. Because we only care the
+ // packet count in stats test, we just reuse the existing packets to increaes
+ // the packet count on both direction.
+
+ // Send packets on original direction.
+ for (int i = 0; i < TX_UDP_PACKET_COUNT; i++) {
+ tester.verifyUpload(remote, originalPacket, p -> {
+ Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
+ return isExpectedUdpPacket(p, false /* hasEther */, true /* isIpv4 */, PAYLOAD);
+ });
+ }
+
+ // Send packets on reply direction.
+ for (int i = 0; i < RX_UDP_PACKET_COUNT; i++) {
+ remote.verifyDownload(tester, replyPacket, p -> {
+ Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
+ return isExpectedUdpPacket(p, true /* hasEther */, true /* isIpv4 */, PAYLOAD2);
+ });
+ }
+
+ // Dump stats map to verify.
+ final HashMap<TetherStatsKey, TetherStatsValue> statsMap = pollRawMapFromDump(
+ TetherStatsKey.class, TetherStatsValue.class, DUMPSYS_RAWMAP_ARG_STATS);
+ assertNotNull(statsMap);
+ assertEquals(1, statsMap.size());
+
+ final Map.Entry<TetherStatsKey, TetherStatsValue> stats =
+ statsMap.entrySet().iterator().next();
+
+ // TODO: verify the upstream index in TetherStatsKey.
+
+ final TetherStatsValue statsValue = stats.getValue();
+ assertEquals(RX_UDP_PACKET_COUNT, statsValue.rxPackets);
+ assertEquals(RX_UDP_PACKET_COUNT * RX_UDP_PACKET_SIZE, statsValue.rxBytes);
+ assertEquals(0, statsValue.rxErrors);
+ assertEquals(TX_UDP_PACKET_COUNT, statsValue.txPackets);
+ assertEquals(TX_UDP_PACKET_COUNT * TX_UDP_PACKET_SIZE, statsValue.txBytes);
+ assertEquals(0, statsValue.txErrors);
}
}
- void initializeTethering() throws Exception {
+ void initializeTethering(List<LinkAddress> upstreamAddresses, List<InetAddress> upstreamDnses)
+ throws Exception {
assumeFalse(mEm.isAvailable());
// MyTetheringEventCallback currently only support await first available upstream. Tethering
// may select internet network as upstream if test network is not available and not be
// preferred yet. Create test upstream network before enable tethering.
- mUpstreamTracker = createTestUpstream(toList(TEST_IP4_ADDR));
+ mUpstreamTracker = createTestUpstream(upstreamAddresses, upstreamDnses);
mDownstreamIface = createTestInterface();
mEm.setIncludeTestInterfaces(true);
@@ -987,23 +1142,58 @@
}
@Test
- @IgnoreAfter(Build.VERSION_CODES.Q)
- public void testTetherUdpV4WithoutBpf() throws Exception {
- initializeTethering();
+ @IgnoreAfter(Build.VERSION_CODES.R)
+ public void testTetherUdpV4UpToR() throws Exception {
+ initializeTethering(toList(TEST_IP4_ADDR), toList(TEST_IP4_DNS));
runUdp4Test(new TetheringTester(mDownstreamReader), new RemoteResponder(mUpstreamReader),
false /* usingBpf */);
}
+ private static boolean isUdpOffloadSupportedByKernel(final String kernelVersion) {
+ final KVersion current = DeviceInfoUtils.getMajorMinorSubminorVersion(kernelVersion);
+ return current.isInRange(new KVersion(4, 14, 222), new KVersion(4, 19, 0))
+ || current.isInRange(new KVersion(4, 19, 176), new KVersion(5, 4, 0))
+ || current.isAtLeast(new KVersion(5, 4, 98));
+ }
+
+ @Test
+ public void testIsUdpOffloadSupportedByKernel() throws Exception {
+ assertFalse(isUdpOffloadSupportedByKernel("4.14.221"));
+ assertTrue(isUdpOffloadSupportedByKernel("4.14.222"));
+ assertTrue(isUdpOffloadSupportedByKernel("4.16.0"));
+ assertTrue(isUdpOffloadSupportedByKernel("4.18.0"));
+ assertFalse(isUdpOffloadSupportedByKernel("4.19.0"));
+
+ assertFalse(isUdpOffloadSupportedByKernel("4.19.175"));
+ assertTrue(isUdpOffloadSupportedByKernel("4.19.176"));
+ assertTrue(isUdpOffloadSupportedByKernel("5.2.0"));
+ assertTrue(isUdpOffloadSupportedByKernel("5.3.0"));
+ assertFalse(isUdpOffloadSupportedByKernel("5.4.0"));
+
+ assertFalse(isUdpOffloadSupportedByKernel("5.4.97"));
+ assertTrue(isUdpOffloadSupportedByKernel("5.4.98"));
+ assertTrue(isUdpOffloadSupportedByKernel("5.10.0"));
+ }
+
+ // TODO: refactor test testTetherUdpV4* into IPv4 UDP non-offload and offload tests.
+ // That can be easier to know which feature is verified from test results.
@Test
@IgnoreUpTo(Build.VERSION_CODES.R)
- public void testTetherUdpV4WithBpf() throws Exception {
- initializeTethering();
+ public void testTetherUdpV4AfterR() throws Exception {
+ initializeTethering(toList(TEST_IP4_ADDR), toList(TEST_IP4_DNS));
+ final String kernelVersion = VintfRuntimeInfo.getKernelVersion();
+ boolean usingBpf = isUdpOffloadSupportedByKernel(kernelVersion);
+ if (!usingBpf) {
+ Log.i(TAG, "testTetherUdpV4AfterR will skip BPF offload test for kernel "
+ + kernelVersion);
+ }
runUdp4Test(new TetheringTester(mDownstreamReader), new RemoteResponder(mUpstreamReader),
- true /* usingBpf */);
+ usingBpf);
}
@Nullable
- private Pair<Tether4Key, Tether4Value> parseTether4KeyValue(@NonNull String dumpStr) {
+ private <K extends Struct, V extends Struct> Pair<K, V> parseMapKeyValue(
+ Class<K> keyClass, Class<V> valueClass, @NonNull String dumpStr) {
Log.w(TAG, "Parsing string: " + dumpStr);
String[] keyValueStrs = dumpStr.split(BASE64_DELIMITER);
@@ -1016,36 +1206,38 @@
Log.d(TAG, "keyBytes: " + dumpHexString(keyBytes));
final ByteBuffer keyByteBuffer = ByteBuffer.wrap(keyBytes);
keyByteBuffer.order(ByteOrder.nativeOrder());
- final Tether4Key tether4Key = Struct.parse(Tether4Key.class, keyByteBuffer);
- Log.w(TAG, "tether4Key: " + tether4Key);
+ final K k = Struct.parse(keyClass, keyByteBuffer);
final byte[] valueBytes = Base64.decode(keyValueStrs[1], Base64.DEFAULT);
Log.d(TAG, "valueBytes: " + dumpHexString(valueBytes));
final ByteBuffer valueByteBuffer = ByteBuffer.wrap(valueBytes);
valueByteBuffer.order(ByteOrder.nativeOrder());
- final Tether4Value tether4Value = Struct.parse(Tether4Value.class, valueByteBuffer);
- Log.w(TAG, "tether4Value: " + tether4Value);
+ final V v = Struct.parse(valueClass, valueByteBuffer);
- return new Pair<>(tether4Key, tether4Value);
+ return new Pair<>(k, v);
}
@NonNull
- private HashMap<Tether4Key, Tether4Value> dumpIpv4UpstreamMap() throws Exception {
- final String rawMapStr = DumpTestUtils.dumpService(Context.TETHERING_SERVICE,
- DUMPSYS_TETHERING_RAWMAP_ARG);
- final HashMap<Tether4Key, Tether4Value> map = new HashMap<>();
+ private <K extends Struct, V extends Struct> HashMap<K, V> dumpAndParseRawMap(
+ Class<K> keyClass, Class<V> valueClass, @NonNull String mapArg)
+ throws Exception {
+ final String[] args = new String[] {DUMPSYS_TETHERING_RAWMAP_ARG, mapArg};
+ final String rawMapStr = DumpTestUtils.dumpService(Context.TETHERING_SERVICE, args);
+ final HashMap<K, V> map = new HashMap<>();
for (final String line : rawMapStr.split(LINE_DELIMITER)) {
- final Pair<Tether4Key, Tether4Value> rule = parseTether4KeyValue(line.trim());
+ final Pair<K, V> rule = parseMapKeyValue(keyClass, valueClass, line.trim());
map.put(rule.first, rule.second);
}
return map;
}
@Nullable
- private HashMap<Tether4Key, Tether4Value> pollIpv4UpstreamMapFromDump() throws Exception {
+ private <K extends Struct, V extends Struct> HashMap<K, V> pollRawMapFromDump(
+ Class<K> keyClass, Class<V> valueClass, @NonNull String mapArg)
+ throws Exception {
for (int retryCount = 0; retryCount < DUMP_POLLING_MAX_RETRY; retryCount++) {
- final HashMap<Tether4Key, Tether4Value> map = dumpIpv4UpstreamMap();
+ final HashMap<K, V> map = dumpAndParseRawMap(keyClass, valueClass, mapArg);
if (!map.isEmpty()) return map;
Thread.sleep(DUMP_POLLING_INTERVAL_MS);
@@ -1055,6 +1247,96 @@
return null;
}
+ @Nullable
+ private Inet6Address getClatIpv6Address(TetheringTester tester,
+ RemoteResponder remote, TetheredDevice tethered) throws Exception {
+ final ByteBuffer probePacket = buildUdpPacket(tethered.macAddr,
+ tethered.routerMacAddr, tethered.ipv4Addr /* srcIp */,
+ REMOTE_IP4_ADDR /* dstIp */, LOCAL_PORT /* srcPort */, REMOTE_PORT /* dstPort */,
+ TEST_REACHABILITY_PAYLOAD);
+
+ // Send an IPv4 UDP packet from client and check that a CLAT translated IPv6 UDP packet can
+ // be found on upstream interface. Get CLAT IPv6 address from the CLAT translated IPv6 UDP
+ // packet.
+ byte[] expectedPacket = null;
+ for (int i = 0; i < TETHER_REACHABILITY_ATTEMPTS; i++) {
+ tester.sendPacket(probePacket);
+ expectedPacket = remote.getNextMatchedPacket(p -> {
+ Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
+ return isExpectedUdpPacket(p, false /* hasEther */, false /* isIpv4 */,
+ TEST_REACHABILITY_PAYLOAD);
+ });
+ if (expectedPacket != null) break;
+ }
+ if (expectedPacket == null) return null;
+
+ // Above has guaranteed that the found packet is an IPv6 packet without ether header.
+ final Ipv6Header ipv6Header = Struct.parse(Ipv6Header.class,
+ ByteBuffer.wrap(expectedPacket));
+ return ipv6Header.srcIp;
+ }
+
+ // Test network topology:
+ //
+ // public network (rawip) private network
+ // | UE (CLAT support) |
+ // +---------------+ V +------------+------------+ V +------------+
+ // | NAT64 Gateway +---------+ Upstream | Downstream +---------+ Client |
+ // +---------------+ +------------+------------+ +------------+
+ // remote ip public ip private ip
+ // [64:ff9b::808:808]:443 [clat ipv6]:9876 [TetheredDevice ipv4]:9876
+ //
+ // Note that CLAT IPv6 address is generated by ClatCoordinator. Get the CLAT IPv6 address by
+ // sending out an IPv4 packet and extracting the source address from CLAT translated IPv6
+ // packet.
+ //
+ private void runClatUdpTest(TetheringTester tester, RemoteResponder remote)
+ throws Exception {
+ // Currently tethering don't have API to tell when ipv6 tethering is available. Thus, let
+ // TetheringTester test ipv6 tethering connectivity before testing ipv6.
+ // TODO: move to a common place to avoid that every IPv6 test needs to call this function.
+ tester.waitForIpv6TetherConnectivityVerified();
+
+ final TetheredDevice tethered = tester.createTetheredDevice(MacAddress.fromString(
+ "1:2:3:4:5:6"), true /* hasIpv6 */);
+
+ // Get CLAT IPv6 address.
+ final Inet6Address clatAddr6 = getClatIpv6Address(tester, remote, tethered);
+ assertNotNull(clatAddr6);
+
+ // Send an IPv4 UDP packet in original direction.
+ // IPv4 packet -- CLAT translation --> IPv6 packet
+ final ByteBuffer originalPacket = buildUdpPacket(tethered.macAddr,
+ tethered.routerMacAddr, tethered.ipv4Addr /* srcIp */,
+ REMOTE_IP4_ADDR /* dstIp */, LOCAL_PORT /* srcPort */, REMOTE_PORT /* dstPort */,
+ PAYLOAD /* payload */);
+ tester.verifyUpload(remote, originalPacket, p -> {
+ Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
+ return isExpectedUdpPacket(p, false /* hasEther */, false /* isIpv4 */, PAYLOAD);
+ });
+
+ // Send an IPv6 UDP packet in reply direction.
+ // IPv6 packet -- CLAT translation --> IPv4 packet
+ final ByteBuffer replyPacket = buildUdpPacket(REMOTE_NAT64_ADDR /* srcIp */,
+ clatAddr6 /* dstIp */, REMOTE_PORT /* srcPort */, LOCAL_PORT /* dstPort */,
+ PAYLOAD2 /* payload */);
+ remote.verifyDownload(tester, replyPacket, p -> {
+ Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
+ return isExpectedUdpPacket(p, true /* hasEther */, true /* isIpv4 */, PAYLOAD2);
+ });
+
+ // TODO: test CLAT bpf maps.
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.R)
+ public void testTetherClatUdp() throws Exception {
+ // CLAT only starts on IPv6 only network.
+ initializeTethering(toList(TEST_IP6_ADDR), toList(TEST_IP6_DNS));
+ runClatUdpTest(new TetheringTester(mDownstreamReader),
+ new RemoteResponder(mUpstreamReader));
+ }
+
private <T> List<T> toList(T... array) {
return Arrays.asList(array);
}
diff --git a/Tethering/tests/integration/src/android/net/TetheringTester.java b/Tethering/tests/integration/src/android/net/TetheringTester.java
index d24661a..458680a 100644
--- a/Tethering/tests/integration/src/android/net/TetheringTester.java
+++ b/Tethering/tests/integration/src/android/net/TetheringTester.java
@@ -16,10 +16,22 @@
package android.net;
+import static android.net.InetAddresses.parseNumericAddress;
+import static android.system.OsConstants.IPPROTO_ICMPV6;
+
import static com.android.net.module.util.NetworkStackConstants.ARP_REPLY;
import static com.android.net.module.util.NetworkStackConstants.ARP_REQUEST;
import static com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN;
import static com.android.net.module.util.NetworkStackConstants.ETHER_BROADCAST;
+import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_PIO;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_SLLA;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_TLLA;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NEIGHBOR_SOLICITATION;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_NODES_MULTICAST;
+import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE;
+import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
@@ -32,11 +44,24 @@
import androidx.annotation.Nullable;
+import com.android.net.module.util.Ipv6Utils;
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.structs.EthernetHeader;
+import com.android.net.module.util.structs.Icmpv6Header;
+import com.android.net.module.util.structs.Ipv6Header;
+import com.android.net.module.util.structs.LlaOption;
+import com.android.net.module.util.structs.NsHeader;
+import com.android.net.module.util.structs.PrefixInformationOption;
+import com.android.net.module.util.structs.RaHeader;
import com.android.networkstack.arp.ArpPacket;
import com.android.testutils.TapPacketReader;
import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeoutException;
import java.util.function.Predicate;
@@ -50,12 +75,14 @@
private static final String TAG = TetheringTester.class.getSimpleName();
private static final int PACKET_READ_TIMEOUT_MS = 100;
private static final int DHCP_DISCOVER_ATTEMPTS = 10;
+ private static final int READ_RA_ATTEMPTS = 10;
private static final byte[] DHCP_REQUESTED_PARAMS = new byte[] {
DhcpPacket.DHCP_SUBNET_MASK,
DhcpPacket.DHCP_ROUTER,
DhcpPacket.DHCP_DNS_SERVER,
DhcpPacket.DHCP_LEASE_TIME,
};
+ private static final InetAddress LINK_LOCAL = parseNumericAddress("fe80::1");
public static final String DHCP_HOSTNAME = "testhostname";
@@ -69,12 +96,13 @@
mTetheredDevices = new ArrayMap<>();
}
- public TetheredDevice createTetheredDevice(MacAddress macAddr) throws Exception {
+ public TetheredDevice createTetheredDevice(MacAddress macAddr, boolean hasIpv6)
+ throws Exception {
if (mTetheredDevices.get(macAddr) != null) {
fail("Tethered device already created");
}
- TetheredDevice tethered = new TetheredDevice(macAddr);
+ TetheredDevice tethered = new TetheredDevice(macAddr, hasIpv6);
mTetheredDevices.put(macAddr, tethered);
return tethered;
@@ -84,14 +112,15 @@
public final MacAddress macAddr;
public final MacAddress routerMacAddr;
public final Inet4Address ipv4Addr;
+ public final Inet6Address ipv6Addr;
- private TetheredDevice(MacAddress mac) throws Exception {
+ private TetheredDevice(MacAddress mac, boolean hasIpv6) throws Exception {
macAddr = mac;
-
DhcpResults dhcpResults = runDhcp(macAddr.toByteArray());
ipv4Addr = (Inet4Address) dhcpResults.ipAddress.getAddress();
routerMacAddr = getRouterMacAddressFromArp(ipv4Addr, macAddr,
dhcpResults.serverAddress);
+ ipv6Addr = hasIpv6 ? runSlaac(macAddr, routerMacAddr) : null;
}
}
@@ -216,6 +245,141 @@
return null;
}
+ public void waitForIpv6TetherConnectivityVerified() throws Exception {
+ Log.d(TAG, "Waiting RA multicast");
+
+ // Wait for RA multicast message from router to confirm that the IPv6 tethering
+ // connectivity is ready. We don't extract the router mac address from RA because
+ // we get the router mac address from IPv4 ARP packet. See #getRouterMacAddressFromArp.
+ for (int i = 0; i < READ_RA_ATTEMPTS; i++) {
+ final byte[] raPacket = getNextMatchedPacket((p) -> {
+ return isIcmpv6Type(p, true /* hasEth */, ICMPV6_ROUTER_ADVERTISEMENT);
+ });
+ if (raPacket != null) return;
+ }
+
+ fail("Could not get RA multicast packet after " + READ_RA_ATTEMPTS + " attempts");
+ }
+
+ private List<PrefixInformationOption> getRaPrefixOptions(byte[] packet) {
+ ByteBuffer buf = ByteBuffer.wrap(packet);
+ if (!isIcmpv6Type(buf, true /* hasEth */, ICMPV6_ROUTER_ADVERTISEMENT)) return null;
+
+ Struct.parse(RaHeader.class, buf);
+ final ArrayList<PrefixInformationOption> pioList = new ArrayList<>();
+ while (buf.position() < packet.length) {
+ final int currentPos = buf.position();
+ final int type = Byte.toUnsignedInt(buf.get());
+ final int length = Byte.toUnsignedInt(buf.get());
+ if (type == ICMPV6_ND_OPTION_PIO) {
+ final ByteBuffer pioBuf = ByteBuffer.wrap(buf.array(), currentPos,
+ Struct.getSize(PrefixInformationOption.class));
+ final PrefixInformationOption pio =
+ Struct.parse(PrefixInformationOption.class, pioBuf);
+ pioList.add(pio);
+
+ // Move ByteBuffer position to the next option.
+ buf.position(currentPos + Struct.getSize(PrefixInformationOption.class));
+ } else {
+ buf.position(currentPos + (length * 8));
+ }
+ }
+ return pioList;
+ }
+
+ private Inet6Address runSlaac(MacAddress srcMac, MacAddress dstMac) throws Exception {
+ sendRsPacket(srcMac, dstMac);
+
+ final byte[] raPacket = getNextMatchedPacket((p) -> {
+ return isIcmpv6Type(p, true /* hasEth */, ICMPV6_ROUTER_ADVERTISEMENT);
+ });
+
+ if (raPacket == null) {
+ fail("Could not get ra for prefix options");
+ }
+ final List<PrefixInformationOption> options = getRaPrefixOptions(raPacket);
+
+ for (PrefixInformationOption pio : options) {
+ if (pio.validLifetime > 0) {
+ final byte[] addressBytes = pio.prefix;
+ // Random the last two bytes as suffix.
+ // TODO: Currently do not implmement DAD in the test. Rely the gateway ipv6 address
+ // genetrated by tethering module always has random the last byte.
+ addressBytes[addressBytes.length - 1] = (byte) (new Random()).nextInt();
+ addressBytes[addressBytes.length - 2] = (byte) (new Random()).nextInt();
+
+ return (Inet6Address) InetAddress.getByAddress(addressBytes);
+ }
+ }
+
+ fail("Could not get ipv6 address");
+ return null;
+ }
+
+ private void sendRsPacket(MacAddress srcMac, MacAddress dstMac) throws Exception {
+ Log.d(TAG, "Sending RS");
+ ByteBuffer slla = LlaOption.build((byte) ICMPV6_ND_OPTION_SLLA, srcMac);
+ ByteBuffer rs = Ipv6Utils.buildRsPacket(srcMac, dstMac, (Inet6Address) LINK_LOCAL,
+ IPV6_ADDR_ALL_NODES_MULTICAST, slla);
+
+ sendPacket(rs);
+ }
+
+ private void maybeReplyNa(byte[] packet) {
+ ByteBuffer buf = ByteBuffer.wrap(packet);
+ final EthernetHeader ethHdr = Struct.parse(EthernetHeader.class, buf);
+ if (ethHdr.etherType != ETHER_TYPE_IPV6) return;
+
+ final Ipv6Header ipv6Hdr = Struct.parse(Ipv6Header.class, buf);
+ if (ipv6Hdr.nextHeader != (byte) IPPROTO_ICMPV6) return;
+
+ final Icmpv6Header icmpv6Hdr = Struct.parse(Icmpv6Header.class, buf);
+ if (icmpv6Hdr.type != (short) ICMPV6_NEIGHBOR_SOLICITATION) return;
+
+ final NsHeader nsHdr = Struct.parse(NsHeader.class, buf);
+ for (int i = 0; i < mTetheredDevices.size(); i++) {
+ TetheredDevice tethered = mTetheredDevices.valueAt(i);
+ if (!nsHdr.target.equals(tethered.ipv6Addr)) continue;
+
+ final ByteBuffer tlla = LlaOption.build((byte) ICMPV6_ND_OPTION_TLLA, tethered.macAddr);
+ int flags = NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED
+ | NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE;
+ ByteBuffer ns = Ipv6Utils.buildNaPacket(tethered.macAddr, tethered.routerMacAddr,
+ nsHdr.target, ipv6Hdr.srcIp, flags, nsHdr.target, tlla);
+ try {
+ sendPacket(ns);
+ } catch (Exception e) {
+ fail("Failed to reply NA for " + tethered.ipv6Addr);
+ }
+
+ return;
+ }
+ }
+
+ public static boolean isIcmpv6Type(byte[] packet, boolean hasEth, int type) {
+ final ByteBuffer buf = ByteBuffer.wrap(packet);
+ return isIcmpv6Type(buf, hasEth, type);
+ }
+
+ private static boolean isIcmpv6Type(ByteBuffer buf, boolean hasEth, int type) {
+ try {
+ if (hasEth) {
+ final EthernetHeader ethHdr = Struct.parse(EthernetHeader.class, buf);
+ if (ethHdr.etherType != ETHER_TYPE_IPV6) return false;
+ }
+
+ final Ipv6Header ipv6Hdr = Struct.parse(Ipv6Header.class, buf);
+ if (ipv6Hdr.nextHeader != (byte) IPPROTO_ICMPV6) return false;
+
+ final Icmpv6Header icmpv6Hdr = Struct.parse(Icmpv6Header.class, buf);
+ return icmpv6Hdr.type == (short) type;
+ } catch (Exception e) {
+ // Parsing packet fail means it is not icmpv6 packet.
+ }
+
+ return false;
+ }
+
public void sendPacket(ByteBuffer packet) throws Exception {
mDownstreamReader.sendResponse(packet);
}
@@ -226,6 +390,7 @@
if (filter.test(packet)) return packet;
maybeReplyArp(packet);
+ maybeReplyNa(packet);
}
return null;
diff --git a/Tethering/tests/jarjar-rules.txt b/Tethering/tests/jarjar-rules.txt
index a7c7488..cd8fd3a 100644
--- a/Tethering/tests/jarjar-rules.txt
+++ b/Tethering/tests/jarjar-rules.txt
@@ -7,6 +7,8 @@
rule com.android.internal.util.State* com.android.networkstack.tethering.util.State@1
rule com.android.internal.util.StateMachine* com.android.networkstack.tethering.util.StateMachine@1
rule com.android.internal.util.TrafficStatsConstants* com.android.networkstack.tethering.util.TrafficStatsConstants@1
+# Keep other com.android.internal.util as-is
+rule com.android.internal.util.** @0
rule android.util.LocalLog* com.android.networkstack.tethering.util.LocalLog@1
diff --git a/Tethering/tests/mts/Android.bp b/Tethering/tests/mts/Android.bp
index 18fd63b..a84fdd2 100644
--- a/Tethering/tests/mts/Android.bp
+++ b/Tethering/tests/mts/Android.bp
@@ -22,7 +22,7 @@
name: "MtsTetheringTestLatestSdk",
min_sdk_version: "30",
- target_sdk_version: "31",
+ target_sdk_version: "33",
libs: [
"android.test.base",
diff --git a/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java b/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
index ad2faa0..68c1c57 100644
--- a/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
+++ b/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
@@ -352,15 +352,6 @@
assertFalse(mTestMap.isEmpty());
mTestMap.clear();
assertTrue(mTestMap.isEmpty());
-
- // Clearing an already-closed map throws.
- mTestMap.close();
- try {
- mTestMap.clear();
- fail("clearing already-closed map should throw");
- } catch (ErrnoException expected) {
- assertEquals(OsConstants.EBADF, expected.errno);
- }
}
@Test
diff --git a/Tethering/tests/unit/Android.bp b/Tethering/tests/unit/Android.bp
index d1b8380..0ee12ad 100644
--- a/Tethering/tests/unit/Android.bp
+++ b/Tethering/tests/unit/Android.bp
@@ -89,7 +89,7 @@
static_libs: [
"TetheringApiStableLib",
],
- target_sdk_version: "31",
+ target_sdk_version: "33",
visibility: [
"//packages/modules/Connectivity/tests:__subpackages__",
"//packages/modules/Connectivity/Tethering/tests:__subpackages__",
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index 6488421..bf7e887 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -103,6 +103,8 @@
import com.android.net.module.util.NetworkStackConstants;
import com.android.net.module.util.bpf.Tether4Key;
import com.android.net.module.util.bpf.Tether4Value;
+import com.android.net.module.util.bpf.TetherStatsKey;
+import com.android.net.module.util.bpf.TetherStatsValue;
import com.android.networkstack.tethering.BpfCoordinator;
import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
import com.android.networkstack.tethering.PrivateAddressCoordinator;
@@ -112,10 +114,9 @@
import com.android.networkstack.tethering.TetherDownstream6Key;
import com.android.networkstack.tethering.TetherLimitKey;
import com.android.networkstack.tethering.TetherLimitValue;
-import com.android.networkstack.tethering.TetherStatsKey;
-import com.android.networkstack.tethering.TetherStatsValue;
import com.android.networkstack.tethering.TetherUpstream6Key;
import com.android.networkstack.tethering.TetheringConfiguration;
+import com.android.networkstack.tethering.metrics.TetheringMetrics;
import com.android.networkstack.tethering.util.InterfaceSet;
import com.android.networkstack.tethering.util.PrefixUtils;
import com.android.testutils.DevSdkIgnoreRule;
@@ -156,6 +157,8 @@
private static final int BLUETOOTH_DHCP_PREFIX_LENGTH = 24;
private static final int DHCP_LEASE_TIME_SECS = 3600;
private static final boolean DEFAULT_USING_BPF_OFFLOAD = true;
+ private static final int DEFAULT_SUBNET_PREFIX_LENGTH = 0;
+ private static final int P2P_SUBNET_PREFIX_LENGTH = 25;
private static final InterfaceParams TEST_IFACE_PARAMS = new InterfaceParams(
IFACE_NAME, 42 /* index */, MacAddress.ALL_ZEROS_ADDRESS, 1500 /* defaultMtu */);
@@ -184,6 +187,7 @@
@Mock private NetworkStatsManager mStatsManager;
@Mock private TetheringConfiguration mTetherConfig;
@Mock private ConntrackMonitor mConntrackMonitor;
+ @Mock private TetheringMetrics mTetheringMetrics;
@Mock private BpfMap<Tether4Key, Tether4Value> mBpfDownstream4Map;
@Mock private BpfMap<Tether4Key, Tether4Value> mBpfUpstream4Map;
@Mock private BpfMap<TetherDownstream6Key, Tether6Value> mBpfDownstream6Map;
@@ -230,9 +234,10 @@
when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(usingBpfOffload);
when(mTetherConfig.useLegacyDhcpServer()).thenReturn(usingLegacyDhcp);
+ when(mTetherConfig.getP2pLeasesSubnetPrefixLength()).thenReturn(P2P_SUBNET_PREFIX_LENGTH);
mIpServer = new IpServer(
IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd, mBpfCoordinator,
- mCallback, mTetherConfig, mAddressCoordinator, mDependencies);
+ mCallback, mTetherConfig, mAddressCoordinator, mTetheringMetrics, mDependencies);
mIpServer.start();
mNeighborEventConsumer = neighborCaptor.getValue();
@@ -364,7 +369,7 @@
.thenReturn(mIpNeighborMonitor);
mIpServer = new IpServer(IFACE_NAME, mLooper.getLooper(), TETHERING_BLUETOOTH, mSharedLog,
mNetd, mBpfCoordinator, mCallback, mTetherConfig, mAddressCoordinator,
- mDependencies);
+ mTetheringMetrics, mDependencies);
mIpServer.start();
mLooper.dispatchAll();
verify(mCallback).updateInterfaceState(
@@ -448,6 +453,9 @@
mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
inOrder.verify(mCallback).updateLinkProperties(
eq(mIpServer), any(LinkProperties.class));
+ verify(mTetheringMetrics).updateErrorCode(eq(TETHERING_BLUETOOTH),
+ eq(TETHER_ERROR_NO_ERROR));
+ verify(mTetheringMetrics).sendReport(eq(TETHERING_BLUETOOTH));
verifyNoMoreInteractions(mNetd, mCallback, mAddressCoordinator);
}
@@ -655,6 +663,9 @@
usbTeardownOrder.verify(mCallback).updateLinkProperties(
eq(mIpServer), mLinkPropertiesCaptor.capture());
assertNoAddressesNorRoutes(mLinkPropertiesCaptor.getValue());
+ verify(mTetheringMetrics).updateErrorCode(eq(TETHERING_USB),
+ eq(TETHER_ERROR_TETHER_IFACE_ERROR));
+ verify(mTetheringMetrics).sendReport(eq(TETHERING_USB));
}
@Test
@@ -673,6 +684,9 @@
usbTeardownOrder.verify(mCallback).updateLinkProperties(
eq(mIpServer), mLinkPropertiesCaptor.capture());
assertNoAddressesNorRoutes(mLinkPropertiesCaptor.getValue());
+ verify(mTetheringMetrics).updateErrorCode(eq(TETHERING_USB),
+ eq(TETHER_ERROR_ENABLE_FORWARDING_ERROR));
+ verify(mTetheringMetrics).sendReport(eq(TETHERING_USB));
}
@Test
@@ -1312,6 +1326,12 @@
if (mIpServer.interfaceType() == TETHERING_NCM) {
assertTrue(params.changePrefixOnDecline);
}
+
+ if (mIpServer.interfaceType() == TETHERING_WIFI_P2P) {
+ assertEquals(P2P_SUBNET_PREFIX_LENGTH, params.leasesSubnetPrefixLength);
+ } else {
+ assertEquals(DEFAULT_SUBNET_PREFIX_LENGTH, params.leasesSubnetPrefixLength);
+ }
}
private void assertDhcpStarted(IpPrefix expectedPrefix) throws Exception {
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
index 179fc8a..3630f24 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
@@ -102,6 +102,8 @@
import com.android.net.module.util.NetworkStackConstants;
import com.android.net.module.util.bpf.Tether4Key;
import com.android.net.module.util.bpf.Tether4Value;
+import com.android.net.module.util.bpf.TetherStatsKey;
+import com.android.net.module.util.bpf.TetherStatsValue;
import com.android.net.module.util.netlink.ConntrackMessage;
import com.android.net.module.util.netlink.NetlinkConstants;
import com.android.net.module.util.netlink.NetlinkSocket;
@@ -224,8 +226,6 @@
private int mSrcPort = PRIVATE_PORT;
private int mDstPort = REMOTE_PORT;
- Builder() {}
-
public Builder setProto(int proto) {
if (proto != IPPROTO_TCP && proto != IPPROTO_UDP) {
fail("Not support protocol " + proto);
@@ -250,8 +250,6 @@
private int mSrcPort = REMOTE_PORT;
private int mDstPort = PUBLIC_PORT;
- Builder() {}
-
public Builder setProto(int proto) {
if (proto != IPPROTO_TCP && proto != IPPROTO_UDP) {
fail("Not support protocol " + proto);
@@ -279,8 +277,6 @@
private int mDstPort = REMOTE_PORT;
private long mLastUsed = 0;
- Builder() {}
-
public Tether4Value build() {
return new Tether4Value(mOif, mEthDstMac, mEthSrcMac, mEthProto, mPmtu,
mSrc46, mDst46, mSrcPort, mDstPort, mLastUsed);
@@ -301,8 +297,6 @@
private int mDstPort = PRIVATE_PORT;
private long mLastUsed = 0;
- Builder() {}
-
public Tether4Value build() {
return new Tether4Value(mOif, mEthDstMac, mEthSrcMac, mEthProto, mPmtu,
mSrc46, mDst46, mSrcPort, mDstPort, mLastUsed);
@@ -321,8 +315,6 @@
private short mPublicPort = PUBLIC_PORT;
private short mRemotePort = REMOTE_PORT;
- Builder() {}
-
public Builder setMsgType(short msgType) {
if (msgType != IPCTNL_MSG_CT_NEW && msgType != IPCTNL_MSG_CT_DELETE) {
fail("Not support message type " + msgType);
@@ -377,6 +369,7 @@
// Late init since the object must be initialized by the BPF coordinator instance because
// it has to access the non-static function of BPF coordinator.
private BpfConntrackEventConsumer mConsumer;
+ private HashMap<IpServer, HashMap<Inet4Address, ClientInfo>> mTetherClients;
private long mElapsedRealtimeNanos = 0;
private final ArgumentCaptor<ArrayList> mStringArrayCaptor =
@@ -482,6 +475,8 @@
final BpfCoordinator coordinator = new BpfCoordinator(mDeps);
mConsumer = coordinator.getBpfConntrackEventConsumerForTesting();
+ mTetherClients = coordinator.getTetherClientsForTesting();
+
final ArgumentCaptor<BpfCoordinator.BpfTetherStatsProvider>
tetherStatsProviderCaptor =
ArgumentCaptor.forClass(BpfCoordinator.BpfTetherStatsProvider.class);
@@ -1793,4 +1788,280 @@
clearInvocations(mBpfUpstream4Map, mBpfDownstream4Map);
}
}
+
+ // Test network topology:
+ //
+ // public network UE private network
+ // | / \ |
+ // +------------+ V +-------------+ +--------------+ V +------------+
+ // | Sever +------+ Upstream |+------+-----+ Downstream 1 +-----+ Client A |
+ // +------------+ +-------------+| | +--------------+ +------------+
+ // remote ip +-------------+ | private ip
+ // 140.112.8.116:443 public ip | 192.168.80.12:62449
+ // (upstream 1, rawip) |
+ // 1.0.0.1:62449 |
+ // 1.0.0.1:62450 | +--------------+ +------------+
+ // - or - +-----+ Downstream 2 +-----+ Client B |
+ // (upstream 2, ether) +--------------+ +------------+
+ // private ip
+ // 192.168.90.12:62450
+ //
+ // Build two test rule sets which include BPF upstream and downstream rules.
+ //
+ // Rule set A: a socket connection from client A to remote server via the first upstream
+ // (UPSTREAM_IFINDEX).
+ // 192.168.80.12:62449 -> 1.0.0.1:62449 -> 140.112.8.116:443
+ // Rule set B: a socket connection from client B to remote server via the first upstream
+ // (UPSTREAM_IFINDEX).
+ // 192.168.80.12:62450 -> 1.0.0.1:62450 -> 140.112.8.116:443
+ //
+ // The second upstream (UPSTREAM_IFINDEX2) is an ethernet interface which is not supported by
+ // BPF. Used for testing the rule adding and removing on an unsupported upstream interface.
+ //
+ private static final Tether4Key UPSTREAM4_RULE_KEY_A = makeUpstream4Key(
+ DOWNSTREAM_IFINDEX, DOWNSTREAM_MAC, PRIVATE_ADDR, PRIVATE_PORT);
+ private static final Tether4Value UPSTREAM4_RULE_VALUE_A = makeUpstream4Value(PUBLIC_PORT);
+ private static final Tether4Key DOWNSTREAM4_RULE_KEY_A = makeDownstream4Key(PUBLIC_PORT);
+ private static final Tether4Value DOWNSTREAM4_RULE_VALUE_A = makeDownstream4Value(
+ DOWNSTREAM_IFINDEX, MAC_A, DOWNSTREAM_MAC, PRIVATE_ADDR, PRIVATE_PORT);
+
+ private static final Tether4Key UPSTREAM4_RULE_KEY_B = makeUpstream4Key(
+ DOWNSTREAM_IFINDEX2, DOWNSTREAM_MAC2, PRIVATE_ADDR2, PRIVATE_PORT2);
+ private static final Tether4Value UPSTREAM4_RULE_VALUE_B = makeUpstream4Value(PUBLIC_PORT2);
+ private static final Tether4Key DOWNSTREAM4_RULE_KEY_B = makeDownstream4Key(PUBLIC_PORT2);
+ private static final Tether4Value DOWNSTREAM4_RULE_VALUE_B = makeDownstream4Value(
+ DOWNSTREAM_IFINDEX2, MAC_B, DOWNSTREAM_MAC2, PRIVATE_ADDR2, PRIVATE_PORT2);
+
+ private static final ConntrackEvent CONNTRACK_EVENT_A = makeTestConntrackEvent(
+ PUBLIC_PORT, PRIVATE_ADDR, PRIVATE_PORT);
+
+ private static final ConntrackEvent CONNTRACK_EVENT_B = makeTestConntrackEvent(
+ PUBLIC_PORT2, PRIVATE_ADDR2, PRIVATE_PORT2);
+
+ @NonNull
+ private static Tether4Key makeUpstream4Key(final int downstreamIfindex,
+ @NonNull final MacAddress downstreamMac, @NonNull final Inet4Address privateAddr,
+ final short privatePort) {
+ return new Tether4Key(downstreamIfindex, downstreamMac, (short) IPPROTO_TCP,
+ privateAddr.getAddress(), REMOTE_ADDR.getAddress(), privatePort, REMOTE_PORT);
+ }
+
+ @NonNull
+ private static Tether4Key makeDownstream4Key(final short publicPort) {
+ return new Tether4Key(UPSTREAM_IFINDEX, MacAddress.ALL_ZEROS_ADDRESS /* dstMac (rawip) */,
+ (short) IPPROTO_TCP, REMOTE_ADDR.getAddress(), PUBLIC_ADDR.getAddress(),
+ REMOTE_PORT, publicPort);
+ }
+
+ @NonNull
+ private static Tether4Value makeUpstream4Value(final short publicPort) {
+ return new Tether4Value(UPSTREAM_IFINDEX,
+ MacAddress.ALL_ZEROS_ADDRESS /* ethDstMac (rawip) */,
+ MacAddress.ALL_ZEROS_ADDRESS /* ethSrcMac (rawip) */, ETH_P_IP,
+ NetworkStackConstants.ETHER_MTU, toIpv4MappedAddressBytes(PUBLIC_ADDR),
+ toIpv4MappedAddressBytes(REMOTE_ADDR), publicPort, REMOTE_PORT,
+ 0 /* lastUsed */);
+ }
+
+ @NonNull
+ private static Tether4Value makeDownstream4Value(final int downstreamIfindex,
+ @NonNull final MacAddress clientMac, @NonNull final MacAddress downstreamMac,
+ @NonNull final Inet4Address privateAddr, final short privatePort) {
+ return new Tether4Value(downstreamIfindex, clientMac, downstreamMac,
+ ETH_P_IP, NetworkStackConstants.ETHER_MTU, toIpv4MappedAddressBytes(REMOTE_ADDR),
+ toIpv4MappedAddressBytes(privateAddr), REMOTE_PORT, privatePort, 0 /* lastUsed */);
+ }
+
+ @NonNull
+ private static ConntrackEvent makeTestConntrackEvent(final short publicPort,
+ @NonNull final Inet4Address privateAddr, final short privatePort) {
+ return new ConntrackEvent(
+ (short) (NetlinkConstants.NFNL_SUBSYS_CTNETLINK << 8 | IPCTNL_MSG_CT_NEW),
+ new Tuple(new TupleIpv4(privateAddr, REMOTE_ADDR),
+ new TupleProto((byte) IPPROTO_TCP, privatePort, REMOTE_PORT)),
+ new Tuple(new TupleIpv4(REMOTE_ADDR, PUBLIC_ADDR),
+ new TupleProto((byte) IPPROTO_TCP, REMOTE_PORT, publicPort)),
+ ESTABLISHED_MASK,
+ 100 /* nonzero, CT_NEW */);
+ }
+
+ void checkRule4ExistInUpstreamDownstreamMap() throws Exception {
+ assertEquals(UPSTREAM4_RULE_VALUE_A, mBpfUpstream4Map.getValue(UPSTREAM4_RULE_KEY_A));
+ assertEquals(DOWNSTREAM4_RULE_VALUE_A, mBpfDownstream4Map.getValue(
+ DOWNSTREAM4_RULE_KEY_A));
+ assertEquals(UPSTREAM4_RULE_VALUE_B, mBpfUpstream4Map.getValue(UPSTREAM4_RULE_KEY_B));
+ assertEquals(DOWNSTREAM4_RULE_VALUE_B, mBpfDownstream4Map.getValue(
+ DOWNSTREAM4_RULE_KEY_B));
+ }
+
+ void checkRule4NotExistInUpstreamDownstreamMap() throws Exception {
+ assertNull(mBpfUpstream4Map.getValue(UPSTREAM4_RULE_KEY_A));
+ assertNull(mBpfDownstream4Map.getValue(DOWNSTREAM4_RULE_KEY_A));
+ assertNull(mBpfUpstream4Map.getValue(UPSTREAM4_RULE_KEY_B));
+ assertNull(mBpfDownstream4Map.getValue(DOWNSTREAM4_RULE_KEY_B));
+ }
+
+ // Both #addDownstreamAndClientInformationTo and #setUpstreamInformationTo need to be called
+ // before this function because upstream and downstream information are required to build
+ // the rules while conntrack event is received.
+ void addAndCheckRule4ForDownstreams() throws Exception {
+ // Add rule set A which is on the first downstream and rule set B which is on the second
+ // downstream.
+ mConsumer.accept(CONNTRACK_EVENT_A);
+ mConsumer.accept(CONNTRACK_EVENT_B);
+
+ // Check that both rule set A and B were added.
+ checkRule4ExistInUpstreamDownstreamMap();
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.R)
+ public void testTetherOffloadRule4Clear_RemoveDownstream() throws Exception {
+ final BpfCoordinator coordinator = makeBpfCoordinator();
+
+ // Initialize upstream and downstream information manually but calling the setup helper
+ // #initBpfCoordinatorForRule4 because this test needs to {update, remove} upstream and
+ // downstream manually for testing.
+ addDownstreamAndClientInformationTo(coordinator, DOWNSTREAM_IFINDEX);
+ addDownstreamAndClientInformationTo(coordinator, DOWNSTREAM_IFINDEX2);
+
+ setUpstreamInformationTo(coordinator, UPSTREAM_IFINDEX);
+ addAndCheckRule4ForDownstreams();
+
+ // [1] Remove the first downstream. Remove only the rule set A which is on the first
+ // downstream.
+ coordinator.tetherOffloadClientClear(mIpServer);
+ assertNull(mBpfUpstream4Map.getValue(UPSTREAM4_RULE_KEY_A));
+ assertNull(mBpfDownstream4Map.getValue(DOWNSTREAM4_RULE_KEY_A));
+ assertEquals(UPSTREAM4_RULE_VALUE_B, mBpfUpstream4Map.getValue(
+ UPSTREAM4_RULE_KEY_B));
+ assertEquals(DOWNSTREAM4_RULE_VALUE_B, mBpfDownstream4Map.getValue(
+ DOWNSTREAM4_RULE_KEY_B));
+
+ // Clear client information for the first downstream only.
+ assertNull(mTetherClients.get(mIpServer));
+ assertNotNull(mTetherClients.get(mIpServer2));
+
+ // [2] Remove the second downstream. Remove the rule set B which is on the second
+ // downstream.
+ coordinator.tetherOffloadClientClear(mIpServer2);
+ assertNull(mBpfUpstream4Map.getValue(UPSTREAM4_RULE_KEY_B));
+ assertNull(mBpfDownstream4Map.getValue(DOWNSTREAM4_RULE_KEY_B));
+
+ // Clear client information for the second downstream.
+ assertNull(mTetherClients.get(mIpServer2));
+ }
+
+ private void asseertClientInfoExist(@NonNull IpServer ipServer,
+ @NonNull ClientInfo clientInfo) {
+ HashMap<Inet4Address, ClientInfo> clients = mTetherClients.get(ipServer);
+ assertNotNull(clients);
+ assertEquals(clientInfo, clients.get(clientInfo.clientAddress));
+ }
+
+ // Although either ClientInfo for a given downstream (IpServer) is not found or a given
+ // client address is not found on a given downstream can be treated "ClientInfo not
+ // exist", we still want to know the real reason exactly. For example, we don't the
+ // exact reason in the following:
+ // assertNull(clients == null ? clients : clients.get(clientInfo.clientAddress));
+ // This helper only verifies the case that the downstream still has at least one client.
+ // In other words, ClientInfo for a given IpServer has not been removed yet.
+ private void asseertClientInfoNotExist(@NonNull IpServer ipServer,
+ @NonNull ClientInfo clientInfo) {
+ HashMap<Inet4Address, ClientInfo> clients = mTetherClients.get(ipServer);
+ assertNotNull(clients);
+ assertNull(clients.get(clientInfo.clientAddress));
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.R)
+ public void testTetherOffloadRule4Clear_ChangeOrRemoveUpstream() throws Exception {
+ final BpfCoordinator coordinator = makeBpfCoordinator();
+
+ // Initialize upstream and downstream information manually but calling the helper
+ // #initBpfCoordinatorForRule4 because this test needs to {update, remove} upstream and
+ // downstream.
+ addDownstreamAndClientInformationTo(coordinator, DOWNSTREAM_IFINDEX);
+ addDownstreamAndClientInformationTo(coordinator, DOWNSTREAM_IFINDEX2);
+
+ setUpstreamInformationTo(coordinator, UPSTREAM_IFINDEX);
+ addAndCheckRule4ForDownstreams();
+
+ // [1] Update the same upstream state. Nothing happens.
+ setUpstreamInformationTo(coordinator, UPSTREAM_IFINDEX);
+ checkRule4ExistInUpstreamDownstreamMap();
+
+ // [2] Switch upstream interface from the first upstream (rawip, bpf supported) to
+ // the second upstream (ethernet, bpf not supported). Clear all rules.
+ setUpstreamInformationTo(coordinator, UPSTREAM_IFINDEX2);
+ checkRule4NotExistInUpstreamDownstreamMap();
+
+ // Setup the upstream interface information and the rules for next test.
+ setUpstreamInformationTo(coordinator, UPSTREAM_IFINDEX);
+ addAndCheckRule4ForDownstreams();
+
+ // [3] Switch upstream from the first upstream (rawip, bpf supported) to no upstream. Clear
+ // all rules.
+ setUpstreamInformationTo(coordinator, INVALID_IFINDEX);
+ checkRule4NotExistInUpstreamDownstreamMap();
+
+ // Client information should be not deleted.
+ asseertClientInfoExist(mIpServer, CLIENT_INFO_A);
+ asseertClientInfoExist(mIpServer2, CLIENT_INFO_B);
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.R)
+ public void testTetherOffloadClientAddRemove() throws Exception {
+ final BpfCoordinator coordinator = makeBpfCoordinator();
+
+ // [1] Add client information A and B on on the same downstream.
+ final ClientInfo clientA = new ClientInfo(DOWNSTREAM_IFINDEX, DOWNSTREAM_MAC,
+ PRIVATE_ADDR, MAC_A);
+ final ClientInfo clientB = new ClientInfo(DOWNSTREAM_IFINDEX, DOWNSTREAM_MAC,
+ PRIVATE_ADDR2, MAC_B);
+ coordinator.tetherOffloadClientAdd(mIpServer, clientA);
+ coordinator.tetherOffloadClientAdd(mIpServer, clientB);
+ asseertClientInfoExist(mIpServer, clientA);
+ asseertClientInfoExist(mIpServer, clientB);
+
+ // Add the rules for client A and client B.
+ final Tether4Key upstream4KeyA = makeUpstream4Key(
+ DOWNSTREAM_IFINDEX, DOWNSTREAM_MAC, PRIVATE_ADDR, PRIVATE_PORT);
+ final Tether4Value upstream4ValueA = makeUpstream4Value(PUBLIC_PORT);
+ final Tether4Key downstream4KeyA = makeDownstream4Key(PUBLIC_PORT);
+ final Tether4Value downstream4ValueA = makeDownstream4Value(
+ DOWNSTREAM_IFINDEX, MAC_A, DOWNSTREAM_MAC, PRIVATE_ADDR, PRIVATE_PORT);
+ final Tether4Key upstream4KeyB = makeUpstream4Key(
+ DOWNSTREAM_IFINDEX, DOWNSTREAM_MAC2, PRIVATE_ADDR2, PRIVATE_PORT2);
+ final Tether4Value upstream4ValueB = makeUpstream4Value(PUBLIC_PORT2);
+ final Tether4Key downstream4KeyB = makeDownstream4Key(PUBLIC_PORT2);
+ final Tether4Value downstream4ValueB = makeDownstream4Value(
+ DOWNSTREAM_IFINDEX, MAC_B, DOWNSTREAM_MAC2, PRIVATE_ADDR2, PRIVATE_PORT2);
+
+ mBpfUpstream4Map.insertEntry(upstream4KeyA, upstream4ValueA);
+ mBpfDownstream4Map.insertEntry(downstream4KeyA, downstream4ValueA);
+ mBpfUpstream4Map.insertEntry(upstream4KeyB, upstream4ValueB);
+ mBpfDownstream4Map.insertEntry(downstream4KeyB, downstream4ValueB);
+
+ // [2] Remove client information A. Only the rules on client A should be removed and
+ // the rules on client B should exist.
+ coordinator.tetherOffloadClientRemove(mIpServer, clientA);
+ asseertClientInfoNotExist(mIpServer, clientA);
+ asseertClientInfoExist(mIpServer, clientB);
+ assertNull(mBpfUpstream4Map.getValue(upstream4KeyA));
+ assertNull(mBpfDownstream4Map.getValue(downstream4KeyA));
+ assertEquals(upstream4ValueB, mBpfUpstream4Map.getValue(upstream4KeyB));
+ assertEquals(downstream4ValueB, mBpfDownstream4Map.getValue(downstream4KeyB));
+
+ // [3] Remove client information B. The rules on client B should be removed.
+ // Exactly, ClientInfo for a given IpServer is removed because the last client B
+ // has been removed from the downstream. Can't use the helper #asseertClientInfoExist
+ // to check because the container ClientInfo for a given downstream has been removed.
+ // See #asseertClientInfoExist.
+ coordinator.tetherOffloadClientRemove(mIpServer, clientB);
+ assertNull(mTetherClients.get(mIpServer));
+ assertNull(mBpfUpstream4Map.getValue(upstream4KeyB));
+ assertNull(mBpfDownstream4Map.getValue(downstream4KeyB));
+ }
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
index 690ff71..01d7b4b 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
@@ -304,33 +304,6 @@
}
@Test
- public void toleratesCarrierConfigManagerMissing() {
- setupForRequiredProvisioning();
- mockService(Context.CARRIER_CONFIG_SERVICE, CarrierConfigManager.class, null);
- mConfig = new FakeTetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
- // Couldn't get the CarrierConfigManager, but still had a declared provisioning app.
- // Therefore provisioning still be required.
- assertTrue(mEnMgr.isTetherProvisioningRequired(mConfig));
- }
-
- @Test
- public void toleratesCarrierConfigMissing() {
- setupForRequiredProvisioning();
- when(mCarrierConfigManager.getConfig()).thenReturn(null);
- mConfig = new FakeTetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
- // We still have a provisioning app configured, so still require provisioning.
- assertTrue(mEnMgr.isTetherProvisioningRequired(mConfig));
- }
-
- @Test
- public void toleratesCarrierConfigNotLoaded() {
- setupForRequiredProvisioning();
- mCarrierConfig.putBoolean(CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, false);
- // We still have a provisioning app configured, so still require provisioning.
- assertTrue(mEnMgr.isTetherProvisioningRequired(mConfig));
- }
-
- @Test
public void provisioningNotRequiredWhenAppNotFound() {
setupForRequiredProvisioning();
when(mResources.getStringArray(R.array.config_mobile_hotspot_provision_app))
@@ -706,8 +679,8 @@
@IgnoreUpTo(SC_V2)
public void requestLatestTetheringEntitlementResult_carrierDoesNotSupport_noProvisionCount()
throws Exception {
- setupForRequiredProvisioning();
setupCarrierConfig(false);
+ setupForRequiredProvisioning();
mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
ResultReceiver receiver = new ResultReceiver(null) {
@Override
@@ -735,6 +708,7 @@
mEnMgr.notifyUpstream(false);
mLooper.dispatchAll();
setupCarrierConfig(false);
+ mConfig = new FakeTetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
mEnMgr.reevaluateSimCardProvisioning(mConfig);
// Turn on upstream.
@@ -749,8 +723,8 @@
@IgnoreUpTo(SC_V2)
public void startProvisioningIfNeeded_carrierUnsupport()
throws Exception {
- setupForRequiredProvisioning();
setupCarrierConfig(false);
+ setupForRequiredProvisioning();
mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true);
verify(mTetherProvisioningFailedListener, never())
.onTetherProvisioningFailed(TETHERING_WIFI, "Carrier does not support.");
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java
index e9716b3..8ef0c76 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java
@@ -26,6 +26,7 @@
import static android.net.RouteInfo.RTN_UNICAST;
import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
+import static com.android.modules.utils.build.SdkLevel.isAtLeastS;
import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
import static com.android.networkstack.tethering.OffloadController.StatsType.STATS_PER_IFACE;
import static com.android.networkstack.tethering.OffloadController.StatsType.STATS_PER_UID;
@@ -668,7 +669,7 @@
if (isAtLeastT()) {
mTetherStatsProviderCb.expectNotifyLimitReached();
- } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.S) {
+ } else if (isAtLeastS()) {
mTetherStatsProviderCb.expectNotifyWarningOrLimitReached();
} else {
mTetherStatsProviderCb.expectNotifyLimitReached();
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
index e8bb315..3190f35 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
@@ -22,10 +22,13 @@
import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
+import static android.telephony.CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL;
+import static android.telephony.CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.networkstack.apishim.ConstantsShim.KEY_CARRIER_SUPPORTS_TETHERING_BOOL;
import static com.android.networkstack.tethering.TetheringConfiguration.TETHER_FORCE_USB_FUNCTIONS;
import static com.android.networkstack.tethering.TetheringConfiguration.TETHER_USB_NCM_FUNCTION;
import static com.android.networkstack.tethering.TetheringConfiguration.TETHER_USB_RNDIS_FUNCTION;
@@ -46,8 +49,10 @@
import android.content.res.Resources;
import android.net.util.SharedLog;
import android.os.Build;
+import android.os.PersistableBundle;
import android.provider.DeviceConfig;
import android.provider.Settings;
+import android.telephony.CarrierConfigManager;
import android.telephony.TelephonyManager;
import android.test.mock.MockContentResolver;
@@ -56,6 +61,7 @@
import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.DeviceConfigUtils;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
@@ -88,6 +94,7 @@
private static final long TEST_PACKAGE_VERSION = 1234L;
@Mock private ApplicationInfo mApplicationInfo;
@Mock private Context mContext;
+ @Mock private CarrierConfigManager mCarrierConfigManager;
@Mock private TelephonyManager mTelephonyManager;
@Mock private Resources mResources;
@Mock private Resources mResourcesForSubId;
@@ -97,6 +104,7 @@
private boolean mHasTelephonyManager;
private MockitoSession mMockingSession;
private MockContentResolver mContentResolver;
+ private final PersistableBundle mCarrierConfig = new PersistableBundle();
private class MockTetheringConfiguration extends TetheringConfiguration {
MockTetheringConfiguration(Context ctx, SharedLog log, int id) {
@@ -474,6 +482,56 @@
PROVISIONING_APP_RESPONSE);
}
+ private <T> void mockService(String serviceName, Class<T> serviceClass, T service) {
+ when(mMockContext.getSystemServiceName(serviceClass)).thenReturn(serviceName);
+ when(mMockContext.getSystemService(serviceName)).thenReturn(service);
+ }
+
+ @Test
+ public void testGetCarrierConfigBySubId_noCarrierConfigManager_configsAreDefault() {
+ // Act like the CarrierConfigManager is present and ready unless told otherwise.
+ mockService(Context.CARRIER_CONFIG_SERVICE,
+ CarrierConfigManager.class, null);
+ final TetheringConfiguration cfg = new TetheringConfiguration(
+ mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+
+ assertTrue(cfg.isCarrierSupportTethering);
+ assertTrue(cfg.isCarrierConfigAffirmsEntitlementCheckRequired);
+ }
+
+ @Test
+ public void testGetCarrierConfigBySubId_carrierConfigMissing_configsAreDefault() {
+ // Act like the CarrierConfigManager is present and ready unless told otherwise.
+ mockService(Context.CARRIER_CONFIG_SERVICE,
+ CarrierConfigManager.class, mCarrierConfigManager);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(null);
+ final TetheringConfiguration cfg = new TetheringConfiguration(
+ mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+
+ assertTrue(cfg.isCarrierSupportTethering);
+ assertTrue(cfg.isCarrierConfigAffirmsEntitlementCheckRequired);
+ }
+
+ @Test
+ public void testGetCarrierConfigBySubId_hasConfigs_carrierUnsupportAndCheckNotRequired() {
+ mockService(Context.CARRIER_CONFIG_SERVICE,
+ CarrierConfigManager.class, mCarrierConfigManager);
+ mCarrierConfig.putBoolean(KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
+ mCarrierConfig.putBoolean(KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL, false);
+ mCarrierConfig.putBoolean(KEY_CARRIER_SUPPORTS_TETHERING_BOOL, false);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(mCarrierConfig);
+ final TetheringConfiguration cfg = new TetheringConfiguration(
+ mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+
+ if (SdkLevel.isAtLeastT()) {
+ assertFalse(cfg.isCarrierSupportTethering);
+ } else {
+ assertTrue(cfg.isCarrierSupportTethering);
+ }
+ assertFalse(cfg.isCarrierConfigAffirmsEntitlementCheckRequired);
+
+ }
+
@Test
public void testEnableLegacyWifiP2PAddress() throws Exception {
final TetheringConfiguration defaultCfg = new TetheringConfiguration(
@@ -616,4 +674,35 @@
assertArrayEquals(ncmRegexs, cfg.tetherableNcmRegexs);
}
+ @Test
+ public void testP2pLeasesSubnetPrefixLength() throws Exception {
+ when(mResources.getBoolean(R.bool.config_tether_enable_legacy_wifi_p2p_dedicated_ip))
+ .thenReturn(true);
+
+ final int defaultSubnetPrefixLength = 0;
+ final TetheringConfiguration defaultCfg =
+ new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+ assertEquals(defaultSubnetPrefixLength, defaultCfg.getP2pLeasesSubnetPrefixLength());
+
+ final int prefixLengthTooSmall = -1;
+ when(mResources.getInteger(R.integer.config_p2p_leases_subnet_prefix_length)).thenReturn(
+ prefixLengthTooSmall);
+ final TetheringConfiguration tooSmallCfg =
+ new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+ assertEquals(defaultSubnetPrefixLength, tooSmallCfg.getP2pLeasesSubnetPrefixLength());
+
+ final int prefixLengthTooLarge = 31;
+ when(mResources.getInteger(R.integer.config_p2p_leases_subnet_prefix_length)).thenReturn(
+ prefixLengthTooLarge);
+ final TetheringConfiguration tooLargeCfg =
+ new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+ assertEquals(defaultSubnetPrefixLength, tooLargeCfg.getP2pLeasesSubnetPrefixLength());
+
+ final int p2pLeasesSubnetPrefixLength = 27;
+ when(mResources.getInteger(R.integer.config_p2p_leases_subnet_prefix_length)).thenReturn(
+ p2pLeasesSubnetPrefixLength);
+ final TetheringConfiguration p2pCfg =
+ new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+ assertEquals(p2pLeasesSubnetPrefixLength, p2pCfg.getP2pLeasesSubnetPrefixLength());
+ }
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
index f664d5d..9db8f16 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
@@ -275,7 +275,7 @@
mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
result);
verify(mTethering).isTetheringSupported();
- verify(mTethering).startTethering(eq(request), eq(result));
+ verify(mTethering).startTethering(eq(request), eq(TEST_CALLER_PKG), eq(result));
}
@Test
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 0388758..773cae3 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -45,6 +45,7 @@
import static android.net.TetheringManager.TETHERING_WIFI;
import static android.net.TetheringManager.TETHERING_WIFI_P2P;
import static android.net.TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_INTERNAL_ERROR;
import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
import static android.net.TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL;
import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_IFACE;
@@ -57,6 +58,7 @@
import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_STATE;
import static android.net.wifi.WifiManager.IFACE_IP_MODE_LOCAL_ONLY;
import static android.net.wifi.WifiManager.IFACE_IP_MODE_TETHERED;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED;
import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -142,6 +144,7 @@
import android.net.TetheringCallbackStartedParcel;
import android.net.TetheringConfigurationParcel;
import android.net.TetheringInterface;
+import android.net.TetheringManager;
import android.net.TetheringRequestParcel;
import android.net.dhcp.DhcpLeaseParcelable;
import android.net.dhcp.DhcpServerCallbacks;
@@ -175,6 +178,7 @@
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.test.mock.MockContentResolver;
+import android.util.ArraySet;
import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
@@ -190,11 +194,15 @@
import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceRequestShim;
import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
import com.android.networkstack.tethering.TestConnectivityManager.TestNetworkAgent;
+import com.android.networkstack.tethering.metrics.TetheringMetrics;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.MiscAsserts;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -211,11 +219,14 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.Set;
import java.util.Vector;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class TetheringTest {
+ @Rule public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
+
private static final int IFINDEX_OFFSET = 100;
private static final String TEST_MOBILE_IFNAME = "test_rmnet_data0";
@@ -236,6 +247,7 @@
private static final String TEST_WIFI_REGEX = "test_wlan\\d";
private static final String TEST_P2P_REGEX = "test_p2p-p2p\\d-.*";
private static final String TEST_BT_REGEX = "test_pan\\d";
+ private static final String TEST_CALLER_PKG = "com.test.tethering";
private static final int CELLULAR_NETID = 100;
private static final int WIFI_NETID = 101;
@@ -270,6 +282,7 @@
@Mock private BluetoothPan mBluetoothPan;
@Mock private BluetoothPanShim mBluetoothPanShim;
@Mock private TetheredInterfaceRequestShim mTetheredInterfaceRequestShim;
+ @Mock private TetheringMetrics mTetheringMetrics;
private final MockIpServerDependencies mIpServerDependencies =
spy(new MockIpServerDependencies());
@@ -293,6 +306,7 @@
private OffloadController mOffloadCtrl;
private PrivateAddressCoordinator mPrivateAddressCoordinator;
private SoftApCallback mSoftApCallback;
+ private SoftApCallback mLocalOnlyHotspotCallback;
private UpstreamNetworkMonitor mUpstreamNetworkMonitor;
private TetheredInterfaceCallbackShim mTetheredInterfaceCallbackShim;
@@ -494,6 +508,11 @@
}
@Override
+ public TetheringMetrics getTetheringMetrics() {
+ return mTetheringMetrics;
+ }
+
+ @Override
public PrivateAddressCoordinator getPrivateAddressCoordinator(Context ctx,
TetheringConfiguration cfg) {
mPrivateAddressCoordinator = super.getPrivateAddressCoordinator(ctx, cfg);
@@ -658,6 +677,14 @@
verify(mWifiManager).registerSoftApCallback(any(), softApCallbackCaptor.capture());
mSoftApCallback = softApCallbackCaptor.getValue();
+ if (isAtLeastT()) {
+ final ArgumentCaptor<SoftApCallback> localOnlyCallbackCaptor =
+ ArgumentCaptor.forClass(SoftApCallback.class);
+ verify(mWifiManager).registerLocalOnlyHotspotSoftApCallback(any(),
+ localOnlyCallbackCaptor.capture());
+ mLocalOnlyHotspotCallback = localOnlyCallbackCaptor.getValue();
+ }
+
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)).thenReturn(true);
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)).thenReturn(true);
}
@@ -852,7 +879,8 @@
private void prepareNcmTethering() {
// Emulate startTethering(TETHERING_NCM) called
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_NCM), null);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_NCM), TEST_CALLER_PKG,
+ null);
mLooper.dispatchAll();
verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_NCM);
}
@@ -860,7 +888,7 @@
private void prepareUsbTethering() {
// Emulate pressing the USB tethering button in Settings UI.
final TetheringRequestParcel request = createTetheringRequestParcel(TETHERING_USB);
- mTethering.startTethering(request, null);
+ mTethering.startTethering(request, TEST_CALLER_PKG, null);
mLooper.dispatchAll();
assertEquals(1, mTethering.getActiveTetheringRequests().size());
@@ -933,7 +961,7 @@
// Emulate externally-visible WifiManager effects, when hotspot mode
// is being torn down.
- sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_DISABLED);
+ sendWifiApStateChanged(WIFI_AP_STATE_DISABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_LOCAL_ONLY);
mTethering.interfaceRemoved(TEST_WLAN_IFNAME);
mLooper.dispatchAll();
@@ -1430,7 +1458,8 @@
when(mWifiManager.startTetheredHotspot(any(SoftApConfiguration.class))).thenReturn(true);
// Emulate pressing the WiFi tethering button.
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), null);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), TEST_CALLER_PKG,
+ null);
mLooper.dispatchAll();
verify(mWifiManager, times(1)).startTetheredHotspot(null);
verifyNoMoreInteractions(mWifiManager);
@@ -1457,7 +1486,8 @@
when(mWifiManager.startTetheredHotspot(any(SoftApConfiguration.class))).thenReturn(true);
// Emulate pressing the WiFi tethering button.
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), null);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), TEST_CALLER_PKG,
+ null);
mLooper.dispatchAll();
verify(mWifiManager, times(1)).startTetheredHotspot(null);
verifyNoMoreInteractions(mWifiManager);
@@ -1506,7 +1536,7 @@
// Emulate externally-visible WifiManager effects, when tethering mode
// is being torn down.
- sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_DISABLED);
+ sendWifiApStateChanged(WIFI_AP_STATE_DISABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
mTethering.interfaceRemoved(TEST_WLAN_IFNAME);
mLooper.dispatchAll();
@@ -1533,11 +1563,13 @@
doThrow(new RemoteException()).when(mNetd).ipfwdEnableForwarding(TETHERING_NAME);
// Emulate pressing the WiFi tethering button.
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), null);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), TEST_CALLER_PKG,
+ null);
mLooper.dispatchAll();
verify(mWifiManager, times(1)).startTetheredHotspot(null);
verifyNoMoreInteractions(mWifiManager);
verifyNoMoreInteractions(mNetd);
+ verify(mTetheringMetrics).createBuilder(eq(TETHERING_WIFI), anyString());
// Emulate externally-visible WifiManager effects, causing the
// per-interface state machine to start up, and telling us that
@@ -1576,6 +1608,10 @@
verify(mWifiManager).updateInterfaceIpState(
TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_CONFIGURATION_ERROR);
+ verify(mTetheringMetrics, times(2)).updateErrorCode(eq(TETHERING_WIFI),
+ eq(TETHER_ERROR_INTERNAL_ERROR));
+ verify(mTetheringMetrics, times(2)).sendReport(eq(TETHERING_WIFI));
+
verifyNoMoreInteractions(mWifiManager);
verifyNoMoreInteractions(mNetd);
}
@@ -1696,6 +1732,7 @@
private final ArrayList<TetherStatesParcel> mTetherStates = new ArrayList<>();
private final ArrayList<Integer> mOffloadStatus = new ArrayList<>();
private final ArrayList<List<TetheredClient>> mTetheredClients = new ArrayList<>();
+ private final ArrayList<Long> mSupportedBitmaps = new ArrayList<>();
// This function will remove the recorded callbacks, so it must be called once for
// each callback. If this is called after multiple callback, the order matters.
@@ -1748,6 +1785,10 @@
assertTrue(leases.containsAll(result));
}
+ public void expectSupportedTetheringTypes(Set<Integer> expectedTypes) {
+ assertEquals(expectedTypes, TetheringManager.unpackBits(mSupportedBitmaps.remove(0)));
+ }
+
@Override
public void onUpstreamChanged(Network network) {
mActualUpstreams.add(network);
@@ -1780,11 +1821,17 @@
mTetherStates.add(parcel.states);
mOffloadStatus.add(parcel.offloadStatus);
mTetheredClients.add(parcel.tetheredClients);
+ mSupportedBitmaps.add(parcel.supportedTypes);
}
@Override
public void onCallbackStopped(int errorCode) { }
+ @Override
+ public void onSupportedTetheringTypes(long supportedBitmap) {
+ mSupportedBitmaps.add(supportedBitmap);
+ }
+
public void assertNoUpstreamChangeCallback() {
assertTrue(mActualUpstreams.isEmpty());
}
@@ -1867,7 +1914,8 @@
tetherState = callback.pollTetherStatesChanged();
assertArrayEquals(tetherState.availableList, new TetheringInterface[] {wifiIface});
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), null);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), TEST_CALLER_PKG,
+ null);
sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
tetherState = callback.pollTetherStatesChanged();
assertArrayEquals(tetherState.tetheredList, new TetheringInterface[] {wifiIface});
@@ -1889,7 +1937,13 @@
mTethering.unregisterTetheringEventCallback(callback);
mLooper.dispatchAll();
mTethering.stopTethering(TETHERING_WIFI);
- sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_DISABLED);
+ sendWifiApStateChanged(WIFI_AP_STATE_DISABLED);
+ if (isAtLeastT()) {
+ // After T, tethering doesn't support WIFI_AP_STATE_DISABLED with null interface name.
+ callback2.assertNoStateChangeCallback();
+ sendWifiApStateChanged(WIFI_AP_STATE_DISABLED, TEST_WLAN_IFNAME,
+ IFACE_IP_MODE_TETHERED);
+ }
tetherState = callback2.pollTetherStatesChanged();
assertArrayEquals(tetherState.availableList, new TetheringInterface[] {wifiIface});
mLooper.dispatchAll();
@@ -1964,10 +2018,12 @@
public void testNoDuplicatedEthernetRequest() throws Exception {
final TetheredInterfaceRequest mockRequest = mock(TetheredInterfaceRequest.class);
when(mEm.requestTetheredInterface(any(), any())).thenReturn(mockRequest);
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_ETHERNET), null);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_ETHERNET), TEST_CALLER_PKG,
+ null);
mLooper.dispatchAll();
verify(mEm, times(1)).requestTetheredInterface(any(), any());
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_ETHERNET), null);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_ETHERNET), TEST_CALLER_PKG,
+ null);
mLooper.dispatchAll();
verifyNoMoreInteractions(mEm);
mTethering.stopTethering(TETHERING_ETHERNET);
@@ -2171,14 +2227,16 @@
final ResultListener thirdResult = new ResultListener(TETHER_ERROR_NO_ERROR);
// Enable USB tethering and check that Tethering starts USB.
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB), firstResult);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB), TEST_CALLER_PKG,
+ firstResult);
mLooper.dispatchAll();
firstResult.assertHasResult();
verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_RNDIS);
verifyNoMoreInteractions(mUsbManager);
// Enable USB tethering again with the same request and expect no change to USB.
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB), secondResult);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB), TEST_CALLER_PKG,
+ secondResult);
mLooper.dispatchAll();
secondResult.assertHasResult();
verify(mUsbManager, never()).setCurrentFunctions(UsbManager.FUNCTION_NONE);
@@ -2187,7 +2245,8 @@
// Enable USB tethering with a different request and expect that USB is stopped and
// started.
mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB,
- serverLinkAddr, clientLinkAddr, false, CONNECTIVITY_SCOPE_GLOBAL), thirdResult);
+ serverLinkAddr, clientLinkAddr, false, CONNECTIVITY_SCOPE_GLOBAL),
+ TEST_CALLER_PKG, thirdResult);
mLooper.dispatchAll();
thirdResult.assertHasResult();
verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_NONE);
@@ -2216,7 +2275,8 @@
final ArgumentCaptor<DhcpServingParamsParcel> dhcpParamsCaptor =
ArgumentCaptor.forClass(DhcpServingParamsParcel.class);
mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB,
- serverLinkAddr, clientLinkAddr, false, CONNECTIVITY_SCOPE_GLOBAL), null);
+ serverLinkAddr, clientLinkAddr, false, CONNECTIVITY_SCOPE_GLOBAL),
+ TEST_CALLER_PKG, null);
mLooper.dispatchAll();
verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_NCM);
mTethering.interfaceStatusChanged(TEST_NCM_IFNAME, true);
@@ -2284,7 +2344,7 @@
final TetheringRequestParcel wifiNotExemptRequest =
createTetheringRequestParcel(TETHERING_WIFI, null, null, false,
CONNECTIVITY_SCOPE_GLOBAL);
- mTethering.startTethering(wifiNotExemptRequest, null);
+ mTethering.startTethering(wifiNotExemptRequest, TEST_CALLER_PKG, null);
mLooper.dispatchAll();
verify(mEntitleMgr).startProvisioningIfNeeded(TETHERING_WIFI, false);
verify(mEntitleMgr, never()).setExemptedDownstreamType(TETHERING_WIFI);
@@ -2298,7 +2358,7 @@
final TetheringRequestParcel wifiExemptRequest =
createTetheringRequestParcel(TETHERING_WIFI, null, null, true,
CONNECTIVITY_SCOPE_GLOBAL);
- mTethering.startTethering(wifiExemptRequest, null);
+ mTethering.startTethering(wifiExemptRequest, TEST_CALLER_PKG, null);
mLooper.dispatchAll();
verify(mEntitleMgr, never()).startProvisioningIfNeeded(TETHERING_WIFI, false);
verify(mEntitleMgr).setExemptedDownstreamType(TETHERING_WIFI);
@@ -2311,14 +2371,14 @@
// If one app enables tethering without provisioning check first, then another app enables
// tethering of the same type but does not disable the provisioning check.
setupForRequiredProvisioning();
- mTethering.startTethering(wifiExemptRequest, null);
+ mTethering.startTethering(wifiExemptRequest, TEST_CALLER_PKG, null);
mLooper.dispatchAll();
verify(mEntitleMgr, never()).startProvisioningIfNeeded(TETHERING_WIFI, false);
verify(mEntitleMgr).setExemptedDownstreamType(TETHERING_WIFI);
assertTrue(mEntitleMgr.isCellularUpstreamPermitted());
reset(mEntitleMgr);
setupForRequiredProvisioning();
- mTethering.startTethering(wifiNotExemptRequest, null);
+ mTethering.startTethering(wifiNotExemptRequest, TEST_CALLER_PKG, null);
mLooper.dispatchAll();
verify(mEntitleMgr).startProvisioningIfNeeded(TETHERING_WIFI, false);
verify(mEntitleMgr, never()).setExemptedDownstreamType(TETHERING_WIFI);
@@ -2408,7 +2468,8 @@
when(mEm.requestTetheredInterface(any(), any())).thenReturn(mockRequest);
final ArgumentCaptor<TetheredInterfaceCallback> callbackCaptor =
ArgumentCaptor.forClass(TetheredInterfaceCallback.class);
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_ETHERNET), null);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_ETHERNET),
+ TEST_CALLER_PKG, null);
mLooper.dispatchAll();
verify(mEm).requestTetheredInterface(any(), callbackCaptor.capture());
TetheredInterfaceCallback ethCallback = callbackCaptor.getValue();
@@ -2490,12 +2551,11 @@
eventCallbacks = dhcpEventCbsCaptor.getValue();
// Update lease for local only tethering.
final MacAddress testMac1 = MacAddress.fromString("11:11:11:11:11:11");
- final ArrayList<DhcpLeaseParcelable> p2pLeases = new ArrayList<>();
- p2pLeases.add(createDhcpLeaseParcelable("clientId1", testMac1, "192.168.50.24", 24,
- Long.MAX_VALUE, "test1"));
- notifyDhcpLeasesChanged(p2pLeases, eventCallbacks);
- final List<TetheredClient> clients = toTetheredClients(p2pLeases, TETHERING_WIFI_P2P);
- callback.expectTetheredClientChanged(clients);
+ final DhcpLeaseParcelable p2pLease = createDhcpLeaseParcelable("clientId1", testMac1,
+ "192.168.50.24", 24, Long.MAX_VALUE, "test1");
+ final List<TetheredClient> p2pClients = notifyDhcpLeasesChanged(TETHERING_WIFI_P2P,
+ eventCallbacks, p2pLease);
+ callback.expectTetheredClientChanged(p2pClients);
reset(mDhcpServer);
// Run wifi tethering.
@@ -2505,25 +2565,20 @@
any(), dhcpEventCbsCaptor.capture());
eventCallbacks = dhcpEventCbsCaptor.getValue();
// Update mac address from softAp callback before getting dhcp lease.
- final ArrayList<WifiClient> wifiClients = new ArrayList<>();
final MacAddress testMac2 = MacAddress.fromString("22:22:22:22:22:22");
- final WifiClient testClient = mock(WifiClient.class);
- when(testClient.getMacAddress()).thenReturn(testMac2);
- wifiClients.add(testClient);
- mSoftApCallback.onConnectedClientsChanged(wifiClients);
- final TetheredClient noAddrClient = new TetheredClient(testMac2,
- Collections.emptyList() /* addresses */, TETHERING_WIFI);
- clients.add(noAddrClient);
- callback.expectTetheredClientChanged(clients);
+ final TetheredClient noAddrClient = notifyConnectedWifiClientsChanged(testMac2,
+ false /* isLocalOnly */);
+ final List<TetheredClient> p2pAndNoAddrClients = new ArrayList<>(p2pClients);
+ p2pAndNoAddrClients.add(noAddrClient);
+ callback.expectTetheredClientChanged(p2pAndNoAddrClients);
// Update dhcp lease for wifi tethering.
- clients.remove(noAddrClient);
- final ArrayList<DhcpLeaseParcelable> wifiLeases = new ArrayList<>();
- wifiLeases.add(createDhcpLeaseParcelable("clientId2", testMac2, "192.168.43.24", 24,
- Long.MAX_VALUE, "test2"));
- notifyDhcpLeasesChanged(wifiLeases, eventCallbacks);
- clients.addAll(toTetheredClients(wifiLeases, TETHERING_WIFI));
- callback.expectTetheredClientChanged(clients);
+ final DhcpLeaseParcelable wifiLease = createDhcpLeaseParcelable("clientId2", testMac2,
+ "192.168.43.24", 24, Long.MAX_VALUE, "test2");
+ final List<TetheredClient> p2pAndWifiClients = new ArrayList<>(p2pClients);
+ p2pAndWifiClients.addAll(notifyDhcpLeasesChanged(TETHERING_WIFI,
+ eventCallbacks, wifiLease));
+ callback.expectTetheredClientChanged(p2pAndWifiClients);
// Test onStarted callback that register second callback when tethering is running.
TestTetheringEventCallback callback2 = new TestTetheringEventCallback();
@@ -2531,18 +2586,74 @@
mTethering.registerTetheringEventCallback(callback2);
mLooper.dispatchAll();
});
- callback2.expectTetheredClientChanged(clients);
+ callback2.expectTetheredClientChanged(p2pAndWifiClients);
}
- private void notifyDhcpLeasesChanged(List<DhcpLeaseParcelable> leaseParcelables,
- IDhcpEventCallbacks callback) throws Exception {
- callback.onLeasesChanged(leaseParcelables);
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testUpdateConnectedClientsForLocalOnlyHotspot() throws Exception {
+ TestTetheringEventCallback callback = new TestTetheringEventCallback();
+ runAsShell(NETWORK_SETTINGS, () -> {
+ mTethering.registerTetheringEventCallback(callback);
+ mLooper.dispatchAll();
+ });
+ callback.expectTetheredClientChanged(Collections.emptyList());
+
+ // Run local only hotspot.
+ mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
+ sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_LOCAL_ONLY);
+
+ final ArgumentCaptor<IDhcpEventCallbacks> dhcpEventCbsCaptor =
+ ArgumentCaptor.forClass(IDhcpEventCallbacks.class);
+ verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS)).startWithCallbacks(
+ any(), dhcpEventCbsCaptor.capture());
+ final IDhcpEventCallbacks eventCallbacks = dhcpEventCbsCaptor.getValue();
+ // Update mac address from softAp callback before getting dhcp lease.
+ final MacAddress testMac = MacAddress.fromString("22:22:22:22:22:22");
+ final TetheredClient noAddrClient = notifyConnectedWifiClientsChanged(testMac,
+ true /* isLocalOnly */);
+ final List<TetheredClient> noAddrLocalOnlyClients = new ArrayList<>();
+ noAddrLocalOnlyClients.add(noAddrClient);
+ callback.expectTetheredClientChanged(noAddrLocalOnlyClients);
+
+ // Update dhcp lease for local only hotspot.
+ final DhcpLeaseParcelable wifiLease = createDhcpLeaseParcelable("clientId", testMac,
+ "192.168.43.24", 24, Long.MAX_VALUE, "test");
+ final List<TetheredClient> localOnlyClients = notifyDhcpLeasesChanged(TETHERING_WIFI,
+ eventCallbacks, wifiLease);
+ callback.expectTetheredClientChanged(localOnlyClients);
+
+ // Client disconnect from local only hotspot.
+ mLocalOnlyHotspotCallback.onConnectedClientsChanged(Collections.emptyList());
+ callback.expectTetheredClientChanged(Collections.emptyList());
+ }
+
+ private TetheredClient notifyConnectedWifiClientsChanged(final MacAddress mac,
+ boolean isLocalOnly) throws Exception {
+ final ArrayList<WifiClient> wifiClients = new ArrayList<>();
+ final WifiClient testClient = mock(WifiClient.class);
+ when(testClient.getMacAddress()).thenReturn(mac);
+ wifiClients.add(testClient);
+ if (isLocalOnly) {
+ mLocalOnlyHotspotCallback.onConnectedClientsChanged(wifiClients);
+ } else {
+ mSoftApCallback.onConnectedClientsChanged(wifiClients);
+ }
+ return new TetheredClient(mac, Collections.emptyList() /* addresses */, TETHERING_WIFI);
+ }
+
+ private List<TetheredClient> notifyDhcpLeasesChanged(int type, IDhcpEventCallbacks callback,
+ DhcpLeaseParcelable... leases) throws Exception {
+ final List<DhcpLeaseParcelable> dhcpLeases = Arrays.asList(leases);
+ callback.onLeasesChanged(dhcpLeases);
mLooper.dispatchAll();
+
+ return toTetheredClients(dhcpLeases, type);
}
private List<TetheredClient> toTetheredClients(List<DhcpLeaseParcelable> leaseParcelables,
int type) throws Exception {
- final ArrayList<TetheredClient> leases = new ArrayList<>();
+ final ArrayList<TetheredClient> clients = new ArrayList<>();
for (DhcpLeaseParcelable lease : leaseParcelables) {
final LinkAddress address = new LinkAddress(
intToInet4AddressHTH(lease.netAddr), lease.prefixLength,
@@ -2552,13 +2663,13 @@
final MacAddress macAddress = MacAddress.fromBytes(lease.hwAddr);
final AddressInfo addressInfo = new TetheredClient.AddressInfo(address, lease.hostname);
- leases.add(new TetheredClient(
+ clients.add(new TetheredClient(
macAddress,
Collections.singletonList(addressInfo),
type));
}
- return leases;
+ return clients;
}
private DhcpLeaseParcelable createDhcpLeaseParcelable(final String clientId,
@@ -2583,7 +2694,8 @@
final ResultListener result = new ResultListener(TETHER_ERROR_NO_ERROR);
mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */);
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH), result);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH),
+ TEST_CALLER_PKG, result);
mLooper.dispatchAll();
verifySetBluetoothTethering(true /* enable */, true /* bindToPanService */);
result.assertHasResult();
@@ -2618,7 +2730,8 @@
final ResultListener result = new ResultListener(TETHER_ERROR_NO_ERROR);
mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */);
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH), result);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH),
+ TEST_CALLER_PKG, result);
mLooper.dispatchAll();
verifySetBluetoothTethering(true /* enable */, true /* bindToPanService */);
result.assertHasResult();
@@ -2639,7 +2752,8 @@
// already bound.
mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */);
final ResultListener secondResult = new ResultListener(TETHER_ERROR_NO_ERROR);
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH), secondResult);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH),
+ TEST_CALLER_PKG, secondResult);
mLooper.dispatchAll();
verifySetBluetoothTethering(true /* enable */, false /* bindToPanService */);
secondResult.assertHasResult();
@@ -2660,7 +2774,8 @@
public void testBluetoothServiceDisconnects() throws Exception {
final ResultListener result = new ResultListener(TETHER_ERROR_NO_ERROR);
mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */);
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH), result);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH),
+ TEST_CALLER_PKG, result);
mLooper.dispatchAll();
ServiceListener panListener = verifySetBluetoothTethering(true /* enable */,
true /* bindToPanService */);
@@ -2811,18 +2926,26 @@
runNcmTethering();
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
any(), any());
+ verify(mTetheringMetrics).createBuilder(eq(TETHERING_NCM), anyString());
// Change the USB tethering function to NCM. Because the USB tethering function was set to
// RNDIS (the default), tethering is stopped.
forceUsbTetheringUse(TETHER_USB_NCM_FUNCTION);
verifyUsbTetheringStopDueToSettingChange(TEST_NCM_IFNAME);
+ verify(mTetheringMetrics).updateErrorCode(anyInt(), eq(TETHER_ERROR_NO_ERROR));
+ verify(mTetheringMetrics).sendReport(eq(TETHERING_NCM));
// If TETHERING_USB is forced to use ncm function, TETHERING_NCM would no longer be
// available.
final ResultListener ncmResult = new ResultListener(TETHER_ERROR_SERVICE_UNAVAIL);
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_NCM), ncmResult);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_NCM), TEST_CALLER_PKG,
+ ncmResult);
mLooper.dispatchAll();
ncmResult.assertHasResult();
+ verify(mTetheringMetrics, times(2)).createBuilder(eq(TETHERING_NCM), anyString());
+ verify(mTetheringMetrics).updateErrorCode(eq(TETHERING_NCM),
+ eq(TETHER_ERROR_SERVICE_UNAVAIL));
+ verify(mTetheringMetrics, times(2)).sendReport(eq(TETHERING_NCM));
// Run TETHERING_USB with ncm configuration.
runDualStackUsbTethering(TEST_NCM_IFNAME);
@@ -2836,53 +2959,81 @@
runStopUSBTethering();
}
+ public static ArraySet<Integer> getAllSupportedTetheringTypes() {
+ return new ArraySet<>(new Integer[] { TETHERING_USB, TETHERING_NCM, TETHERING_WIFI,
+ TETHERING_WIFI_P2P, TETHERING_BLUETOOTH, TETHERING_ETHERNET });
+ }
+
@Test
public void testTetheringSupported() throws Exception {
+ final ArraySet<Integer> expectedTypes = getAllSupportedTetheringTypes();
+ // Check tethering is supported after initialization.
setTetheringSupported(true /* supported */);
- updateConfigAndVerifySupported(true /* supported */);
+ TestTetheringEventCallback callback = new TestTetheringEventCallback();
+ mTethering.registerTetheringEventCallback(callback);
+ mLooper.dispatchAll();
+ updateConfigAndVerifySupported(callback, expectedTypes);
// Could disable tethering supported by settings.
Settings.Global.putInt(mContentResolver, Settings.Global.TETHER_SUPPORTED, 0);
- updateConfigAndVerifySupported(false /* supported */);
+ updateConfigAndVerifySupported(callback, new ArraySet<>());
// Could disable tethering supported by user restriction.
setTetheringSupported(true /* supported */);
+ updateConfigAndVerifySupported(callback, expectedTypes);
when(mUserManager.hasUserRestriction(
UserManager.DISALLOW_CONFIG_TETHERING)).thenReturn(true);
- updateConfigAndVerifySupported(false /* supported */);
+ updateConfigAndVerifySupported(callback, new ArraySet<>());
// Tethering is supported if it has any supported downstream.
setTetheringSupported(true /* supported */);
+ updateConfigAndVerifySupported(callback, expectedTypes);
+ // Usb tethering is not supported:
+ expectedTypes.remove(TETHERING_USB);
when(mResources.getStringArray(R.array.config_tether_usb_regexs))
.thenReturn(new String[0]);
- updateConfigAndVerifySupported(true /* supported */);
+ updateConfigAndVerifySupported(callback, expectedTypes);
+ // Wifi tethering is not supported:
+ expectedTypes.remove(TETHERING_WIFI);
when(mResources.getStringArray(R.array.config_tether_wifi_regexs))
.thenReturn(new String[0]);
- updateConfigAndVerifySupported(true /* supported */);
-
+ updateConfigAndVerifySupported(callback, expectedTypes);
+ // Bluetooth tethering is not supported:
+ expectedTypes.remove(TETHERING_BLUETOOTH);
+ when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs))
+ .thenReturn(new String[0]);
if (isAtLeastT()) {
- when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs))
- .thenReturn(new String[0]);
- updateConfigAndVerifySupported(true /* supported */);
+ updateConfigAndVerifySupported(callback, expectedTypes);
+
+ // P2p tethering is not supported:
+ expectedTypes.remove(TETHERING_WIFI_P2P);
when(mResources.getStringArray(R.array.config_tether_wifi_p2p_regexs))
.thenReturn(new String[0]);
- updateConfigAndVerifySupported(true /* supported */);
+ updateConfigAndVerifySupported(callback, expectedTypes);
+ // Ncm tethering is not supported:
+ expectedTypes.remove(TETHERING_NCM);
when(mResources.getStringArray(R.array.config_tether_ncm_regexs))
.thenReturn(new String[0]);
- updateConfigAndVerifySupported(true /* supported */);
+ updateConfigAndVerifySupported(callback, expectedTypes);
+ // Ethernet tethering (last supported type) is not supported:
+ expectedTypes.remove(TETHERING_ETHERNET);
mForceEthernetServiceUnavailable = true;
- updateConfigAndVerifySupported(false /* supported */);
+ updateConfigAndVerifySupported(callback, new ArraySet<>());
+
} else {
- when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs))
- .thenReturn(new String[0]);
- updateConfigAndVerifySupported(false /* supported */);
+ // If wifi, usb and bluetooth are all not supported, all the types are not supported.
+ expectedTypes.clear();
+ updateConfigAndVerifySupported(callback, expectedTypes);
}
}
- private void updateConfigAndVerifySupported(boolean supported) {
+ private void updateConfigAndVerifySupported(final TestTetheringEventCallback callback,
+ final ArraySet<Integer> expectedTypes) {
sendConfigurationChanged();
- assertEquals(supported, mTethering.isTetheringSupported());
+
+ assertEquals(expectedTypes.size() > 0, mTethering.isTetheringSupported());
+ callback.expectSupportedTetheringTypes(expectedTypes);
}
// TODO: Test that a request for hotspot mode doesn't interfere with an
// already operating tethering mode interface.
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java
new file mode 100644
index 0000000..c34cf5f
--- /dev/null
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.tethering.metrics;
+
+import static android.net.TetheringManager.TETHERING_BLUETOOTH;
+import static android.net.TetheringManager.TETHERING_ETHERNET;
+import static android.net.TetheringManager.TETHERING_NCM;
+import static android.net.TetheringManager.TETHERING_USB;
+import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.net.TetheringManager.TETHERING_WIFI_P2P;
+import static android.net.TetheringManager.TETHER_ERROR_DHCPSERVER_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_DISABLE_FORWARDING_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_ENABLE_FORWARDING_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN;
+import static android.net.TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_INTERNAL_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION;
+import static android.net.TetheringManager.TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION;
+import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_PROVISIONING_FAILED;
+import static android.net.TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL;
+import static android.net.TetheringManager.TETHER_ERROR_TETHER_IFACE_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_UNAVAIL_IFACE;
+import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_IFACE;
+import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_TYPE;
+import static android.net.TetheringManager.TETHER_ERROR_UNSUPPORTED;
+import static android.net.TetheringManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
+
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.stats.connectivity.DownstreamType;
+import android.stats.connectivity.ErrorCode;
+import android.stats.connectivity.UpstreamType;
+import android.stats.connectivity.UserType;
+import android.util.Pair;
+
+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.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class TetheringMetricsTest {
+ private static final String TEST_CALLER_PKG = "com.test.caller.pkg";
+ private static final String SETTINGS_PKG = "com.android.settings";
+ private static final String SYSTEMUI_PKG = "com.android.systemui";
+ private static final String GMS_PKG = "com.google.android.gms";
+ private TetheringMetrics mTetheringMetrics;
+
+ private final NetworkTetheringReported.Builder mStatsBuilder =
+ NetworkTetheringReported.newBuilder();
+
+ private class MockTetheringMetrics extends TetheringMetrics {
+ @Override
+ public void write(final NetworkTetheringReported reported) { }
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mTetheringMetrics = spy(new MockTetheringMetrics());
+ }
+
+ private void runDownstreamTypesTest(final Pair<Integer, DownstreamType>... testPairs)
+ throws Exception {
+ for (Pair<Integer, DownstreamType> testPair : testPairs) {
+ final int type = testPair.first;
+ final DownstreamType expectedResult = testPair.second;
+
+ mTetheringMetrics.createBuilder(type, TEST_CALLER_PKG);
+ mTetheringMetrics.updateErrorCode(type, TETHER_ERROR_NO_ERROR);
+ mTetheringMetrics.sendReport(type);
+ NetworkTetheringReported expectedReport =
+ mStatsBuilder.setDownstreamType(expectedResult)
+ .setUserType(UserType.USER_UNKNOWN)
+ .setUpstreamType(UpstreamType.UT_UNKNOWN)
+ .setErrorCode(ErrorCode.EC_NO_ERROR)
+ .build();
+ verify(mTetheringMetrics).write(expectedReport);
+ reset(mTetheringMetrics);
+ }
+ }
+
+ @Test
+ public void testDownstreamTypes() throws Exception {
+ runDownstreamTypesTest(new Pair<>(TETHERING_WIFI, DownstreamType.DS_TETHERING_WIFI),
+ new Pair<>(TETHERING_WIFI_P2P, DownstreamType.DS_TETHERING_WIFI_P2P),
+ new Pair<>(TETHERING_BLUETOOTH, DownstreamType.DS_TETHERING_BLUETOOTH),
+ new Pair<>(TETHERING_USB, DownstreamType.DS_TETHERING_USB),
+ new Pair<>(TETHERING_NCM, DownstreamType.DS_TETHERING_NCM),
+ new Pair<>(TETHERING_ETHERNET, DownstreamType.DS_TETHERING_ETHERNET));
+ }
+
+ private void runErrorCodesTest(final Pair<Integer, ErrorCode>... testPairs)
+ throws Exception {
+ for (Pair<Integer, ErrorCode> testPair : testPairs) {
+ final int errorCode = testPair.first;
+ final ErrorCode expectedResult = testPair.second;
+
+ mTetheringMetrics.createBuilder(TETHERING_WIFI, TEST_CALLER_PKG);
+ mTetheringMetrics.updateErrorCode(TETHERING_WIFI, errorCode);
+ mTetheringMetrics.sendReport(TETHERING_WIFI);
+ NetworkTetheringReported expectedReport =
+ mStatsBuilder.setDownstreamType(DownstreamType.DS_TETHERING_WIFI)
+ .setUserType(UserType.USER_UNKNOWN)
+ .setUpstreamType(UpstreamType.UT_UNKNOWN)
+ .setErrorCode(expectedResult)
+ .build();
+ verify(mTetheringMetrics).write(expectedReport);
+ reset(mTetheringMetrics);
+ }
+ }
+
+ @Test
+ public void testErrorCodes() throws Exception {
+ runErrorCodesTest(new Pair<>(TETHER_ERROR_NO_ERROR, ErrorCode.EC_NO_ERROR),
+ new Pair<>(TETHER_ERROR_UNKNOWN_IFACE, ErrorCode.EC_UNKNOWN_IFACE),
+ new Pair<>(TETHER_ERROR_SERVICE_UNAVAIL, ErrorCode.EC_SERVICE_UNAVAIL),
+ new Pair<>(TETHER_ERROR_UNSUPPORTED, ErrorCode.EC_UNSUPPORTED),
+ new Pair<>(TETHER_ERROR_UNAVAIL_IFACE, ErrorCode.EC_UNAVAIL_IFACE),
+ new Pair<>(TETHER_ERROR_INTERNAL_ERROR, ErrorCode.EC_INTERNAL_ERROR),
+ new Pair<>(TETHER_ERROR_TETHER_IFACE_ERROR, ErrorCode.EC_TETHER_IFACE_ERROR),
+ new Pair<>(TETHER_ERROR_UNTETHER_IFACE_ERROR, ErrorCode.EC_UNTETHER_IFACE_ERROR),
+ new Pair<>(TETHER_ERROR_ENABLE_FORWARDING_ERROR,
+ ErrorCode.EC_ENABLE_FORWARDING_ERROR),
+ new Pair<>(TETHER_ERROR_DISABLE_FORWARDING_ERROR,
+ ErrorCode.EC_DISABLE_FORWARDING_ERROR),
+ new Pair<>(TETHER_ERROR_IFACE_CFG_ERROR, ErrorCode.EC_IFACE_CFG_ERROR),
+ new Pair<>(TETHER_ERROR_PROVISIONING_FAILED, ErrorCode.EC_PROVISIONING_FAILED),
+ new Pair<>(TETHER_ERROR_DHCPSERVER_ERROR, ErrorCode.EC_DHCPSERVER_ERROR),
+ new Pair<>(TETHER_ERROR_ENTITLEMENT_UNKNOWN, ErrorCode.EC_ENTITLEMENT_UNKNOWN),
+ new Pair<>(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION,
+ ErrorCode.EC_NO_CHANGE_TETHERING_PERMISSION),
+ new Pair<>(TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION,
+ ErrorCode.EC_NO_ACCESS_TETHERING_PERMISSION),
+ new Pair<>(TETHER_ERROR_UNKNOWN_TYPE, ErrorCode.EC_UNKNOWN_TYPE));
+ }
+
+ private void runUserTypesTest(final Pair<String, UserType>... testPairs)
+ throws Exception {
+ for (Pair<String, UserType> testPair : testPairs) {
+ final String callerPkg = testPair.first;
+ final UserType expectedResult = testPair.second;
+
+ mTetheringMetrics.createBuilder(TETHERING_WIFI, callerPkg);
+ mTetheringMetrics.updateErrorCode(TETHERING_WIFI, TETHER_ERROR_NO_ERROR);
+ mTetheringMetrics.sendReport(TETHERING_WIFI);
+ NetworkTetheringReported expectedReport =
+ mStatsBuilder.setDownstreamType(DownstreamType.DS_TETHERING_WIFI)
+ .setUserType(expectedResult)
+ .setUpstreamType(UpstreamType.UT_UNKNOWN)
+ .setErrorCode(ErrorCode.EC_NO_ERROR)
+ .build();
+ verify(mTetheringMetrics).write(expectedReport);
+ reset(mTetheringMetrics);
+ }
+ }
+
+ @Test
+ public void testUserTypes() throws Exception {
+ runUserTypesTest(new Pair<>(TEST_CALLER_PKG, UserType.USER_UNKNOWN),
+ new Pair<>(SETTINGS_PKG, UserType.USER_SETTINGS),
+ new Pair<>(SYSTEMUI_PKG, UserType.USER_SYSTEMUI),
+ new Pair<>(GMS_PKG, UserType.USER_GMS));
+ }
+}
diff --git a/bpf_progs/Android.bp b/bpf_progs/Android.bp
index 6718402..23af3e3 100644
--- a/bpf_progs/Android.bp
+++ b/bpf_progs/Android.bp
@@ -47,6 +47,7 @@
"//packages/modules/Connectivity/service/native/libs/libclat",
"//packages/modules/Connectivity/Tethering",
"//packages/modules/Connectivity/service/native",
+ "//packages/modules/Connectivity/tests/native",
"//packages/modules/Connectivity/service-t/native/libs/libnetworkstats",
"//packages/modules/Connectivity/tests/unit/jni",
"//system/netd/server",
@@ -58,8 +59,20 @@
// bpf kernel programs
//
bpf {
+ name: "block.o",
+ srcs: ["block.c"],
+ btf: true,
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ sub_dir: "net_shared",
+}
+
+bpf {
name: "dscp_policy.o",
srcs: ["dscp_policy.c"],
+ btf: true,
cflags: [
"-Wall",
"-Werror",
@@ -86,8 +99,9 @@
}
bpf {
- name: "clatd.o_mainline",
+ name: "clatd.o",
srcs: ["clatd.c"],
+ btf: true,
cflags: [
"-Wall",
"-Werror",
@@ -99,8 +113,9 @@
}
bpf {
- name: "netd.o_mainline",
+ name: "netd.o",
srcs: ["netd.c"],
+ btf: true,
cflags: [
"-Wall",
"-Werror",
@@ -108,5 +123,5 @@
include_dirs: [
"frameworks/libs/net/common/netd/libnetdutils/include",
],
- sub_dir: "net_shared",
+ sub_dir: "netd_shared",
}
diff --git a/bpf_progs/block.c b/bpf_progs/block.c
new file mode 100644
index 0000000..f2a3e62
--- /dev/null
+++ b/bpf_progs/block.c
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <linux/types.h>
+#include <linux/bpf.h>
+#include <netinet/in.h>
+#include <stdint.h>
+
+// The resulting .o needs to load on the Android T beta 3 bpfloader
+#define BPFLOADER_MIN_VER BPFLOADER_T_BETA3_VERSION
+
+#include "bpf_helpers.h"
+
+#define ALLOW 1
+#define DISALLOW 0
+
+DEFINE_BPF_MAP_GRW(blocked_ports_map, ARRAY, int, uint64_t,
+ 1024 /* 64K ports -> 1024 u64s */, AID_SYSTEM)
+
+static inline __always_inline int block_port(struct bpf_sock_addr *ctx) {
+ if (!ctx->user_port) return ALLOW;
+
+ switch (ctx->protocol) {
+ case IPPROTO_TCP:
+ case IPPROTO_MPTCP:
+ case IPPROTO_UDP:
+ case IPPROTO_UDPLITE:
+ case IPPROTO_DCCP:
+ case IPPROTO_SCTP:
+ break;
+ default:
+ return ALLOW; // unknown protocols are allowed
+ }
+
+ int key = ctx->user_port >> 6;
+ int shift = ctx->user_port & 63;
+
+ uint64_t *val = bpf_blocked_ports_map_lookup_elem(&key);
+ // Lookup should never fail in reality, but if it does return here to keep the
+ // BPF verifier happy.
+ if (!val) return ALLOW;
+
+ if ((*val >> shift) & 1) return DISALLOW;
+ return ALLOW;
+}
+
+DEFINE_BPF_PROG_KVER("bind4/block_port", AID_ROOT, AID_SYSTEM,
+ bind4_block_port, KVER(5, 4, 0))
+(struct bpf_sock_addr *ctx) {
+ return block_port(ctx);
+}
+
+DEFINE_BPF_PROG_KVER("bind6/block_port", AID_ROOT, AID_SYSTEM,
+ bind6_block_port, KVER(5, 4, 0))
+(struct bpf_sock_addr *ctx) {
+ return block_port(ctx);
+}
+
+LICENSE("Apache 2.0");
+CRITICAL("ConnectivityNative");
diff --git a/bpf_progs/bpf_net_helpers.h b/bpf_progs/bpf_net_helpers.h
index c798580..e382713 100644
--- a/bpf_progs/bpf_net_helpers.h
+++ b/bpf_progs/bpf_net_helpers.h
@@ -65,8 +65,9 @@
skb->pkt_type == PACKET_MULTICAST;
}
-// try to make the first 'len' header bytes readable via direct packet access
-static inline __always_inline void try_make_readable(struct __sk_buff* skb, int len) {
+// try to make the first 'len' header bytes readable/writable via direct packet access
+// (note: AFAIK there is no way to ask for only direct packet read without also getting write)
+static inline __always_inline void try_make_writable(struct __sk_buff* skb, int len) {
if (len > skb->len) len = skb->len;
if (skb->data_end - skb->data < len) bpf_skb_pull_data(skb, len);
}
diff --git a/bpf_progs/bpf_shared.h b/bpf_progs/bpf_shared.h
index 2ddc7b8..2afb789 100644
--- a/bpf_progs/bpf_shared.h
+++ b/bpf_progs/bpf_shared.h
@@ -98,29 +98,29 @@
static const int CONFIGURATION_MAP_SIZE = 2;
static const int UID_OWNER_MAP_SIZE = 2000;
-#define BPF_PATH "/sys/fs/bpf/"
+#define BPF_NETD_PATH "/sys/fs/bpf/netd_shared/"
-#define BPF_EGRESS_PROG_PATH BPF_PATH "prog_netd_cgroupskb_egress_stats"
-#define BPF_INGRESS_PROG_PATH BPF_PATH "prog_netd_cgroupskb_ingress_stats"
-#define XT_BPF_INGRESS_PROG_PATH BPF_PATH "prog_netd_skfilter_ingress_xtbpf"
-#define XT_BPF_EGRESS_PROG_PATH BPF_PATH "prog_netd_skfilter_egress_xtbpf"
-#define XT_BPF_ALLOWLIST_PROG_PATH BPF_PATH "prog_netd_skfilter_allowlist_xtbpf"
-#define XT_BPF_DENYLIST_PROG_PATH BPF_PATH "prog_netd_skfilter_denylist_xtbpf"
-#define CGROUP_SOCKET_PROG_PATH BPF_PATH "prog_netd_cgroupsock_inet_create"
+#define BPF_EGRESS_PROG_PATH BPF_NETD_PATH "prog_netd_cgroupskb_egress_stats"
+#define BPF_INGRESS_PROG_PATH BPF_NETD_PATH "prog_netd_cgroupskb_ingress_stats"
+#define XT_BPF_INGRESS_PROG_PATH BPF_NETD_PATH "prog_netd_skfilter_ingress_xtbpf"
+#define XT_BPF_EGRESS_PROG_PATH BPF_NETD_PATH "prog_netd_skfilter_egress_xtbpf"
+#define XT_BPF_ALLOWLIST_PROG_PATH BPF_NETD_PATH "prog_netd_skfilter_allowlist_xtbpf"
+#define XT_BPF_DENYLIST_PROG_PATH BPF_NETD_PATH "prog_netd_skfilter_denylist_xtbpf"
+#define CGROUP_SOCKET_PROG_PATH BPF_NETD_PATH "prog_netd_cgroupsock_inet_create"
#define TC_BPF_INGRESS_ACCOUNT_PROG_NAME "prog_netd_schedact_ingress_account"
-#define TC_BPF_INGRESS_ACCOUNT_PROG_PATH BPF_PATH TC_BPF_INGRESS_ACCOUNT_PROG_NAME
+#define TC_BPF_INGRESS_ACCOUNT_PROG_PATH BPF_NETD_PATH TC_BPF_INGRESS_ACCOUNT_PROG_NAME
-#define COOKIE_TAG_MAP_PATH BPF_PATH "map_netd_cookie_tag_map"
-#define UID_COUNTERSET_MAP_PATH BPF_PATH "map_netd_uid_counterset_map"
-#define APP_UID_STATS_MAP_PATH BPF_PATH "map_netd_app_uid_stats_map"
-#define STATS_MAP_A_PATH BPF_PATH "map_netd_stats_map_A"
-#define STATS_MAP_B_PATH BPF_PATH "map_netd_stats_map_B"
-#define IFACE_INDEX_NAME_MAP_PATH BPF_PATH "map_netd_iface_index_name_map"
-#define IFACE_STATS_MAP_PATH BPF_PATH "map_netd_iface_stats_map"
-#define CONFIGURATION_MAP_PATH BPF_PATH "map_netd_configuration_map"
-#define UID_OWNER_MAP_PATH BPF_PATH "map_netd_uid_owner_map"
-#define UID_PERMISSION_MAP_PATH BPF_PATH "map_netd_uid_permission_map"
+#define COOKIE_TAG_MAP_PATH BPF_NETD_PATH "map_netd_cookie_tag_map"
+#define UID_COUNTERSET_MAP_PATH BPF_NETD_PATH "map_netd_uid_counterset_map"
+#define APP_UID_STATS_MAP_PATH BPF_NETD_PATH "map_netd_app_uid_stats_map"
+#define STATS_MAP_A_PATH BPF_NETD_PATH "map_netd_stats_map_A"
+#define STATS_MAP_B_PATH BPF_NETD_PATH "map_netd_stats_map_B"
+#define IFACE_INDEX_NAME_MAP_PATH BPF_NETD_PATH "map_netd_iface_index_name_map"
+#define IFACE_STATS_MAP_PATH BPF_NETD_PATH "map_netd_iface_stats_map"
+#define CONFIGURATION_MAP_PATH BPF_NETD_PATH "map_netd_configuration_map"
+#define UID_OWNER_MAP_PATH BPF_NETD_PATH "map_netd_uid_owner_map"
+#define UID_PERMISSION_MAP_PATH BPF_NETD_PATH "map_netd_uid_permission_map"
enum UidOwnerMatchType {
NO_MATCH = 0,
@@ -132,6 +132,9 @@
RESTRICTED_MATCH = (1 << 5),
LOW_POWER_STANDBY_MATCH = (1 << 6),
IIF_MATCH = (1 << 7),
+ LOCKDOWN_VPN_MATCH = (1 << 8),
+ OEM_DENY_1_MATCH = (1 << 9),
+ OEM_DENY_2_MATCH = (1 << 10),
};
enum BpfPermissionMatch {
@@ -146,9 +149,9 @@
SELECT_MAP_B,
};
-// TODO: change the configuration object from an 8-bit bitmask to an object with clearer
+// TODO: change the configuration object from a bitmask to an object with clearer
// semantics, like a struct.
-typedef uint8_t BpfConfig;
+typedef uint32_t BpfConfig;
static const BpfConfig DEFAULT_CONFIG = 0;
typedef struct {
@@ -159,16 +162,20 @@
} UidOwnerValue;
STRUCT_SIZE(UidOwnerValue, 2 * 4); // 8
+// Entry in the configuration map that stores which UID rules are enabled.
#define UID_RULES_CONFIGURATION_KEY 1
+// Entry in the configuration map that stores which stats map is currently in use.
#define CURRENT_STATS_MAP_CONFIGURATION_KEY 2
+#define BPF_CLATD_PATH "/sys/fs/bpf/net_shared/"
+
#define CLAT_INGRESS6_PROG_RAWIP_NAME "prog_clatd_schedcls_ingress6_clat_rawip"
#define CLAT_INGRESS6_PROG_ETHER_NAME "prog_clatd_schedcls_ingress6_clat_ether"
-#define CLAT_INGRESS6_PROG_RAWIP_PATH BPF_PATH CLAT_INGRESS6_PROG_RAWIP_NAME
-#define CLAT_INGRESS6_PROG_ETHER_PATH BPF_PATH CLAT_INGRESS6_PROG_ETHER_NAME
+#define CLAT_INGRESS6_PROG_RAWIP_PATH BPF_CLATD_PATH CLAT_INGRESS6_PROG_RAWIP_NAME
+#define CLAT_INGRESS6_PROG_ETHER_PATH BPF_CLATD_PATH CLAT_INGRESS6_PROG_ETHER_NAME
-#define CLAT_INGRESS6_MAP_PATH BPF_PATH "map_clatd_clat_ingress6_map"
+#define CLAT_INGRESS6_MAP_PATH BPF_CLATD_PATH "map_clatd_clat_ingress6_map"
typedef struct {
uint32_t iif; // The input interface index
@@ -186,10 +193,10 @@
#define CLAT_EGRESS4_PROG_RAWIP_NAME "prog_clatd_schedcls_egress4_clat_rawip"
#define CLAT_EGRESS4_PROG_ETHER_NAME "prog_clatd_schedcls_egress4_clat_ether"
-#define CLAT_EGRESS4_PROG_RAWIP_PATH BPF_PATH CLAT_EGRESS4_PROG_RAWIP_NAME
-#define CLAT_EGRESS4_PROG_ETHER_PATH BPF_PATH CLAT_EGRESS4_PROG_ETHER_NAME
+#define CLAT_EGRESS4_PROG_RAWIP_PATH BPF_CLATD_PATH CLAT_EGRESS4_PROG_RAWIP_NAME
+#define CLAT_EGRESS4_PROG_ETHER_PATH BPF_CLATD_PATH CLAT_EGRESS4_PROG_ETHER_NAME
-#define CLAT_EGRESS4_MAP_PATH BPF_PATH "map_clatd_clat_egress4_map"
+#define CLAT_EGRESS4_MAP_PATH BPF_CLATD_PATH "map_clatd_clat_egress4_map"
typedef struct {
uint32_t iif; // The input interface index
diff --git a/bpf_progs/bpf_tethering.h b/bpf_progs/bpf_tethering.h
index b0ec8f6..f9ef6ef 100644
--- a/bpf_progs/bpf_tethering.h
+++ b/bpf_progs/bpf_tethering.h
@@ -73,10 +73,6 @@
#define STRUCT_SIZE(name, size) _Static_assert(sizeof(name) == (size), "Incorrect struct size.")
-#define BPF_PATH_TETHER BPF_PATH "tethering/"
-
-#define TETHER_STATS_MAP_PATH BPF_PATH_TETHER "map_offload_tether_stats_map"
-
typedef uint32_t TetherStatsKey; // upstream ifindex
typedef struct {
@@ -89,19 +85,9 @@
} TetherStatsValue;
STRUCT_SIZE(TetherStatsValue, 6 * 8); // 48
-#define TETHER_LIMIT_MAP_PATH BPF_PATH_TETHER "map_offload_tether_limit_map"
-
typedef uint32_t TetherLimitKey; // upstream ifindex
typedef uint64_t TetherLimitValue; // in bytes
-#define TETHER_DOWNSTREAM6_TC_PROG_RAWIP_NAME "prog_offload_schedcls_tether_downstream6_rawip"
-#define TETHER_DOWNSTREAM6_TC_PROG_ETHER_NAME "prog_offload_schedcls_tether_downstream6_ether"
-
-#define TETHER_DOWNSTREAM6_TC_PROG_RAWIP_PATH BPF_PATH_TETHER TETHER_DOWNSTREAM6_TC_PROG_RAWIP_NAME
-#define TETHER_DOWNSTREAM6_TC_PROG_ETHER_PATH BPF_PATH_TETHER TETHER_DOWNSTREAM6_TC_PROG_ETHER_NAME
-
-#define TETHER_DOWNSTREAM6_MAP_PATH BPF_PATH_TETHER "map_offload_tether_downstream6_map"
-
// For now tethering offload only needs to support downstreams that use 6-byte MAC addresses,
// because all downstream types that are currently supported (WiFi, USB, Bluetooth and
// Ethernet) have 6-byte MAC addresses.
@@ -121,8 +107,6 @@
} Tether6Value;
STRUCT_SIZE(Tether6Value, 4 + 14 + 2); // 20
-#define TETHER_DOWNSTREAM64_MAP_PATH BPF_PATH_TETHER "map_offload_tether_downstream64_map"
-
typedef struct {
uint32_t iif; // The input interface index
uint8_t dstMac[ETH_ALEN]; // destination ethernet mac address (zeroed iff rawip ingress)
@@ -146,14 +130,6 @@
} TetherDownstream64Value;
STRUCT_SIZE(TetherDownstream64Value, 4 + 14 + 2 + 4 + 4 + 2 + 2 + 8); // 40
-#define TETHER_UPSTREAM6_TC_PROG_RAWIP_NAME "prog_offload_schedcls_tether_upstream6_rawip"
-#define TETHER_UPSTREAM6_TC_PROG_ETHER_NAME "prog_offload_schedcls_tether_upstream6_ether"
-
-#define TETHER_UPSTREAM6_TC_PROG_RAWIP_PATH BPF_PATH_TETHER TETHER_UPSTREAM6_TC_PROG_RAWIP_NAME
-#define TETHER_UPSTREAM6_TC_PROG_ETHER_PATH BPF_PATH_TETHER TETHER_UPSTREAM6_TC_PROG_ETHER_NAME
-
-#define TETHER_UPSTREAM6_MAP_PATH BPF_PATH_TETHER "map_offload_tether_upstream6_map"
-
typedef struct {
uint32_t iif; // The input interface index
uint8_t dstMac[ETH_ALEN]; // destination ethernet mac address (zeroed iff rawip ingress)
@@ -162,23 +138,6 @@
} TetherUpstream6Key;
STRUCT_SIZE(TetherUpstream6Key, 12);
-#define TETHER_DOWNSTREAM4_TC_PROG_RAWIP_NAME "prog_offload_schedcls_tether_downstream4_rawip"
-#define TETHER_DOWNSTREAM4_TC_PROG_ETHER_NAME "prog_offload_schedcls_tether_downstream4_ether"
-
-#define TETHER_DOWNSTREAM4_TC_PROG_RAWIP_PATH BPF_PATH_TETHER TETHER_DOWNSTREAM4_TC_PROG_RAWIP_NAME
-#define TETHER_DOWNSTREAM4_TC_PROG_ETHER_PATH BPF_PATH_TETHER TETHER_DOWNSTREAM4_TC_PROG_ETHER_NAME
-
-#define TETHER_DOWNSTREAM4_MAP_PATH BPF_PATH_TETHER "map_offload_tether_downstream4_map"
-
-
-#define TETHER_UPSTREAM4_TC_PROG_RAWIP_NAME "prog_offload_schedcls_tether_upstream4_rawip"
-#define TETHER_UPSTREAM4_TC_PROG_ETHER_NAME "prog_offload_schedcls_tether_upstream4_ether"
-
-#define TETHER_UPSTREAM4_TC_PROG_RAWIP_PATH BPF_PATH_TETHER TETHER_UPSTREAM4_TC_PROG_RAWIP_NAME
-#define TETHER_UPSTREAM4_TC_PROG_ETHER_PATH BPF_PATH_TETHER TETHER_UPSTREAM4_TC_PROG_ETHER_NAME
-
-#define TETHER_UPSTREAM4_MAP_PATH BPF_PATH_TETHER "map_offload_tether_upstream4_map"
-
typedef struct {
uint32_t iif; // The input interface index
uint8_t dstMac[ETH_ALEN]; // destination ethernet mac address (zeroed iff rawip ingress)
@@ -202,16 +161,4 @@
} Tether4Value;
STRUCT_SIZE(Tether4Value, 4 + 14 + 2 + 16 + 16 + 2 + 2 + 8); // 64
-#define TETHER_DOWNSTREAM_XDP_PROG_RAWIP_NAME "prog_offload_xdp_tether_downstream_rawip"
-#define TETHER_DOWNSTREAM_XDP_PROG_ETHER_NAME "prog_offload_xdp_tether_downstream_ether"
-
-#define TETHER_DOWNSTREAM_XDP_PROG_RAWIP_PATH BPF_PATH_TETHER TETHER_DOWNSTREAM_XDP_PROG_RAWIP_NAME
-#define TETHER_DOWNSTREAM_XDP_PROG_ETHER_PATH BPF_PATH_TETHER TETHER_DOWNSTREAM_XDP_PROG_ETHER_NAME
-
-#define TETHER_UPSTREAM_XDP_PROG_RAWIP_NAME "prog_offload_xdp_tether_upstream_rawip"
-#define TETHER_UPSTREAM_XDP_PROG_ETHER_NAME "prog_offload_xdp_tether_upstream_ether"
-
-#define TETHER_UPSTREAM_XDP_PROG_RAWIP_PATH BPF_PATH_TETHER TETHER_UPSTREAM_XDP_PROG_RAWIP_NAME
-#define TETHER_UPSTREAM_XDP_PROG_ETHER_PATH BPF_PATH_TETHER TETHER_UPSTREAM_XDP_PROG_ETHER_NAME
-
#undef STRUCT_SIZE
diff --git a/bpf_progs/clatd.c b/bpf_progs/clatd.c
index dc646c3..c5b8555 100644
--- a/bpf_progs/clatd.c
+++ b/bpf_progs/clatd.c
@@ -30,6 +30,9 @@
#define __kernel_udphdr udphdr
#include <linux/udp.h>
+// The resulting .o needs to load on the Android T beta 3 bpfloader
+#define BPFLOADER_MIN_VER BPFLOADER_T_BETA3_VERSION
+
#include "bpf_helpers.h"
#include "bpf_net_helpers.h"
#include "bpf_shared.h"
@@ -37,21 +40,33 @@
// From kernel:include/net/ip.h
#define IP_DF 0x4000 // Flag: "Don't Fragment"
+// Used for iptables drops ingress clat packet. Beware of clat mark change may break the device
+// which is using the old clat mark in netd platform code. The reason is that the clat mark is a
+// mainline constant since T+ but netd iptable rules (ex: bandwidth control, firewall, and so on)
+// are set in stone.
+#define CLAT_MARK 0xdeadc1a7
+
DEFINE_BPF_MAP_GRW(clat_ingress6_map, HASH, ClatIngress6Key, ClatIngress6Value, 16, AID_SYSTEM)
static inline __always_inline int nat64(struct __sk_buff* skb, bool is_ethernet) {
- const int l2_header_size = is_ethernet ? sizeof(struct ethhdr) : 0;
- void* data = (void*)(long)skb->data;
- const void* data_end = (void*)(long)skb->data_end;
- const struct ethhdr* const eth = is_ethernet ? data : NULL; // used iff is_ethernet
- const struct ipv6hdr* const ip6 = is_ethernet ? (void*)(eth + 1) : data;
-
// Require ethernet dst mac address to be our unicast address.
if (is_ethernet && (skb->pkt_type != PACKET_HOST)) return TC_ACT_PIPE;
// Must be meta-ethernet IPv6 frame
if (skb->protocol != htons(ETH_P_IPV6)) return TC_ACT_PIPE;
+ const int l2_header_size = is_ethernet ? sizeof(struct ethhdr) : 0;
+
+ // Not clear if this is actually necessary considering we use DPA (Direct Packet Access),
+ // but we need to make sure we can read the IPv6 header reliably so that we can set
+ // skb->mark = 0xDeadC1a7 for packets we fail to offload.
+ try_make_writable(skb, l2_header_size + sizeof(struct ipv6hdr));
+
+ void* data = (void*)(long)skb->data;
+ const void* data_end = (void*)(long)skb->data_end;
+ const struct ethhdr* const eth = is_ethernet ? data : NULL; // used iff is_ethernet
+ const struct ipv6hdr* const ip6 = is_ethernet ? (void*)(eth + 1) : data;
+
// Must have (ethernet and) ipv6 header
if (data + l2_header_size + sizeof(*ip6) > data_end) return TC_ACT_PIPE;
@@ -64,17 +79,6 @@
// Maximum IPv6 payload length that can be translated to IPv4
if (ntohs(ip6->payload_len) > 0xFFFF - sizeof(struct iphdr)) return TC_ACT_PIPE;
- switch (ip6->nexthdr) {
- case IPPROTO_TCP: // For TCP & UDP the checksum neutrality of the chosen IPv6
- case IPPROTO_UDP: // address means there is no need to update their checksums.
- case IPPROTO_GRE: // We do not need to bother looking at GRE/ESP headers,
- case IPPROTO_ESP: // since there is never a checksum to update.
- break;
-
- default: // do not know how to handle anything else
- return TC_ACT_PIPE;
- }
-
ClatIngress6Key k = {
.iif = skb->ifindex,
.pfx96.in6_u.u6_addr32 =
@@ -90,6 +94,21 @@
if (!v) return TC_ACT_PIPE;
+ switch (ip6->nexthdr) {
+ case IPPROTO_TCP: // For TCP & UDP the checksum neutrality of the chosen IPv6
+ case IPPROTO_UDP: // address means there is no need to update their checksums.
+ case IPPROTO_GRE: // We do not need to bother looking at GRE/ESP headers,
+ case IPPROTO_ESP: // since there is never a checksum to update.
+ break;
+
+ default: // do not know how to handle anything else
+ // Mark ingress non-offloaded clat packet for dropping in ip6tables bw_raw_PREROUTING.
+ // Non-offloaded clat packet is going to be handled by clat daemon and ip6tables. The
+ // duplicate one in ip6tables is not necessary.
+ skb->mark = CLAT_MARK;
+ return TC_ACT_PIPE;
+ }
+
struct ethhdr eth2; // used iff is_ethernet
if (is_ethernet) {
eth2 = *eth; // Copy over the ethernet header (src/dst mac)
@@ -132,7 +151,13 @@
// Packet mutations begin - point of no return, but if this first modification fails
// the packet is probably still pristine, so let clatd handle it.
- if (bpf_skb_change_proto(skb, htons(ETH_P_IP), 0)) return TC_ACT_PIPE;
+ if (bpf_skb_change_proto(skb, htons(ETH_P_IP), 0)) {
+ // Mark ingress non-offloaded clat packet for dropping in ip6tables bw_raw_PREROUTING.
+ // Non-offloaded clat packet is going to be handled by clat daemon and ip6tables. The
+ // duplicate one in ip6tables is not necessary.
+ skb->mark = CLAT_MARK;
+ return TC_ACT_PIPE;
+ }
// This takes care of updating the skb->csum field for a CHECKSUM_COMPLETE packet.
//
@@ -198,13 +223,16 @@
DEFINE_BPF_PROG("schedcls/egress4/clat_rawip", AID_ROOT, AID_SYSTEM, sched_cls_egress4_clat_rawip)
(struct __sk_buff* skb) {
+ // Must be meta-ethernet IPv4 frame
+ if (skb->protocol != htons(ETH_P_IP)) return TC_ACT_PIPE;
+
+ // Possibly not needed, but for consistency with nat64 up above
+ try_make_writable(skb, sizeof(struct iphdr));
+
void* data = (void*)(long)skb->data;
const void* data_end = (void*)(long)skb->data_end;
const struct iphdr* const ip4 = data;
- // Must be meta-ethernet IPv4 frame
- if (skb->protocol != htons(ETH_P_IP)) return TC_ACT_PIPE;
-
// Must have ipv4 header
if (data + sizeof(*ip4) > data_end) return TC_ACT_PIPE;
diff --git a/bpf_progs/dscp_policy.c b/bpf_progs/dscp_policy.c
index 9989e6b..538a9e4 100644
--- a/bpf_progs/dscp_policy.c
+++ b/bpf_progs/dscp_policy.c
@@ -16,6 +16,7 @@
#include <linux/types.h>
#include <linux/bpf.h>
+#include <linux/if_packet.h>
#include <linux/ip.h>
#include <linux/ipv6.h>
#include <linux/if_ether.h>
@@ -26,250 +27,298 @@
#include <netinet/udp.h>
#include <string.h>
+// The resulting .o needs to load on the Android T beta 3 bpfloader
+#define BPFLOADER_MIN_VER BPFLOADER_T_BETA3_VERSION
+
#include "bpf_helpers.h"
+#include "dscp_policy.h"
-#define MAX_POLICIES 16
-#define MAP_A 1
-#define MAP_B 2
-
-#define STRUCT_SIZE(name, size) _Static_assert(sizeof(name) == (size), "Incorrect struct size.")
-
-// TODO: these are already defined in /system/netd/bpf_progs/bpf_net_helpers.h
-// should they be moved to common location?
-static uint64_t (*bpf_get_socket_cookie)(struct __sk_buff* skb) =
- (void*)BPF_FUNC_get_socket_cookie;
-static int (*bpf_skb_store_bytes)(struct __sk_buff* skb, __u32 offset, const void* from, __u32 len,
- __u64 flags) = (void*)BPF_FUNC_skb_store_bytes;
-static int (*bpf_l3_csum_replace)(struct __sk_buff* skb, __u32 offset, __u64 from, __u64 to,
- __u64 flags) = (void*)BPF_FUNC_l3_csum_replace;
-
-typedef struct {
- // Add family here to match __sk_buff ?
- struct in_addr srcIp;
- struct in_addr dstIp;
- __be16 srcPort;
- __be16 dstPort;
- uint8_t proto;
- uint8_t dscpVal;
- uint8_t pad[2];
-} Ipv4RuleEntry;
-STRUCT_SIZE(Ipv4RuleEntry, 2 * 4 + 2 * 2 + 2 * 1 + 2); // 16, 4 for in_addr
-
-#define SRC_IP_MASK 1
-#define DST_IP_MASK 2
-#define SRC_PORT_MASK 4
-#define DST_PORT_MASK 8
-#define PROTO_MASK 16
-
-typedef struct {
- struct in6_addr srcIp;
- struct in6_addr dstIp;
- __be16 srcPort;
- __be16 dstPortStart;
- __be16 dstPortEnd;
- uint8_t proto;
- uint8_t dscpVal;
- uint8_t mask;
- uint8_t pad[3];
-} Ipv4Policy;
-STRUCT_SIZE(Ipv4Policy, 2 * 16 + 3 * 2 + 3 * 1 + 3); // 44
-
-typedef struct {
- struct in6_addr srcIp;
- struct in6_addr dstIp;
- __be16 srcPort;
- __be16 dstPortStart;
- __be16 dstPortEnd;
- uint8_t proto;
- uint8_t dscpVal;
- uint8_t mask;
- // should we override this struct to include the param bitmask for linear search?
- // For mapping socket to policies, all the params should match exactly since we can
- // pull any missing from the sock itself.
-} Ipv6RuleEntry;
-STRUCT_SIZE(Ipv6RuleEntry, 2 * 16 + 3 * 2 + 3 * 1 + 3); // 44
-
-// TODO: move to using 1 map. Map v4 address to 0xffff::v4
-DEFINE_BPF_MAP_GRW(ipv4_socket_to_policies_map_A, HASH, uint64_t, Ipv4RuleEntry, MAX_POLICIES,
- AID_SYSTEM)
-DEFINE_BPF_MAP_GRW(ipv4_socket_to_policies_map_B, HASH, uint64_t, Ipv4RuleEntry, MAX_POLICIES,
- AID_SYSTEM)
-DEFINE_BPF_MAP_GRW(ipv6_socket_to_policies_map_A, HASH, uint64_t, Ipv6RuleEntry, MAX_POLICIES,
- AID_SYSTEM)
-DEFINE_BPF_MAP_GRW(ipv6_socket_to_policies_map_B, HASH, uint64_t, Ipv6RuleEntry, MAX_POLICIES,
- AID_SYSTEM)
DEFINE_BPF_MAP_GRW(switch_comp_map, ARRAY, int, uint64_t, 1, AID_SYSTEM)
-DEFINE_BPF_MAP_GRW(ipv4_dscp_policies_map, ARRAY, uint32_t, Ipv4Policy, MAX_POLICIES,
+DEFINE_BPF_MAP_GRW(ipv4_socket_to_policies_map_A, HASH, uint64_t, RuleEntry, MAX_POLICIES,
AID_SYSTEM)
-DEFINE_BPF_MAP_GRW(ipv6_dscp_policies_map, ARRAY, uint32_t, Ipv6RuleEntry, MAX_POLICIES,
+DEFINE_BPF_MAP_GRW(ipv4_socket_to_policies_map_B, HASH, uint64_t, RuleEntry, MAX_POLICIES,
+ AID_SYSTEM)
+DEFINE_BPF_MAP_GRW(ipv6_socket_to_policies_map_A, HASH, uint64_t, RuleEntry, MAX_POLICIES,
+ AID_SYSTEM)
+DEFINE_BPF_MAP_GRW(ipv6_socket_to_policies_map_B, HASH, uint64_t, RuleEntry, MAX_POLICIES,
AID_SYSTEM)
-DEFINE_BPF_PROG_KVER("schedcls/set_dscp", AID_ROOT, AID_SYSTEM,
- schedcls_set_dscp, KVER(5, 4, 0))
-(struct __sk_buff* skb) {
- int one = 0;
- uint64_t* selectedMap = bpf_switch_comp_map_lookup_elem(&one);
+DEFINE_BPF_MAP_GRW(ipv4_dscp_policies_map, ARRAY, uint32_t, DscpPolicy, MAX_POLICIES,
+ AID_SYSTEM)
+DEFINE_BPF_MAP_GRW(ipv6_dscp_policies_map, ARRAY, uint32_t, DscpPolicy, MAX_POLICIES,
+ AID_SYSTEM)
+
+static inline __always_inline void match_policy(struct __sk_buff* skb, bool ipv4, bool is_eth) {
+ void* data = (void*)(long)skb->data;
+ const void* data_end = (void*)(long)skb->data_end;
+
+ const int l2_header_size = is_eth ? sizeof(struct ethhdr) : 0;
+ struct ethhdr* eth = is_eth ? data : NULL;
+
+ if (data + l2_header_size > data_end) return;
+
+ int zero = 0;
+ int hdr_size = 0;
+ uint64_t* selectedMap = bpf_switch_comp_map_lookup_elem(&zero);
// use this with HASH map so map lookup only happens once policies have been added?
if (!selectedMap) {
- return TC_ACT_PIPE;
+ return;
}
// used for map lookup
uint64_t cookie = bpf_get_socket_cookie(skb);
+ if (!cookie)
+ return;
- // Do we need separate maps for ipv4/ipv6
- if (skb->protocol == htons(ETH_P_IP)) { //maybe bpf_htons()
- Ipv4RuleEntry* v4Policy;
- if (*selectedMap == MAP_A) {
- v4Policy = bpf_ipv4_socket_to_policies_map_A_lookup_elem(&cookie);
- } else {
- v4Policy = bpf_ipv4_socket_to_policies_map_B_lookup_elem(&cookie);
- }
-
- // How to use bitmask here to compare params efficiently?
- // TODO: add BPF_PROG_TYPE_SK_SKB prog type to Loader?
-
- void* data = (void*)(long)skb->data;
- const void* data_end = (void*)(long)skb->data_end;
- const struct iphdr* const iph = data;
-
+ uint16_t sport = 0;
+ uint16_t dport = 0;
+ uint8_t protocol = 0; // TODO: Use are reserved value? Or int (-1) and cast to uint below?
+ struct in6_addr srcIp = {};
+ struct in6_addr dstIp = {};
+ uint8_t tos = 0; // Only used for IPv4
+ uint8_t priority = 0; // Only used for IPv6
+ uint8_t flow_lbl = 0; // Only used for IPv6
+ if (ipv4) {
+ const struct iphdr* const iph = is_eth ? (void*)(eth + 1) : data;
// Must have ipv4 header
- if (data + sizeof(*iph) > data_end) return TC_ACT_PIPE;
+ if (data + l2_header_size + sizeof(*iph) > data_end) return;
// IP version must be 4
- if (iph->version != 4) return TC_ACT_PIPE;
+ if (iph->version != 4) return;
// We cannot handle IP options, just standard 20 byte == 5 dword minimal IPv4 header
- if (iph->ihl != 5) return TC_ACT_PIPE;
+ if (iph->ihl != 5) return;
- if (iph->protocol != IPPROTO_UDP) return TC_ACT_PIPE;
+ // V4 mapped address in in6_addr sets 10/11 position to 0xff.
+ srcIp.s6_addr32[2] = htonl(0x0000ffff);
+ dstIp.s6_addr32[2] = htonl(0x0000ffff);
- struct udphdr *udp;
- udp = data + sizeof(struct iphdr); //sizeof(struct ethhdr)
+ // Copy IPv4 address into in6_addr for easy comparison below.
+ srcIp.s6_addr32[3] = iph->saddr;
+ dstIp.s6_addr32[3] = iph->daddr;
+ protocol = iph->protocol;
+ tos = iph->tos;
+ hdr_size = sizeof(struct iphdr);
+ } else {
+ struct ipv6hdr* ip6h = is_eth ? (void*)(eth + 1) : data;
+ // Must have ipv6 header
+ if (data + l2_header_size + sizeof(*ip6h) > data_end) return;
- if ((void*)(udp + 1) > data_end) return TC_ACT_PIPE;
+ if (ip6h->version != 6) return;
- // Source/destination port in udphdr are stored in be16, need to convert to le16.
- // This can be done via ntohs or htons. Is there a more preferred way?
- // Cached policy was found.
- if (v4Policy && iph->saddr == v4Policy->srcIp.s_addr &&
- iph->daddr == v4Policy->dstIp.s_addr &&
- ntohs(udp->source) == v4Policy->srcPort &&
- ntohs(udp->dest) == v4Policy->dstPort &&
- iph->protocol == v4Policy->proto) {
- // set dscpVal in packet. Least sig 2 bits of TOS
- // reference ipv4_change_dsfield()
+ srcIp = ip6h->saddr;
+ dstIp = ip6h->daddr;
+ protocol = ip6h->nexthdr;
+ priority = ip6h->priority;
+ flow_lbl = ip6h->flow_lbl[0];
+ hdr_size = sizeof(struct ipv6hdr);
+ }
- // TODO: fix checksum...
- int ecn = iph->tos & 3;
- uint8_t newDscpVal = (v4Policy->dscpVal << 2) + ecn;
- int oldDscpVal = iph->tos >> 2;
+ switch (protocol) {
+ case IPPROTO_UDP:
+ case IPPROTO_UDPLITE:
+ {
+ struct udphdr *udp;
+ udp = data + hdr_size;
+ if ((void*)(udp + 1) > data_end) return;
+ sport = udp->source;
+ dport = udp->dest;
+ }
+ break;
+ case IPPROTO_TCP:
+ {
+ struct tcphdr *tcp;
+ tcp = data + hdr_size;
+ if ((void*)(tcp + 1) > data_end) return;
+ sport = tcp->source;
+ dport = tcp->dest;
+ }
+ break;
+ default:
+ return;
+ }
+
+ RuleEntry* existingRule;
+ if (ipv4) {
+ if (*selectedMap == MAP_A) {
+ existingRule = bpf_ipv4_socket_to_policies_map_A_lookup_elem(&cookie);
+ } else {
+ existingRule = bpf_ipv4_socket_to_policies_map_B_lookup_elem(&cookie);
+ }
+ } else {
+ if (*selectedMap == MAP_A) {
+ existingRule = bpf_ipv6_socket_to_policies_map_A_lookup_elem(&cookie);
+ } else {
+ existingRule = bpf_ipv6_socket_to_policies_map_B_lookup_elem(&cookie);
+ }
+ }
+
+ if (existingRule && v6_equal(srcIp, existingRule->srcIp) &&
+ v6_equal(dstIp, existingRule->dstIp) &&
+ skb->ifindex == existingRule->ifindex &&
+ ntohs(sport) == htons(existingRule->srcPort) &&
+ ntohs(dport) == htons(existingRule->dstPort) &&
+ protocol == existingRule->proto) {
+ if (ipv4) {
+ int ecn = tos & 3;
+ uint8_t newDscpVal = (existingRule->dscpVal << 2) + ecn;
+ int oldDscpVal = tos >> 2;
bpf_l3_csum_replace(skb, 1, oldDscpVal, newDscpVal, sizeof(uint8_t));
bpf_skb_store_bytes(skb, 1, &newDscpVal, sizeof(uint8_t), 0);
- return TC_ACT_PIPE;
+ } else {
+ uint8_t new_priority = (existingRule->dscpVal >> 2) + 0x60;
+ uint8_t new_flow_label = ((existingRule->dscpVal & 0xf) << 6) + (priority >> 6);
+ bpf_skb_store_bytes(skb, 0, &new_priority, sizeof(uint8_t), 0);
+ bpf_skb_store_bytes(skb, 1, &new_flow_label, sizeof(uint8_t), 0);
+ }
+ return;
+ }
+
+ // Linear scan ipv4_dscp_policies_map since no stored params match skb.
+ int bestScore = -1;
+ uint32_t bestMatch = 0;
+
+ for (register uint64_t i = 0; i < MAX_POLICIES; i++) {
+ int score = 0;
+ uint8_t tempMask = 0;
+ // Using a uint64 in for loop prevents infinite loop during BPF load,
+ // but the key is uint32, so convert back.
+ uint32_t key = i;
+
+ DscpPolicy* policy;
+ if (ipv4) {
+ policy = bpf_ipv4_dscp_policies_map_lookup_elem(&key);
+ } else {
+ policy = bpf_ipv6_dscp_policies_map_lookup_elem(&key);
}
- // linear scan ipv4_dscp_policies_map, stored socket params do not match actual
- int bestScore = -1;
- uint32_t bestMatch = 0;
+ // If the policy lookup failed, presentFields is 0, or iface index does not match
+ // index on skb buff, then we can continue to next policy.
+ if (!policy || policy->presentFields == 0 || policy->ifindex != skb->ifindex)
+ continue;
- for (register uint64_t i = 0; i < MAX_POLICIES; i++) {
- int score = 0;
- uint8_t tempMask = 0;
- // Using a uint62 in for loop prevents infinite loop during BPF load,
- // but the key is uint32, so convert back.
- uint32_t key = i;
- Ipv4Policy* policy = bpf_ipv4_dscp_policies_map_lookup_elem(&key);
+ if ((policy->presentFields & SRC_IP_MASK_FLAG) == SRC_IP_MASK_FLAG &&
+ v6_equal(srcIp, policy->srcIp)) {
+ score++;
+ tempMask |= SRC_IP_MASK_FLAG;
+ }
+ if ((policy->presentFields & DST_IP_MASK_FLAG) == DST_IP_MASK_FLAG &&
+ v6_equal(dstIp, policy->dstIp)) {
+ score++;
+ tempMask |= DST_IP_MASK_FLAG;
+ }
+ if ((policy->presentFields & SRC_PORT_MASK_FLAG) == SRC_PORT_MASK_FLAG &&
+ ntohs(sport) == htons(policy->srcPort)) {
+ score++;
+ tempMask |= SRC_PORT_MASK_FLAG;
+ }
+ if ((policy->presentFields & DST_PORT_MASK_FLAG) == DST_PORT_MASK_FLAG &&
+ ntohs(dport) >= htons(policy->dstPortStart) &&
+ ntohs(dport) <= htons(policy->dstPortEnd)) {
+ score++;
+ tempMask |= DST_PORT_MASK_FLAG;
+ }
+ if ((policy->presentFields & PROTO_MASK_FLAG) == PROTO_MASK_FLAG &&
+ protocol == policy->proto) {
+ score++;
+ tempMask |= PROTO_MASK_FLAG;
+ }
- // if mask is 0 continue, key does not have corresponding policy value
- if (policy && policy->mask != 0) {
- if ((policy->mask & SRC_IP_MASK) == SRC_IP_MASK &&
- iph->saddr == policy->srcIp.s6_addr32[3]) {
- score++;
- tempMask |= SRC_IP_MASK;
- }
- if ((policy->mask & DST_IP_MASK) == DST_IP_MASK &&
- iph->daddr == policy->dstIp.s6_addr32[3]) {
- score++;
- tempMask |= DST_IP_MASK;
- }
- if ((policy->mask & SRC_PORT_MASK) == SRC_PORT_MASK &&
- ntohs(udp->source) == htons(policy->srcPort)) {
- score++;
- tempMask |= SRC_PORT_MASK;
- }
- if ((policy->mask & DST_PORT_MASK) == DST_PORT_MASK &&
- ntohs(udp->dest) >= htons(policy->dstPortStart) &&
- ntohs(udp->dest) <= htons(policy->dstPortEnd)) {
- score++;
- tempMask |= DST_PORT_MASK;
- }
- if ((policy->mask & PROTO_MASK) == PROTO_MASK &&
- iph->protocol == policy->proto) {
- score++;
- tempMask |= PROTO_MASK;
- }
+ if (score > bestScore && tempMask == policy->presentFields) {
+ bestMatch = i;
+ bestScore = score;
+ }
+ }
- if (score > bestScore && tempMask == policy->mask) {
- bestMatch = i;
- bestScore = score;
- }
+ uint8_t new_tos= 0; // Can 0 be used as default forwarding value?
+ uint8_t new_priority = 0;
+ uint8_t new_flow_lbl = 0;
+ if (bestScore > 0) {
+ DscpPolicy* policy;
+ if (ipv4) {
+ policy = bpf_ipv4_dscp_policies_map_lookup_elem(&bestMatch);
+ } else {
+ policy = bpf_ipv6_dscp_policies_map_lookup_elem(&bestMatch);
+ }
+
+ if (policy) {
+ // TODO: if DSCP value is already set ignore?
+ if (ipv4) {
+ int ecn = tos & 3;
+ new_tos = (policy->dscpVal << 2) + ecn;
+ } else {
+ new_priority = (policy->dscpVal >> 2) + 0x60;
+ new_flow_lbl = ((policy->dscpVal & 0xf) << 6) + (flow_lbl >> 6);
+
+ // Set IPv6 curDscp value to stored value and recalulate priority
+ // and flow label during next use.
+ new_tos = policy->dscpVal;
}
}
+ } else return;
- uint8_t newDscpVal = 0; // Can 0 be used as default forwarding value?
- uint8_t curDscp = iph->tos & 252;
- if (bestScore > 0) {
- Ipv4Policy* policy = bpf_ipv4_dscp_policies_map_lookup_elem(&bestMatch);
- if (policy) {
- // TODO: if DSCP value is already set ignore?
- // TODO: update checksum, for testing increment counter...
- int ecn = iph->tos & 3;
- newDscpVal = (policy->dscpVal << 2) + ecn;
- }
- }
+ RuleEntry value = {
+ .srcIp = srcIp,
+ .dstIp = dstIp,
+ .ifindex = skb->ifindex,
+ .srcPort = sport,
+ .dstPort = dport,
+ .proto = protocol,
+ .dscpVal = new_tos,
+ };
- Ipv4RuleEntry value = {
- .srcIp.s_addr = iph->saddr,
- .dstIp.s_addr = iph->daddr,
- .srcPort = udp->source,
- .dstPort = udp->dest,
- .proto = iph->protocol,
- .dscpVal = newDscpVal,
- };
-
- if (!cookie)
- return TC_ACT_PIPE;
-
- // Update map
+ //Update map with new policy.
+ if (ipv4) {
if (*selectedMap == MAP_A) {
bpf_ipv4_socket_to_policies_map_A_update_elem(&cookie, &value, BPF_ANY);
} else {
bpf_ipv4_socket_to_policies_map_B_update_elem(&cookie, &value, BPF_ANY);
}
-
- // Need to store bytes after updating map or program will not load.
- if (newDscpVal != curDscp) {
- // 1 is the offset (Version/Header length)
- int oldDscpVal = iph->tos >> 2;
- bpf_l3_csum_replace(skb, 1, oldDscpVal, newDscpVal, sizeof(uint8_t));
- bpf_skb_store_bytes(skb, 1, &newDscpVal, sizeof(uint8_t), 0);
- }
-
- } else if (skb->protocol == htons(ETH_P_IPV6)) { //maybe bpf_htons()
- Ipv6RuleEntry* v6Policy;
+ } else {
if (*selectedMap == MAP_A) {
- v6Policy = bpf_ipv6_socket_to_policies_map_A_lookup_elem(&cookie);
+ bpf_ipv6_socket_to_policies_map_A_update_elem(&cookie, &value, BPF_ANY);
} else {
- v6Policy = bpf_ipv6_socket_to_policies_map_B_lookup_elem(&cookie);
+ bpf_ipv6_socket_to_policies_map_B_update_elem(&cookie, &value, BPF_ANY);
}
+ }
- if (!v6Policy)
- return TC_ACT_PIPE;
+ // Need to store bytes after updating map or program will not load.
+ if (ipv4 && new_tos != (tos & 252)) {
+ int oldDscpVal = tos >> 2;
+ bpf_l3_csum_replace(skb, 1, oldDscpVal, new_tos, sizeof(uint8_t));
+ bpf_skb_store_bytes(skb, 1, &new_tos, sizeof(uint8_t), 0);
+ } else if (!ipv4 && (new_priority != priority || new_flow_lbl != flow_lbl)) {
+ bpf_skb_store_bytes(skb, 0, &new_priority, sizeof(uint8_t), 0);
+ bpf_skb_store_bytes(skb, 1, &new_flow_lbl, sizeof(uint8_t), 0);
+ }
+ return;
+}
- // TODO: Add code to process IPv6 packet.
+DEFINE_BPF_PROG_KVER("schedcls/set_dscp_ether", AID_ROOT, AID_SYSTEM,
+ schedcls_set_dscp_ether, KVER(5, 4, 0))
+(struct __sk_buff* skb) {
+
+ if (skb->pkt_type != PACKET_HOST) return TC_ACT_PIPE;
+
+ if (skb->protocol == htons(ETH_P_IP)) {
+ match_policy(skb, true, true);
+ } else if (skb->protocol == htons(ETH_P_IPV6)) {
+ match_policy(skb, false, true);
+ }
+
+ // Always return TC_ACT_PIPE
+ return TC_ACT_PIPE;
+}
+
+DEFINE_BPF_PROG_KVER("schedcls/set_dscp_raw_ip", AID_ROOT, AID_SYSTEM,
+ schedcls_set_dscp_raw_ip, KVER(5, 4, 0))
+(struct __sk_buff* skb) {
+ if (skb->protocol == htons(ETH_P_IP)) {
+ match_policy(skb, true, false);
+ } else if (skb->protocol == htons(ETH_P_IPV6)) {
+ match_policy(skb, false, false);
}
// Always return TC_ACT_PIPE
diff --git a/bpf_progs/dscp_policy.h b/bpf_progs/dscp_policy.h
new file mode 100644
index 0000000..1637f7a
--- /dev/null
+++ b/bpf_progs/dscp_policy.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define MAX_POLICIES 16
+#define MAP_A 1
+#define MAP_B 2
+
+#define SRC_IP_MASK_FLAG 1
+#define DST_IP_MASK_FLAG 2
+#define SRC_PORT_MASK_FLAG 4
+#define DST_PORT_MASK_FLAG 8
+#define PROTO_MASK_FLAG 16
+
+#define STRUCT_SIZE(name, size) _Static_assert(sizeof(name) == (size), "Incorrect struct size.")
+
+#define v6_equal(a, b) \
+ (((a.s6_addr32[0] ^ b.s6_addr32[0]) | \
+ (a.s6_addr32[1] ^ b.s6_addr32[1]) | \
+ (a.s6_addr32[2] ^ b.s6_addr32[2]) | \
+ (a.s6_addr32[3] ^ b.s6_addr32[3])) == 0)
+
+// TODO: these are already defined in packages/modules/Connectivity/bpf_progs/bpf_net_helpers.h.
+// smove to common location in future.
+static uint64_t (*bpf_get_socket_cookie)(struct __sk_buff* skb) =
+ (void*)BPF_FUNC_get_socket_cookie;
+static int (*bpf_skb_store_bytes)(struct __sk_buff* skb, __u32 offset, const void* from, __u32 len,
+ __u64 flags) = (void*)BPF_FUNC_skb_store_bytes;
+static int (*bpf_l3_csum_replace)(struct __sk_buff* skb, __u32 offset, __u64 from, __u64 to,
+ __u64 flags) = (void*)BPF_FUNC_l3_csum_replace;
+static long (*bpf_skb_ecn_set_ce)(struct __sk_buff* skb) =
+ (void*)BPF_FUNC_skb_ecn_set_ce;
+
+typedef struct {
+ struct in6_addr srcIp;
+ struct in6_addr dstIp;
+ uint32_t ifindex;
+ __be16 srcPort;
+ __be16 dstPortStart;
+ __be16 dstPortEnd;
+ uint8_t proto;
+ uint8_t dscpVal;
+ uint8_t presentFields;
+ uint8_t pad[3];
+} DscpPolicy;
+STRUCT_SIZE(DscpPolicy, 2 * 16 + 4 + 3 * 2 + 3 * 1 + 3); // 48
+
+typedef struct {
+ struct in6_addr srcIp;
+ struct in6_addr dstIp;
+ __u32 ifindex;
+ __be16 srcPort;
+ __be16 dstPort;
+ __u8 proto;
+ __u8 dscpVal;
+ __u8 pad[2];
+} RuleEntry;
+STRUCT_SIZE(RuleEntry, 2 * 16 + 1 * 4 + 2 * 2 + 2 * 1 + 2); // 44
\ No newline at end of file
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
index fe9a871..9ae8ab2 100644
--- a/bpf_progs/netd.c
+++ b/bpf_progs/netd.c
@@ -14,6 +14,9 @@
* limitations under the License.
*/
+// The resulting .o needs to load on the Android T Beta 3 bpfloader
+#define BPFLOADER_MIN_VER BPFLOADER_T_BETA3_VERSION
+
#include <bpf_helpers.h>
#include <linux/bpf.h>
#include <linux/if.h>
@@ -59,7 +62,7 @@
DEFINE_BPF_MAP_GRW(stats_map_B, HASH, StatsKey, StatsValue, STATS_MAP_SIZE, AID_NET_BW_ACCT)
DEFINE_BPF_MAP_GRW(iface_stats_map, HASH, uint32_t, StatsValue, IFACE_STATS_MAP_SIZE,
AID_NET_BW_ACCT)
-DEFINE_BPF_MAP_GRW(configuration_map, HASH, uint32_t, uint8_t, CONFIGURATION_MAP_SIZE,
+DEFINE_BPF_MAP_GRW(configuration_map, HASH, uint32_t, uint32_t, CONFIGURATION_MAP_SIZE,
AID_NET_BW_ACCT)
DEFINE_BPF_MAP_GRW(uid_owner_map, HASH, uint32_t, UidOwnerValue, UID_OWNER_MAP_SIZE,
AID_NET_BW_ACCT)
@@ -194,7 +197,7 @@
BpfConfig enabledRules = getConfig(UID_RULES_CONFIGURATION_KEY);
UidOwnerValue* uidEntry = bpf_uid_owner_map_lookup_elem(&uid);
- uint8_t uidRules = uidEntry ? uidEntry->rule : 0;
+ uint32_t uidRules = uidEntry ? uidEntry->rule : 0;
uint32_t allowed_iif = uidEntry ? uidEntry->iif : 0;
if (enabledRules) {
@@ -213,10 +216,23 @@
if ((enabledRules & LOW_POWER_STANDBY_MATCH) && !(uidRules & LOW_POWER_STANDBY_MATCH)) {
return BPF_DROP;
}
+ if ((enabledRules & OEM_DENY_1_MATCH) && (uidRules & OEM_DENY_1_MATCH)) {
+ return BPF_DROP;
+ }
+ if ((enabledRules & OEM_DENY_2_MATCH) && (uidRules & OEM_DENY_2_MATCH)) {
+ return BPF_DROP;
+ }
}
- if (direction == BPF_INGRESS && (uidRules & IIF_MATCH)) {
- // Drops packets not coming from lo nor the allowlisted interface
- if (allowed_iif && skb->ifindex != 1 && skb->ifindex != allowed_iif) {
+ if (direction == BPF_INGRESS && skb->ifindex != 1) {
+ if (uidRules & IIF_MATCH) {
+ if (allowed_iif && skb->ifindex != allowed_iif) {
+ // Drops packets not coming from lo nor the allowed interface
+ // allowed interface=0 is a wildcard and does not drop packets
+ return BPF_DROP_UNLESS_DNS;
+ }
+ } else if (uidRules & LOCKDOWN_VPN_MATCH) {
+ // Drops packets not coming from lo and rule does not have IIF_MATCH but has
+ // LOCKDOWN_VPN_MATCH
return BPF_DROP_UNLESS_DNS;
}
}
@@ -224,7 +240,7 @@
}
static __always_inline inline void update_stats_with_config(struct __sk_buff* skb, int direction,
- StatsKey* key, uint8_t selectedMap) {
+ StatsKey* key, uint32_t selectedMap) {
if (selectedMap == SELECT_MAP_A) {
update_stats_map_A(skb, direction, key);
} else if (selectedMap == SELECT_MAP_B) {
@@ -276,7 +292,7 @@
if (counterSet) key.counterSet = (uint32_t)*counterSet;
uint32_t mapSettingKey = CURRENT_STATS_MAP_CONFIGURATION_KEY;
- uint8_t* selectedMap = bpf_configuration_map_lookup_elem(&mapSettingKey);
+ uint32_t* selectedMap = bpf_configuration_map_lookup_elem(&mapSettingKey);
// Use asm("%0 &= 1" : "+r"(match)) before return match,
// to help kernel's bpf verifier, so that it can be 100% certain
@@ -373,8 +389,7 @@
return BPF_NOMATCH;
}
-DEFINE_BPF_PROG_KVER("cgroupsock/inet/create", AID_ROOT, AID_ROOT, inet_socket_create,
- KVER(4, 14, 0))
+DEFINE_BPF_PROG("cgroupsock/inet/create", AID_ROOT, AID_ROOT, inet_socket_create)
(struct bpf_sock* sk) {
uint64_t gid_uid = bpf_get_current_uid_gid();
/*
diff --git a/bpf_progs/offload.c b/bpf_progs/offload.c
index 977e918..2ec0792 100644
--- a/bpf_progs/offload.c
+++ b/bpf_progs/offload.c
@@ -24,8 +24,8 @@
#define __kernel_udphdr udphdr
#include <linux/udp.h>
-// The resulting .o needs to load on the Android S bpfloader v0.2
-#define BPFLOADER_MIN_VER 2u
+// The resulting .o needs to load on the Android S bpfloader
+#define BPFLOADER_MIN_VER BPFLOADER_S_VERSION
#include "bpf_helpers.h"
#include "bpf_net_helpers.h"
@@ -122,7 +122,7 @@
// not trigger and thus we need to manually make sure we can read packet headers via DPA.
// Note: this is a blind best effort pull, which may fail or pull less - this doesn't matter.
// It has to be done early cause it will invalidate any skb->data/data_end derived pointers.
- try_make_readable(skb, l2_header_size + IP6_HLEN + TCP_HLEN);
+ try_make_writable(skb, l2_header_size + IP6_HLEN + TCP_HLEN);
void* data = (void*)(long)skb->data;
const void* data_end = (void*)(long)skb->data_end;
@@ -355,88 +355,10 @@
DEFINE_BPF_MAP_GRW(tether_upstream4_map, HASH, Tether4Key, Tether4Value, 1024, AID_NETWORK_STACK)
-static inline __always_inline int do_forward4(struct __sk_buff* skb, const bool is_ethernet,
- const bool downstream, const bool updatetime) {
- // Require ethernet dst mac address to be our unicast address.
- if (is_ethernet && (skb->pkt_type != PACKET_HOST)) return TC_ACT_PIPE;
-
- // Must be meta-ethernet IPv4 frame
- if (skb->protocol != htons(ETH_P_IP)) return TC_ACT_PIPE;
-
- const int l2_header_size = is_ethernet ? sizeof(struct ethhdr) : 0;
-
- // Since the program never writes via DPA (direct packet access) auto-pull/unclone logic does
- // not trigger and thus we need to manually make sure we can read packet headers via DPA.
- // Note: this is a blind best effort pull, which may fail or pull less - this doesn't matter.
- // It has to be done early cause it will invalidate any skb->data/data_end derived pointers.
- try_make_readable(skb, l2_header_size + IP4_HLEN + TCP_HLEN);
-
- void* data = (void*)(long)skb->data;
- const void* data_end = (void*)(long)skb->data_end;
- struct ethhdr* eth = is_ethernet ? data : NULL; // used iff is_ethernet
- struct iphdr* ip = is_ethernet ? (void*)(eth + 1) : data;
-
- // Must have (ethernet and) ipv4 header
- if (data + l2_header_size + sizeof(*ip) > data_end) return TC_ACT_PIPE;
-
- // Ethertype - if present - must be IPv4
- if (is_ethernet && (eth->h_proto != htons(ETH_P_IP))) return TC_ACT_PIPE;
-
- // IP version must be 4
- if (ip->version != 4) TC_PUNT(INVALID_IP_VERSION);
-
- // We cannot handle IP options, just standard 20 byte == 5 dword minimal IPv4 header
- if (ip->ihl != 5) TC_PUNT(HAS_IP_OPTIONS);
-
- // Calculate the IPv4 one's complement checksum of the IPv4 header.
- __wsum sum4 = 0;
- for (int i = 0; i < sizeof(*ip) / sizeof(__u16); ++i) {
- sum4 += ((__u16*)ip)[i];
- }
- // Note that sum4 is guaranteed to be non-zero by virtue of ip4->version == 4
- sum4 = (sum4 & 0xFFFF) + (sum4 >> 16); // collapse u32 into range 1 .. 0x1FFFE
- sum4 = (sum4 & 0xFFFF) + (sum4 >> 16); // collapse any potential carry into u16
- // for a correct checksum we should get *a* zero, but sum4 must be positive, ie 0xFFFF
- if (sum4 != 0xFFFF) TC_PUNT(CHECKSUM);
-
- // Minimum IPv4 total length is the size of the header
- if (ntohs(ip->tot_len) < sizeof(*ip)) TC_PUNT(TRUNCATED_IPV4);
-
- // We are incapable of dealing with IPv4 fragments
- if (ip->frag_off & ~htons(IP_DF)) TC_PUNT(IS_IP_FRAG);
-
- // Cannot decrement during forward if already zero or would be zero,
- // Let the kernel's stack handle these cases and generate appropriate ICMP errors.
- if (ip->ttl <= 1) TC_PUNT(LOW_TTL);
-
- // If we cannot update the 'last_used' field due to lack of bpf_ktime_get_boot_ns() helper,
- // then it is not safe to offload UDP due to the small conntrack timeouts, as such,
- // in such a situation we can only support TCP. This also has the added nice benefit of
- // using a separate error counter, and thus making it obvious which version of the program
- // is loaded.
- if (!updatetime && ip->protocol != IPPROTO_TCP) TC_PUNT(NON_TCP);
-
- // We do not support offloading anything besides IPv4 TCP and UDP, due to need for NAT,
- // but no need to check this if !updatetime due to check immediately above.
- if (updatetime && (ip->protocol != IPPROTO_TCP) && (ip->protocol != IPPROTO_UDP))
- TC_PUNT(NON_TCP_UDP);
-
- // We want to make sure that the compiler will, in the !updatetime case, entirely optimize
- // out all the non-tcp logic. Also note that at this point is_udp === !is_tcp.
- const bool is_tcp = !updatetime || (ip->protocol == IPPROTO_TCP);
-
- // This is a bit of a hack to make things easier on the bpf verifier.
- // (In particular I believe the Linux 4.14 kernel's verifier can get confused later on about
- // what offsets into the packet are valid and can spuriously reject the program, this is
- // because it fails to realize that is_tcp && !is_tcp is impossible)
- //
- // For both TCP & UDP we'll need to read and modify the src/dst ports, which so happen to
- // always be in the first 4 bytes of the L4 header. Additionally for UDP we'll need access
- // to the checksum field which is in bytes 7 and 8. While for TCP we'll need to read the
- // TCP flags (at offset 13) and access to the checksum field (2 bytes at offset 16).
- // As such we *always* need access to at least 8 bytes.
- if (data + l2_header_size + sizeof(*ip) + 8 > data_end) TC_PUNT(SHORT_L4_HEADER);
-
+static inline __always_inline int do_forward4_bottom(struct __sk_buff* skb,
+ const int l2_header_size, void* data, const void* data_end,
+ struct ethhdr* eth, struct iphdr* ip, const bool is_ethernet,
+ const bool downstream, const bool updatetime, const bool is_tcp) {
struct tcphdr* tcph = is_tcp ? (void*)(ip + 1) : NULL;
struct udphdr* udph = is_tcp ? NULL : (void*)(ip + 1);
@@ -625,6 +547,102 @@
return bpf_redirect(v->oif, 0 /* this is effectively BPF_F_EGRESS */);
}
+static inline __always_inline int do_forward4(struct __sk_buff* skb, const bool is_ethernet,
+ const bool downstream, const bool updatetime) {
+ // Require ethernet dst mac address to be our unicast address.
+ if (is_ethernet && (skb->pkt_type != PACKET_HOST)) return TC_ACT_PIPE;
+
+ // Must be meta-ethernet IPv4 frame
+ if (skb->protocol != htons(ETH_P_IP)) return TC_ACT_PIPE;
+
+ const int l2_header_size = is_ethernet ? sizeof(struct ethhdr) : 0;
+
+ // Since the program never writes via DPA (direct packet access) auto-pull/unclone logic does
+ // not trigger and thus we need to manually make sure we can read packet headers via DPA.
+ // Note: this is a blind best effort pull, which may fail or pull less - this doesn't matter.
+ // It has to be done early cause it will invalidate any skb->data/data_end derived pointers.
+ try_make_writable(skb, l2_header_size + IP4_HLEN + TCP_HLEN);
+
+ void* data = (void*)(long)skb->data;
+ const void* data_end = (void*)(long)skb->data_end;
+ struct ethhdr* eth = is_ethernet ? data : NULL; // used iff is_ethernet
+ struct iphdr* ip = is_ethernet ? (void*)(eth + 1) : data;
+
+ // Must have (ethernet and) ipv4 header
+ if (data + l2_header_size + sizeof(*ip) > data_end) return TC_ACT_PIPE;
+
+ // Ethertype - if present - must be IPv4
+ if (is_ethernet && (eth->h_proto != htons(ETH_P_IP))) return TC_ACT_PIPE;
+
+ // IP version must be 4
+ if (ip->version != 4) TC_PUNT(INVALID_IP_VERSION);
+
+ // We cannot handle IP options, just standard 20 byte == 5 dword minimal IPv4 header
+ if (ip->ihl != 5) TC_PUNT(HAS_IP_OPTIONS);
+
+ // Calculate the IPv4 one's complement checksum of the IPv4 header.
+ __wsum sum4 = 0;
+ for (int i = 0; i < sizeof(*ip) / sizeof(__u16); ++i) {
+ sum4 += ((__u16*)ip)[i];
+ }
+ // Note that sum4 is guaranteed to be non-zero by virtue of ip4->version == 4
+ sum4 = (sum4 & 0xFFFF) + (sum4 >> 16); // collapse u32 into range 1 .. 0x1FFFE
+ sum4 = (sum4 & 0xFFFF) + (sum4 >> 16); // collapse any potential carry into u16
+ // for a correct checksum we should get *a* zero, but sum4 must be positive, ie 0xFFFF
+ if (sum4 != 0xFFFF) TC_PUNT(CHECKSUM);
+
+ // Minimum IPv4 total length is the size of the header
+ if (ntohs(ip->tot_len) < sizeof(*ip)) TC_PUNT(TRUNCATED_IPV4);
+
+ // We are incapable of dealing with IPv4 fragments
+ if (ip->frag_off & ~htons(IP_DF)) TC_PUNT(IS_IP_FRAG);
+
+ // Cannot decrement during forward if already zero or would be zero,
+ // Let the kernel's stack handle these cases and generate appropriate ICMP errors.
+ if (ip->ttl <= 1) TC_PUNT(LOW_TTL);
+
+ // If we cannot update the 'last_used' field due to lack of bpf_ktime_get_boot_ns() helper,
+ // then it is not safe to offload UDP due to the small conntrack timeouts, as such,
+ // in such a situation we can only support TCP. This also has the added nice benefit of
+ // using a separate error counter, and thus making it obvious which version of the program
+ // is loaded.
+ if (!updatetime && ip->protocol != IPPROTO_TCP) TC_PUNT(NON_TCP);
+
+ // We do not support offloading anything besides IPv4 TCP and UDP, due to need for NAT,
+ // but no need to check this if !updatetime due to check immediately above.
+ if (updatetime && (ip->protocol != IPPROTO_TCP) && (ip->protocol != IPPROTO_UDP))
+ TC_PUNT(NON_TCP_UDP);
+
+ // We want to make sure that the compiler will, in the !updatetime case, entirely optimize
+ // out all the non-tcp logic. Also note that at this point is_udp === !is_tcp.
+ const bool is_tcp = !updatetime || (ip->protocol == IPPROTO_TCP);
+
+ // This is a bit of a hack to make things easier on the bpf verifier.
+ // (In particular I believe the Linux 4.14 kernel's verifier can get confused later on about
+ // what offsets into the packet are valid and can spuriously reject the program, this is
+ // because it fails to realize that is_tcp && !is_tcp is impossible)
+ //
+ // For both TCP & UDP we'll need to read and modify the src/dst ports, which so happen to
+ // always be in the first 4 bytes of the L4 header. Additionally for UDP we'll need access
+ // to the checksum field which is in bytes 7 and 8. While for TCP we'll need to read the
+ // TCP flags (at offset 13) and access to the checksum field (2 bytes at offset 16).
+ // As such we *always* need access to at least 8 bytes.
+ if (data + l2_header_size + sizeof(*ip) + 8 > data_end) TC_PUNT(SHORT_L4_HEADER);
+
+ // We're forcing the compiler to emit two copies of the following code, optimized
+ // separately for is_tcp being true or false. This simplifies the resulting bpf
+ // byte code sufficiently that the 4.14 bpf verifier is able to keep track of things.
+ // Without this (updatetime == true) case would fail to bpf verify on 4.14 even
+ // if the underlying requisite kernel support (bpf_ktime_get_boot_ns) was backported.
+ if (is_tcp) {
+ return do_forward4_bottom(skb, l2_header_size, data, data_end, eth, ip,
+ is_ethernet, downstream, updatetime, /* is_tcp */ true);
+ } else {
+ return do_forward4_bottom(skb, l2_header_size, data, data_end, eth, ip,
+ is_ethernet, downstream, updatetime, /* is_tcp */ false);
+ }
+}
+
// Full featured (required) implementations for 5.8+ kernels (these are S+ by definition)
DEFINE_BPF_PROG_KVER("schedcls/tether_downstream4_rawip$5_8", AID_ROOT, AID_NETWORK_STACK,
diff --git a/bpf_progs/test.c b/bpf_progs/test.c
index c9c73f1..f2fcc8c 100644
--- a/bpf_progs/test.c
+++ b/bpf_progs/test.c
@@ -18,8 +18,8 @@
#include <linux/in.h>
#include <linux/ip.h>
-// The resulting .o needs to load on the Android S bpfloader v0.2
-#define BPFLOADER_MIN_VER 2u
+// The resulting .o needs to load on the Android S bpfloader
+#define BPFLOADER_MIN_VER BPFLOADER_S_VERSION
#include "bpf_helpers.h"
#include "bpf_net_helpers.h"
diff --git a/framework-t/Android.bp b/framework-t/Android.bp
index f46d887..1e508a0 100644
--- a/framework-t/Android.bp
+++ b/framework-t/Android.bp
@@ -19,6 +19,9 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
+// Include build rules from Sources.bp
+build = ["Sources.bp"]
+
java_defaults {
name: "enable-framework-connectivity-t-targets",
enabled: true,
diff --git a/framework-t/Sources.bp b/framework-t/Sources.bp
new file mode 100644
index 0000000..b30ee80
--- /dev/null
+++ b/framework-t/Sources.bp
@@ -0,0 +1,168 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+// NetworkStats related libraries.
+
+filegroup {
+ name: "framework-connectivity-netstats-internal-sources",
+ srcs: [
+ "src/android/app/usage/*.java",
+ "src/android/net/DataUsageRequest.*",
+ "src/android/net/INetworkStatsService.aidl",
+ "src/android/net/INetworkStatsSession.aidl",
+ "src/android/net/NetworkIdentity.java",
+ "src/android/net/NetworkIdentitySet.java",
+ "src/android/net/NetworkStateSnapshot.*",
+ "src/android/net/NetworkStats.*",
+ "src/android/net/NetworkStatsAccess.*",
+ "src/android/net/NetworkStatsCollection.*",
+ "src/android/net/NetworkStatsHistory.*",
+ "src/android/net/NetworkTemplate.*",
+ "src/android/net/TrafficStats.java",
+ "src/android/net/UnderlyingNetworkInfo.*",
+ "src/android/net/netstats/**/*.*",
+ ],
+ path: "src",
+ visibility: [
+ "//visibility:private",
+ ],
+}
+
+filegroup {
+ name: "framework-connectivity-netstats-sources",
+ srcs: [
+ ":framework-connectivity-netstats-internal-sources",
+ ],
+ visibility: [
+ "//visibility:private",
+ ],
+}
+
+// Nsd related libraries.
+
+filegroup {
+ name: "framework-connectivity-nsd-internal-sources",
+ srcs: [
+ "src/android/net/nsd/*.aidl",
+ "src/android/net/nsd/*.java",
+ ],
+ path: "src",
+ visibility: [
+ "//visibility:private",
+ ],
+}
+
+filegroup {
+ name: "framework-connectivity-nsd-sources",
+ srcs: [
+ ":framework-connectivity-nsd-internal-sources",
+ ],
+ visibility: [
+ "//visibility:private",
+ ],
+}
+
+// IpSec related libraries.
+
+filegroup {
+ name: "framework-connectivity-ipsec-sources",
+ srcs: [
+ "src/android/net/IIpSecService.aidl",
+ "src/android/net/IpSec*.*",
+ ],
+ path: "src",
+ visibility: [
+ "//visibility:private",
+ ],
+}
+
+// Ethernet related libraries.
+
+filegroup {
+ name: "framework-connectivity-ethernet-sources",
+ srcs: [
+ "src/android/net/EthernetManager.java",
+ "src/android/net/EthernetNetworkManagementException.java",
+ "src/android/net/EthernetNetworkManagementException.aidl",
+ "src/android/net/EthernetNetworkSpecifier.java",
+ "src/android/net/EthernetNetworkUpdateRequest.java",
+ "src/android/net/EthernetNetworkUpdateRequest.aidl",
+ "src/android/net/IEthernetManager.aidl",
+ "src/android/net/IEthernetServiceListener.aidl",
+ "src/android/net/INetworkInterfaceOutcomeReceiver.aidl",
+ "src/android/net/ITetheredInterfaceCallback.aidl",
+ ],
+ path: "src",
+ visibility: [
+ "//visibility:private",
+ ],
+}
+
+// Connectivity-T common libraries.
+
+filegroup {
+ name: "framework-connectivity-tiramisu-internal-sources",
+ srcs: [
+ "src/android/net/ConnectivityFrameworkInitializerTiramisu.java",
+ ],
+ path: "src",
+ visibility: [
+ "//visibility:private",
+ ],
+}
+
+filegroup {
+ name: "framework-connectivity-tiramisu-updatable-sources",
+ srcs: [
+ ":framework-connectivity-ethernet-sources",
+ ":framework-connectivity-ipsec-sources",
+ ":framework-connectivity-netstats-sources",
+ ":framework-connectivity-nsd-sources",
+ ":framework-connectivity-tiramisu-internal-sources",
+ ],
+ visibility: [
+ "//frameworks/base",
+ "//packages/modules/Connectivity:__subpackages__",
+ ],
+}
+
+cc_library_shared {
+ name: "libframework-connectivity-tiramisu-jni",
+ min_sdk_version: "30",
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-unused-parameter",
+ // Don't warn about S API usage even with
+ // min_sdk 30: the library is only loaded
+ // on S+ devices
+ "-Wno-unguarded-availability",
+ "-Wthread-safety",
+ ],
+ srcs: [
+ "jni/android_net_TrafficStats.cpp",
+ "jni/onload.cpp",
+ ],
+ shared_libs: [
+ "libandroid",
+ "liblog",
+ "libnativehelper",
+ ],
+ stl: "none",
+ apex_available: [
+ "com.android.tethering",
+ ],
+}
diff --git a/framework-t/api/current.txt b/framework-t/api/current.txt
index 4fefa0a..eb77288 100644
--- a/framework-t/api/current.txt
+++ b/framework-t/api/current.txt
@@ -3,7 +3,7 @@
public final class NetworkStats implements java.lang.AutoCloseable {
method public void close();
- method public boolean getNextBucket(android.app.usage.NetworkStats.Bucket);
+ method public boolean getNextBucket(@Nullable android.app.usage.NetworkStats.Bucket);
method public boolean hasNextBucket();
}
@@ -40,21 +40,21 @@
}
public class NetworkStatsManager {
- method @WorkerThread public android.app.usage.NetworkStats queryDetails(int, String, long, long) throws android.os.RemoteException, java.lang.SecurityException;
- method @WorkerThread public android.app.usage.NetworkStats queryDetailsForUid(int, String, long, long, int) throws java.lang.SecurityException;
- method @WorkerThread public android.app.usage.NetworkStats queryDetailsForUidTag(int, String, long, long, int, int) throws java.lang.SecurityException;
- method @WorkerThread public android.app.usage.NetworkStats queryDetailsForUidTagState(int, String, long, long, int, int, int) throws java.lang.SecurityException;
- method @WorkerThread public android.app.usage.NetworkStats querySummary(int, String, long, long) throws android.os.RemoteException, java.lang.SecurityException;
- method @WorkerThread public android.app.usage.NetworkStats.Bucket querySummaryForDevice(int, String, long, long) throws android.os.RemoteException, java.lang.SecurityException;
- method @WorkerThread public android.app.usage.NetworkStats.Bucket querySummaryForUser(int, String, long, long) throws android.os.RemoteException, java.lang.SecurityException;
- method public void registerUsageCallback(int, String, long, android.app.usage.NetworkStatsManager.UsageCallback);
- method public void registerUsageCallback(int, String, long, android.app.usage.NetworkStatsManager.UsageCallback, @Nullable android.os.Handler);
- method public void unregisterUsageCallback(android.app.usage.NetworkStatsManager.UsageCallback);
+ method @WorkerThread public android.app.usage.NetworkStats queryDetails(int, @Nullable String, long, long) throws android.os.RemoteException, java.lang.SecurityException;
+ method @NonNull @WorkerThread public android.app.usage.NetworkStats queryDetailsForUid(int, @Nullable String, long, long, int) throws java.lang.SecurityException;
+ method @NonNull @WorkerThread public android.app.usage.NetworkStats queryDetailsForUidTag(int, @Nullable String, long, long, int, int) throws java.lang.SecurityException;
+ method @NonNull @WorkerThread public android.app.usage.NetworkStats queryDetailsForUidTagState(int, @Nullable String, long, long, int, int, int) throws java.lang.SecurityException;
+ method @WorkerThread public android.app.usage.NetworkStats querySummary(int, @Nullable String, long, long) throws android.os.RemoteException, java.lang.SecurityException;
+ method @WorkerThread public android.app.usage.NetworkStats.Bucket querySummaryForDevice(int, @Nullable String, long, long) throws android.os.RemoteException, java.lang.SecurityException;
+ method @WorkerThread public android.app.usage.NetworkStats.Bucket querySummaryForUser(int, @Nullable String, long, long) throws android.os.RemoteException, java.lang.SecurityException;
+ method public void registerUsageCallback(int, @Nullable String, long, @NonNull android.app.usage.NetworkStatsManager.UsageCallback);
+ method public void registerUsageCallback(int, @Nullable String, long, @NonNull android.app.usage.NetworkStatsManager.UsageCallback, @Nullable android.os.Handler);
+ method public void unregisterUsageCallback(@NonNull android.app.usage.NetworkStatsManager.UsageCallback);
}
public abstract static class NetworkStatsManager.UsageCallback {
ctor public NetworkStatsManager.UsageCallback();
- method public abstract void onThresholdReached(int, String);
+ method public abstract void onThresholdReached(int, @Nullable String);
}
}
@@ -173,12 +173,12 @@
method public static void incrementOperationCount(int, int);
method public static void setThreadStatsTag(int);
method public static void setThreadStatsUid(int);
- method public static void tagDatagramSocket(java.net.DatagramSocket) throws java.net.SocketException;
- method public static void tagFileDescriptor(java.io.FileDescriptor) throws java.io.IOException;
- method public static void tagSocket(java.net.Socket) throws java.net.SocketException;
- method public static void untagDatagramSocket(java.net.DatagramSocket) throws java.net.SocketException;
- method public static void untagFileDescriptor(java.io.FileDescriptor) throws java.io.IOException;
- method public static void untagSocket(java.net.Socket) throws java.net.SocketException;
+ method public static void tagDatagramSocket(@NonNull java.net.DatagramSocket) throws java.net.SocketException;
+ method public static void tagFileDescriptor(@NonNull java.io.FileDescriptor) throws java.io.IOException;
+ method public static void tagSocket(@NonNull java.net.Socket) throws java.net.SocketException;
+ method public static void untagDatagramSocket(@NonNull java.net.DatagramSocket) throws java.net.SocketException;
+ method public static void untagFileDescriptor(@NonNull java.io.FileDescriptor) throws java.io.IOException;
+ method public static void untagSocket(@NonNull java.net.Socket) throws java.net.SocketException;
field public static final int UNSUPPORTED = -1; // 0xffffffff
}
@@ -188,10 +188,12 @@
public final class NsdManager {
method public void discoverServices(String, int, android.net.nsd.NsdManager.DiscoveryListener);
- method public void discoverServices(@NonNull String, int, @Nullable android.net.Network, @NonNull android.net.nsd.NsdManager.DiscoveryListener);
- method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void discoverServices(@NonNull String, int, @NonNull android.net.NetworkRequest, @NonNull android.net.nsd.NsdManager.DiscoveryListener);
+ method public void discoverServices(@NonNull String, int, @Nullable android.net.Network, @NonNull java.util.concurrent.Executor, @NonNull android.net.nsd.NsdManager.DiscoveryListener);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void discoverServices(@NonNull String, int, @NonNull android.net.NetworkRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.nsd.NsdManager.DiscoveryListener);
method public void registerService(android.net.nsd.NsdServiceInfo, int, android.net.nsd.NsdManager.RegistrationListener);
+ method public void registerService(@NonNull android.net.nsd.NsdServiceInfo, int, @NonNull java.util.concurrent.Executor, @NonNull android.net.nsd.NsdManager.RegistrationListener);
method public void resolveService(android.net.nsd.NsdServiceInfo, android.net.nsd.NsdManager.ResolveListener);
+ method public void resolveService(@NonNull android.net.nsd.NsdServiceInfo, @NonNull java.util.concurrent.Executor, @NonNull android.net.nsd.NsdManager.ResolveListener);
method public void stopServiceDiscovery(android.net.nsd.NsdManager.DiscoveryListener);
method public void unregisterService(android.net.nsd.NsdManager.RegistrationListener);
field public static final String ACTION_NSD_STATE_CHANGED = "android.net.nsd.STATE_CHANGED";
diff --git a/framework-t/api/lint-baseline.txt b/framework-t/api/lint-baseline.txt
index 53e1beb..2996a3e 100644
--- a/framework-t/api/lint-baseline.txt
+++ b/framework-t/api/lint-baseline.txt
@@ -41,86 +41,18 @@
android.net.IpSecTransform.Builder does not declare a `build()` method, but builder classes are expected to
-MissingNullability: android.app.usage.NetworkStats#getNextBucket(android.app.usage.NetworkStats.Bucket) parameter #0:
- Missing nullability on parameter `bucketOut` in method `getNextBucket`
MissingNullability: android.app.usage.NetworkStatsManager#queryDetails(int, String, long, long):
Missing nullability on method `queryDetails` return
-MissingNullability: android.app.usage.NetworkStatsManager#queryDetails(int, String, long, long) parameter #1:
- Missing nullability on parameter `subscriberId` in method `queryDetails`
-MissingNullability: android.app.usage.NetworkStatsManager#queryDetailsForUid(int, String, long, long, int):
- Missing nullability on method `queryDetailsForUid` return
-MissingNullability: android.app.usage.NetworkStatsManager#queryDetailsForUid(int, String, long, long, int) parameter #1:
- Missing nullability on parameter `subscriberId` in method `queryDetailsForUid`
-MissingNullability: android.app.usage.NetworkStatsManager#queryDetailsForUidTag(int, String, long, long, int, int):
- Missing nullability on method `queryDetailsForUidTag` return
-MissingNullability: android.app.usage.NetworkStatsManager#queryDetailsForUidTag(int, String, long, long, int, int) parameter #1:
- Missing nullability on parameter `subscriberId` in method `queryDetailsForUidTag`
-MissingNullability: android.app.usage.NetworkStatsManager#queryDetailsForUidTagState(int, String, long, long, int, int, int):
- Missing nullability on method `queryDetailsForUidTagState` return
-MissingNullability: android.app.usage.NetworkStatsManager#queryDetailsForUidTagState(int, String, long, long, int, int, int) parameter #1:
- Missing nullability on parameter `subscriberId` in method `queryDetailsForUidTagState`
MissingNullability: android.app.usage.NetworkStatsManager#querySummary(int, String, long, long):
Missing nullability on method `querySummary` return
-MissingNullability: android.app.usage.NetworkStatsManager#querySummary(int, String, long, long) parameter #1:
- Missing nullability on parameter `subscriberId` in method `querySummary`
MissingNullability: android.app.usage.NetworkStatsManager#querySummaryForDevice(int, String, long, long):
Missing nullability on method `querySummaryForDevice` return
-MissingNullability: android.app.usage.NetworkStatsManager#querySummaryForDevice(int, String, long, long) parameter #1:
- Missing nullability on parameter `subscriberId` in method `querySummaryForDevice`
MissingNullability: android.app.usage.NetworkStatsManager#querySummaryForUser(int, String, long, long):
Missing nullability on method `querySummaryForUser` return
-MissingNullability: android.app.usage.NetworkStatsManager#querySummaryForUser(int, String, long, long) parameter #1:
- Missing nullability on parameter `subscriberId` in method `querySummaryForUser`
-MissingNullability: android.app.usage.NetworkStatsManager#registerUsageCallback(int, String, long, android.app.usage.NetworkStatsManager.UsageCallback) parameter #1:
- Missing nullability on parameter `subscriberId` in method `registerUsageCallback`
-MissingNullability: android.app.usage.NetworkStatsManager#registerUsageCallback(int, String, long, android.app.usage.NetworkStatsManager.UsageCallback) parameter #3:
- Missing nullability on parameter `callback` in method `registerUsageCallback`
-MissingNullability: android.app.usage.NetworkStatsManager#registerUsageCallback(int, String, long, android.app.usage.NetworkStatsManager.UsageCallback, android.os.Handler) parameter #1:
- Missing nullability on parameter `subscriberId` in method `registerUsageCallback`
-MissingNullability: android.app.usage.NetworkStatsManager#registerUsageCallback(int, String, long, android.app.usage.NetworkStatsManager.UsageCallback, android.os.Handler) parameter #3:
- Missing nullability on parameter `callback` in method `registerUsageCallback`
-MissingNullability: android.app.usage.NetworkStatsManager#unregisterUsageCallback(android.app.usage.NetworkStatsManager.UsageCallback) parameter #0:
- Missing nullability on parameter `callback` in method `unregisterUsageCallback`
-MissingNullability: android.app.usage.NetworkStatsManager.UsageCallback#onThresholdReached(int, String) parameter #1:
- Missing nullability on parameter `subscriberId` in method `onThresholdReached`
MissingNullability: android.net.IpSecAlgorithm#writeToParcel(android.os.Parcel, int) parameter #0:
Missing nullability on parameter `out` in method `writeToParcel`
MissingNullability: android.net.IpSecManager.UdpEncapsulationSocket#getFileDescriptor():
Missing nullability on method `getFileDescriptor` return
-MissingNullability: android.net.TrafficStats#tagDatagramSocket(java.net.DatagramSocket) parameter #0:
- Missing nullability on parameter `socket` in method `tagDatagramSocket`
-MissingNullability: android.net.TrafficStats#tagFileDescriptor(java.io.FileDescriptor) parameter #0:
- Missing nullability on parameter `fd` in method `tagFileDescriptor`
-MissingNullability: android.net.TrafficStats#tagSocket(java.net.Socket) parameter #0:
- Missing nullability on parameter `socket` in method `tagSocket`
-MissingNullability: android.net.TrafficStats#untagDatagramSocket(java.net.DatagramSocket) parameter #0:
- Missing nullability on parameter `socket` in method `untagDatagramSocket`
-MissingNullability: android.net.TrafficStats#untagFileDescriptor(java.io.FileDescriptor) parameter #0:
- Missing nullability on parameter `fd` in method `untagFileDescriptor`
-MissingNullability: android.net.TrafficStats#untagSocket(java.net.Socket) parameter #0:
- Missing nullability on parameter `socket` in method `untagSocket`
-MissingNullability: com.android.internal.util.FileRotator#FileRotator(java.io.File, String, long, long) parameter #0:
- Missing nullability on parameter `basePath` in method `FileRotator`
-MissingNullability: com.android.internal.util.FileRotator#FileRotator(java.io.File, String, long, long) parameter #1:
- Missing nullability on parameter `prefix` in method `FileRotator`
-MissingNullability: com.android.internal.util.FileRotator#dumpAll(java.io.OutputStream) parameter #0:
- Missing nullability on parameter `os` in method `dumpAll`
-MissingNullability: com.android.internal.util.FileRotator#readMatching(com.android.internal.util.FileRotator.Reader, long, long) parameter #0:
- Missing nullability on parameter `reader` in method `readMatching`
-MissingNullability: com.android.internal.util.FileRotator#rewriteActive(com.android.internal.util.FileRotator.Rewriter, long) parameter #0:
- Missing nullability on parameter `rewriter` in method `rewriteActive`
-MissingNullability: com.android.internal.util.FileRotator#rewriteAll(com.android.internal.util.FileRotator.Rewriter) parameter #0:
- Missing nullability on parameter `rewriter` in method `rewriteAll`
-MissingNullability: com.android.internal.util.FileRotator.Reader#read(java.io.InputStream) parameter #0:
- Missing nullability on parameter `in` in method `read`
-MissingNullability: com.android.internal.util.FileRotator.Writer#write(java.io.OutputStream) parameter #0:
- Missing nullability on parameter `out` in method `write`
-MissingNullability: com.android.server.NetworkManagementSocketTagger#kernelToTag(String) parameter #0:
- Missing nullability on parameter `string` in method `kernelToTag`
-MissingNullability: com.android.server.NetworkManagementSocketTagger#tag(java.io.FileDescriptor) parameter #0:
- Missing nullability on parameter `fd` in method `tag`
-MissingNullability: com.android.server.NetworkManagementSocketTagger#untag(java.io.FileDescriptor) parameter #0:
- Missing nullability on parameter `fd` in method `untag`
RethrowRemoteException: android.app.usage.NetworkStatsManager#queryDetails(int, String, long, long):
diff --git a/framework-t/api/module-lib-current.txt b/framework-t/api/module-lib-current.txt
index 1bdd388..c1f7b39 100644
--- a/framework-t/api/module-lib-current.txt
+++ b/framework-t/api/module-lib-current.txt
@@ -4,6 +4,8 @@
public class NetworkStatsManager {
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void forceUpdate();
method public static int getCollapsedRatType(int);
+ method @NonNull @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public android.net.NetworkStats getMobileUidStats();
+ method @NonNull @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public android.net.NetworkStats getWifiUidStats();
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void noteUidForeground(int, boolean);
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void notifyNetworkStatus(@NonNull java.util.List<android.net.Network>, @NonNull java.util.List<android.net.NetworkStateSnapshot>, @Nullable String, @NonNull java.util.List<android.net.UnderlyingNetworkInfo>);
method @NonNull @WorkerThread public android.app.usage.NetworkStats queryDetailsForDevice(@NonNull android.net.NetworkTemplate, long, long);
@@ -32,9 +34,15 @@
}
public class EthernetManager {
+ method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void addEthernetStateListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void addInterfaceStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.EthernetManager.InterfaceStateListener);
+ method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public java.util.List<java.lang.String> getInterfaceList();
+ method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void removeEthernetStateListener(@NonNull java.util.function.IntConsumer);
method public void removeInterfaceStateListener(@NonNull android.net.EthernetManager.InterfaceStateListener);
+ method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setEthernetEnabled(boolean);
method public void setIncludeTestInterfaces(boolean);
+ field public static final int ETHERNET_STATE_DISABLED = 0; // 0x0
+ field public static final int ETHERNET_STATE_ENABLED = 1; // 0x1
field public static final int ROLE_CLIENT = 1; // 0x1
field public static final int ROLE_NONE = 0; // 0x0
field public static final int ROLE_SERVER = 2; // 0x2
@@ -176,6 +184,7 @@
public class TrafficStats {
method public static void attachSocketTagger();
method public static void init(@NonNull android.content.Context);
+ method public static void setThreadStatsTagDownload();
}
public final class UnderlyingNetworkInfo implements android.os.Parcelable {
diff --git a/framework-t/api/system-current.txt b/framework-t/api/system-current.txt
index 0149115..6460fed 100644
--- a/framework-t/api/system-current.txt
+++ b/framework-t/api/system-current.txt
@@ -2,10 +2,8 @@
package android.app.usage {
public class NetworkStatsManager {
- method @NonNull @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public android.net.NetworkStats getMobileUidStats();
- method @NonNull @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public android.net.NetworkStats getWifiUidStats();
- method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STATS_PROVIDER, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void registerNetworkStatsProvider(@NonNull String, @NonNull android.net.netstats.provider.NetworkStatsProvider);
- method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STATS_PROVIDER, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void unregisterNetworkStatsProvider(@NonNull android.net.netstats.provider.NetworkStatsProvider);
+ method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STATS_PROVIDER, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void registerNetworkStatsProvider(@NonNull String, @NonNull android.net.netstats.provider.NetworkStatsProvider);
+ method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STATS_PROVIDER, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void unregisterNetworkStatsProvider(@NonNull android.net.netstats.provider.NetworkStatsProvider);
}
}
@@ -13,10 +11,10 @@
package android.net {
public class EthernetManager {
- method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.MANAGE_ETHERNET_NETWORKS}) public void connectNetwork(@NonNull String, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.BiConsumer<android.net.Network,android.net.EthernetNetworkManagementException>);
- method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.MANAGE_ETHERNET_NETWORKS}) public void disconnectNetwork(@NonNull String, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.BiConsumer<android.net.Network,android.net.EthernetNetworkManagementException>);
+ method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.MANAGE_ETHERNET_NETWORKS}) public void disableInterface(@NonNull String, @Nullable java.util.concurrent.Executor, @Nullable android.os.OutcomeReceiver<java.lang.String,android.net.EthernetNetworkManagementException>);
+ method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.MANAGE_ETHERNET_NETWORKS}) public void enableInterface(@NonNull String, @Nullable java.util.concurrent.Executor, @Nullable android.os.OutcomeReceiver<java.lang.String,android.net.EthernetNetworkManagementException>);
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public android.net.EthernetManager.TetheredInterfaceRequest requestTetheredInterface(@NonNull java.util.concurrent.Executor, @NonNull android.net.EthernetManager.TetheredInterfaceCallback);
- method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.MANAGE_ETHERNET_NETWORKS}) public void updateConfiguration(@NonNull String, @NonNull android.net.EthernetNetworkUpdateRequest, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.BiConsumer<android.net.Network,android.net.EthernetNetworkManagementException>);
+ method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.MANAGE_ETHERNET_NETWORKS}) public void updateConfiguration(@NonNull String, @NonNull android.net.EthernetNetworkUpdateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.os.OutcomeReceiver<java.lang.String,android.net.EthernetNetworkManagementException>);
}
public static interface EthernetManager.TetheredInterfaceCallback {
@@ -113,7 +111,6 @@
public class TrafficStats {
method public static void setThreadStatsTagApp();
method public static void setThreadStatsTagBackup();
- method public static void setThreadStatsTagDownload();
method public static void setThreadStatsTagRestore();
field public static final int TAG_NETWORK_STACK_IMPERSONATION_RANGE_END = -113; // 0xffffff8f
field public static final int TAG_NETWORK_STACK_IMPERSONATION_RANGE_START = -128; // 0xffffff80
diff --git a/framework-t/jni/android_net_TrafficStats.cpp b/framework-t/jni/android_net_TrafficStats.cpp
new file mode 100644
index 0000000..f3c58b1
--- /dev/null
+++ b/framework-t/jni/android_net_TrafficStats.cpp
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/file_descriptor_jni.h>
+#include <android/multinetwork.h>
+#include <nativehelper/JNIHelp.h>
+
+namespace android {
+
+static jint tagSocketFd(JNIEnv* env, jclass, jobject fileDescriptor, jint tag, jint uid) {
+ int fd = AFileDescriptor_getFd(env, fileDescriptor);
+ if (fd == -1) return -EBADF;
+ return android_tag_socket_with_uid(fd, tag, uid);
+}
+
+static jint untagSocketFd(JNIEnv* env, jclass, jobject fileDescriptor) {
+ int fd = AFileDescriptor_getFd(env, fileDescriptor);
+ if (fd == -1) return -EBADF;
+ return android_untag_socket(fd);
+}
+
+static const JNINativeMethod gMethods[] = {
+ /* name, signature, funcPtr */
+ { "native_tagSocketFd", "(Ljava/io/FileDescriptor;II)I", (void*) tagSocketFd },
+ { "native_untagSocketFd", "(Ljava/io/FileDescriptor;)I", (void*) untagSocketFd },
+};
+
+int register_android_net_TrafficStats(JNIEnv* env) {
+ return jniRegisterNativeMethods(env, "android/net/TrafficStats", gMethods, NELEM(gMethods));
+}
+
+}; // namespace android
+
diff --git a/framework-t/jni/onload.cpp b/framework-t/jni/onload.cpp
new file mode 100644
index 0000000..1fb42c6
--- /dev/null
+++ b/framework-t/jni/onload.cpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "FrameworkConnectivityJNI"
+
+#include <log/log.h>
+#include <nativehelper/JNIHelp.h>
+
+namespace android {
+
+int register_android_net_TrafficStats(JNIEnv* env);
+
+extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
+ JNIEnv *env;
+ if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+ __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "ERROR: GetEnv failed");
+ return JNI_ERR;
+ }
+
+ if (register_android_net_TrafficStats(env) < 0) return JNI_ERR;
+
+ return JNI_VERSION_1_6;
+}
+
+}; // namespace android
+
diff --git a/framework-t/src/android/app/usage/NetworkStats.java b/framework-t/src/android/app/usage/NetworkStats.java
new file mode 100644
index 0000000..74fe4bd
--- /dev/null
+++ b/framework-t/src/android/app/usage/NetworkStats.java
@@ -0,0 +1,744 @@
+/**
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package android.app.usage;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.net.INetworkStatsService;
+import android.net.INetworkStatsSession;
+import android.net.NetworkStatsHistory;
+import android.net.NetworkTemplate;
+import android.net.TrafficStats;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.net.module.util.CollectionUtils;
+
+import dalvik.system.CloseGuard;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+
+/**
+ * Class providing enumeration over buckets of network usage statistics. {@link NetworkStats} objects
+ * are returned as results to various queries in {@link NetworkStatsManager}.
+ */
+public final class NetworkStats implements AutoCloseable {
+ private final static String TAG = "NetworkStats";
+
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+
+ /**
+ * Start timestamp of stats collected
+ */
+ private final long mStartTimeStamp;
+
+ /**
+ * End timestamp of stats collected
+ */
+ private final long mEndTimeStamp;
+
+ /**
+ * Non-null array indicates the query enumerates over uids.
+ */
+ private int[] mUids;
+
+ /**
+ * Index of the current uid in mUids when doing uid enumeration or a single uid value,
+ * depending on query type.
+ */
+ private int mUidOrUidIndex;
+
+ /**
+ * Tag id in case if was specified in the query.
+ */
+ private int mTag = android.net.NetworkStats.TAG_NONE;
+
+ /**
+ * State in case it was not specified in the query.
+ */
+ private int mState = Bucket.STATE_ALL;
+
+ /**
+ * The session while the query requires it, null if all the stats have been collected or close()
+ * has been called.
+ */
+ private INetworkStatsSession mSession;
+ private NetworkTemplate mTemplate;
+
+ /**
+ * Results of a summary query.
+ */
+ private android.net.NetworkStats mSummary = null;
+
+ /**
+ * Results of detail queries.
+ */
+ private NetworkStatsHistory mHistory = null;
+
+ /**
+ * Where we are in enumerating over the current result.
+ */
+ private int mEnumerationIndex = 0;
+
+ /**
+ * Recycling entry objects to prevent heap fragmentation.
+ */
+ private android.net.NetworkStats.Entry mRecycledSummaryEntry = null;
+ private NetworkStatsHistory.Entry mRecycledHistoryEntry = null;
+
+ /** @hide */
+ NetworkStats(Context context, NetworkTemplate template, int flags, long startTimestamp,
+ long endTimestamp, INetworkStatsService statsService)
+ throws RemoteException, SecurityException {
+ // Open network stats session
+ mSession = statsService.openSessionForUsageStats(flags, context.getOpPackageName());
+ mCloseGuard.open("close");
+ mTemplate = template;
+ mStartTimeStamp = startTimestamp;
+ mEndTimeStamp = endTimestamp;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ // -------------------------BEGINNING OF PUBLIC API-----------------------------------
+
+ /**
+ * Buckets are the smallest elements of a query result. As some dimensions of a result may be
+ * aggregated (e.g. time or state) some values may be equal across all buckets.
+ */
+ public static class Bucket {
+ /** @hide */
+ @IntDef(prefix = { "STATE_" }, value = {
+ STATE_ALL,
+ STATE_DEFAULT,
+ STATE_FOREGROUND
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface State {}
+
+ /**
+ * Combined usage across all states.
+ */
+ public static final int STATE_ALL = -1;
+
+ /**
+ * Usage not accounted for in any other state.
+ */
+ public static final int STATE_DEFAULT = 0x1;
+
+ /**
+ * Foreground usage.
+ */
+ public static final int STATE_FOREGROUND = 0x2;
+
+ /**
+ * Special UID value for aggregate/unspecified.
+ */
+ public static final int UID_ALL = android.net.NetworkStats.UID_ALL;
+
+ /**
+ * Special UID value for removed apps.
+ */
+ public static final int UID_REMOVED = TrafficStats.UID_REMOVED;
+
+ /**
+ * Special UID value for data usage by tethering.
+ */
+ public static final int UID_TETHERING = TrafficStats.UID_TETHERING;
+
+ /** @hide */
+ @IntDef(prefix = { "METERED_" }, value = {
+ METERED_ALL,
+ METERED_NO,
+ METERED_YES
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Metered {}
+
+ /**
+ * Combined usage across all metered states. Covers metered and unmetered usage.
+ */
+ public static final int METERED_ALL = -1;
+
+ /**
+ * Usage that occurs on an unmetered network.
+ */
+ public static final int METERED_NO = 0x1;
+
+ /**
+ * Usage that occurs on a metered network.
+ *
+ * <p>A network is classified as metered when the user is sensitive to heavy data usage on
+ * that connection.
+ */
+ public static final int METERED_YES = 0x2;
+
+ /** @hide */
+ @IntDef(prefix = { "ROAMING_" }, value = {
+ ROAMING_ALL,
+ ROAMING_NO,
+ ROAMING_YES
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Roaming {}
+
+ /**
+ * Combined usage across all roaming states. Covers both roaming and non-roaming usage.
+ */
+ public static final int ROAMING_ALL = -1;
+
+ /**
+ * Usage that occurs on a home, non-roaming network.
+ *
+ * <p>Any cellular usage in this bucket was incurred while the device was connected to a
+ * tower owned or operated by the user's wireless carrier, or a tower that the user's
+ * wireless carrier has indicated should be treated as a home network regardless.
+ *
+ * <p>This is also the default value for network types that do not support roaming.
+ */
+ public static final int ROAMING_NO = 0x1;
+
+ /**
+ * Usage that occurs on a roaming network.
+ *
+ * <p>Any cellular usage in this bucket as incurred while the device was roaming on another
+ * carrier's network, for which additional charges may apply.
+ */
+ public static final int ROAMING_YES = 0x2;
+
+ /** @hide */
+ @IntDef(prefix = { "DEFAULT_NETWORK_" }, value = {
+ DEFAULT_NETWORK_ALL,
+ DEFAULT_NETWORK_NO,
+ DEFAULT_NETWORK_YES
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DefaultNetworkStatus {}
+
+ /**
+ * Combined usage for this network regardless of default network status.
+ */
+ public static final int DEFAULT_NETWORK_ALL = -1;
+
+ /**
+ * Usage that occurs while this network is not a default network.
+ *
+ * <p>This implies that the app responsible for this usage requested that it occur on a
+ * specific network different from the one(s) the system would have selected for it.
+ */
+ public static final int DEFAULT_NETWORK_NO = 0x1;
+
+ /**
+ * Usage that occurs while this network is a default network.
+ *
+ * <p>This implies that the app either did not select a specific network for this usage,
+ * or it selected a network that the system could have selected for app traffic.
+ */
+ public static final int DEFAULT_NETWORK_YES = 0x2;
+
+ /**
+ * Special TAG value for total data across all tags
+ */
+ public static final int TAG_NONE = android.net.NetworkStats.TAG_NONE;
+
+ private int mUid;
+ private int mTag;
+ private int mState;
+ private int mDefaultNetworkStatus;
+ private int mMetered;
+ private int mRoaming;
+ private long mBeginTimeStamp;
+ private long mEndTimeStamp;
+ private long mRxBytes;
+ private long mRxPackets;
+ private long mTxBytes;
+ private long mTxPackets;
+
+ private static int convertSet(@State int state) {
+ switch (state) {
+ case STATE_ALL: return android.net.NetworkStats.SET_ALL;
+ case STATE_DEFAULT: return android.net.NetworkStats.SET_DEFAULT;
+ case STATE_FOREGROUND: return android.net.NetworkStats.SET_FOREGROUND;
+ }
+ return 0;
+ }
+
+ private static @State int convertState(int networkStatsSet) {
+ switch (networkStatsSet) {
+ case android.net.NetworkStats.SET_ALL : return STATE_ALL;
+ case android.net.NetworkStats.SET_DEFAULT : return STATE_DEFAULT;
+ case android.net.NetworkStats.SET_FOREGROUND : return STATE_FOREGROUND;
+ }
+ return 0;
+ }
+
+ private static int convertUid(int uid) {
+ switch (uid) {
+ case TrafficStats.UID_REMOVED: return UID_REMOVED;
+ case TrafficStats.UID_TETHERING: return UID_TETHERING;
+ }
+ return uid;
+ }
+
+ private static int convertTag(int tag) {
+ switch (tag) {
+ case android.net.NetworkStats.TAG_NONE: return TAG_NONE;
+ }
+ return tag;
+ }
+
+ private static @Metered int convertMetered(int metered) {
+ switch (metered) {
+ case android.net.NetworkStats.METERED_ALL : return METERED_ALL;
+ case android.net.NetworkStats.METERED_NO: return METERED_NO;
+ case android.net.NetworkStats.METERED_YES: return METERED_YES;
+ }
+ return 0;
+ }
+
+ private static @Roaming int convertRoaming(int roaming) {
+ switch (roaming) {
+ case android.net.NetworkStats.ROAMING_ALL : return ROAMING_ALL;
+ case android.net.NetworkStats.ROAMING_NO: return ROAMING_NO;
+ case android.net.NetworkStats.ROAMING_YES: return ROAMING_YES;
+ }
+ return 0;
+ }
+
+ private static @DefaultNetworkStatus int convertDefaultNetworkStatus(
+ int defaultNetworkStatus) {
+ switch (defaultNetworkStatus) {
+ case android.net.NetworkStats.DEFAULT_NETWORK_ALL : return DEFAULT_NETWORK_ALL;
+ case android.net.NetworkStats.DEFAULT_NETWORK_NO: return DEFAULT_NETWORK_NO;
+ case android.net.NetworkStats.DEFAULT_NETWORK_YES: return DEFAULT_NETWORK_YES;
+ }
+ return 0;
+ }
+
+ public Bucket() {
+ }
+
+ /**
+ * Key of the bucket. Usually an app uid or one of the following special values:<p />
+ * <ul>
+ * <li>{@link #UID_REMOVED}</li>
+ * <li>{@link #UID_TETHERING}</li>
+ * <li>{@link android.os.Process#SYSTEM_UID}</li>
+ * </ul>
+ * @return Bucket key.
+ */
+ public int getUid() {
+ return mUid;
+ }
+
+ /**
+ * Tag of the bucket.<p />
+ * @return Bucket tag.
+ */
+ public int getTag() {
+ return mTag;
+ }
+
+ /**
+ * Usage state. One of the following values:<p/>
+ * <ul>
+ * <li>{@link #STATE_ALL}</li>
+ * <li>{@link #STATE_DEFAULT}</li>
+ * <li>{@link #STATE_FOREGROUND}</li>
+ * </ul>
+ * @return Usage state.
+ */
+ public @State int getState() {
+ return mState;
+ }
+
+ /**
+ * Metered state. One of the following values:<p/>
+ * <ul>
+ * <li>{@link #METERED_ALL}</li>
+ * <li>{@link #METERED_NO}</li>
+ * <li>{@link #METERED_YES}</li>
+ * </ul>
+ * <p>A network is classified as metered when the user is sensitive to heavy data usage on
+ * that connection. Apps may warn before using these networks for large downloads. The
+ * metered state can be set by the user within data usage network restrictions.
+ */
+ public @Metered int getMetered() {
+ return mMetered;
+ }
+
+ /**
+ * Roaming state. One of the following values:<p/>
+ * <ul>
+ * <li>{@link #ROAMING_ALL}</li>
+ * <li>{@link #ROAMING_NO}</li>
+ * <li>{@link #ROAMING_YES}</li>
+ * </ul>
+ */
+ public @Roaming int getRoaming() {
+ return mRoaming;
+ }
+
+ /**
+ * Default network status. One of the following values:<p/>
+ * <ul>
+ * <li>{@link #DEFAULT_NETWORK_ALL}</li>
+ * <li>{@link #DEFAULT_NETWORK_NO}</li>
+ * <li>{@link #DEFAULT_NETWORK_YES}</li>
+ * </ul>
+ */
+ public @DefaultNetworkStatus int getDefaultNetworkStatus() {
+ return mDefaultNetworkStatus;
+ }
+
+ /**
+ * Start timestamp of the bucket's time interval. Defined in terms of "Unix time", see
+ * {@link java.lang.System#currentTimeMillis}.
+ * @return Start of interval.
+ */
+ public long getStartTimeStamp() {
+ return mBeginTimeStamp;
+ }
+
+ /**
+ * End timestamp of the bucket's time interval. Defined in terms of "Unix time", see
+ * {@link java.lang.System#currentTimeMillis}.
+ * @return End of interval.
+ */
+ public long getEndTimeStamp() {
+ return mEndTimeStamp;
+ }
+
+ /**
+ * Number of bytes received during the bucket's time interval. Statistics are measured at
+ * the network layer, so they include both TCP and UDP usage.
+ * @return Number of bytes.
+ */
+ public long getRxBytes() {
+ return mRxBytes;
+ }
+
+ /**
+ * Number of bytes transmitted during the bucket's time interval. Statistics are measured at
+ * the network layer, so they include both TCP and UDP usage.
+ * @return Number of bytes.
+ */
+ public long getTxBytes() {
+ return mTxBytes;
+ }
+
+ /**
+ * Number of packets received during the bucket's time interval. Statistics are measured at
+ * the network layer, so they include both TCP and UDP usage.
+ * @return Number of packets.
+ */
+ public long getRxPackets() {
+ return mRxPackets;
+ }
+
+ /**
+ * Number of packets transmitted during the bucket's time interval. Statistics are measured
+ * at the network layer, so they include both TCP and UDP usage.
+ * @return Number of packets.
+ */
+ public long getTxPackets() {
+ return mTxPackets;
+ }
+ }
+
+ /**
+ * Fills the recycled bucket with data of the next bin in the enumeration.
+ * @param bucketOut Bucket to be filled with data. If null, the method does
+ * nothing and returning false.
+ * @return true if successfully filled the bucket, false otherwise.
+ */
+ public boolean getNextBucket(@Nullable Bucket bucketOut) {
+ if (mSummary != null) {
+ return getNextSummaryBucket(bucketOut);
+ } else {
+ return getNextHistoryBucket(bucketOut);
+ }
+ }
+
+ /**
+ * Check if it is possible to ask for a next bucket in the enumeration.
+ * @return true if there is at least one more bucket.
+ */
+ public boolean hasNextBucket() {
+ if (mSummary != null) {
+ return mEnumerationIndex < mSummary.size();
+ } else if (mHistory != null) {
+ return mEnumerationIndex < mHistory.size()
+ || hasNextUid();
+ }
+ return false;
+ }
+
+ /**
+ * Closes the enumeration. Call this method before this object gets out of scope.
+ */
+ @Override
+ public void close() {
+ if (mSession != null) {
+ try {
+ mSession.close();
+ } catch (RemoteException e) {
+ Log.w(TAG, e);
+ // Otherwise, meh
+ }
+ }
+ mSession = null;
+ if (mCloseGuard != null) {
+ mCloseGuard.close();
+ }
+ }
+
+ // -------------------------END OF PUBLIC API-----------------------------------
+
+ /**
+ * Collects device summary results into a Bucket.
+ * @throws RemoteException
+ */
+ Bucket getDeviceSummaryForNetwork() throws RemoteException {
+ mSummary = mSession.getDeviceSummaryForNetwork(mTemplate, mStartTimeStamp, mEndTimeStamp);
+
+ // Setting enumeration index beyond end to avoid accidental enumeration over data that does
+ // not belong to the calling user.
+ mEnumerationIndex = mSummary.size();
+
+ return getSummaryAggregate();
+ }
+
+ /**
+ * Collects summary results and sets summary enumeration mode.
+ * @throws RemoteException
+ */
+ void startSummaryEnumeration() throws RemoteException {
+ mSummary = mSession.getSummaryForAllUid(mTemplate, mStartTimeStamp, mEndTimeStamp,
+ false /* includeTags */);
+ mEnumerationIndex = 0;
+ }
+
+ /**
+ * Collects tagged summary results and sets summary enumeration mode.
+ * @throws RemoteException
+ */
+ void startTaggedSummaryEnumeration() throws RemoteException {
+ mSummary = mSession.getTaggedSummaryForAllUid(mTemplate, mStartTimeStamp, mEndTimeStamp);
+ mEnumerationIndex = 0;
+ }
+
+ /**
+ * Collects history results for uid and resets history enumeration index.
+ */
+ void startHistoryUidEnumeration(int uid, int tag, int state) {
+ mHistory = null;
+ try {
+ mHistory = mSession.getHistoryIntervalForUid(mTemplate, uid,
+ Bucket.convertSet(state), tag, NetworkStatsHistory.FIELD_ALL,
+ mStartTimeStamp, mEndTimeStamp);
+ setSingleUidTagState(uid, tag, state);
+ } catch (RemoteException e) {
+ Log.w(TAG, e);
+ // Leaving mHistory null
+ }
+ mEnumerationIndex = 0;
+ }
+
+ /**
+ * Collects history results for network and resets history enumeration index.
+ */
+ void startHistoryDeviceEnumeration() {
+ try {
+ mHistory = mSession.getHistoryIntervalForNetwork(
+ mTemplate, NetworkStatsHistory.FIELD_ALL, mStartTimeStamp, mEndTimeStamp);
+ } catch (RemoteException e) {
+ Log.w(TAG, e);
+ mHistory = null;
+ }
+ mEnumerationIndex = 0;
+ }
+
+ /**
+ * Starts uid enumeration for current user.
+ * @throws RemoteException
+ */
+ void startUserUidEnumeration() throws RemoteException {
+ // TODO: getRelevantUids should be sensitive to time interval. When that's done,
+ // the filtering logic below can be removed.
+ int[] uids = mSession.getRelevantUids();
+ // Filtering of uids with empty history.
+ final ArrayList<Integer> filteredUids = new ArrayList<>();
+ for (int uid : uids) {
+ try {
+ NetworkStatsHistory history = mSession.getHistoryIntervalForUid(mTemplate, uid,
+ android.net.NetworkStats.SET_ALL, android.net.NetworkStats.TAG_NONE,
+ NetworkStatsHistory.FIELD_ALL, mStartTimeStamp, mEndTimeStamp);
+ if (history != null && history.size() > 0) {
+ filteredUids.add(uid);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error while getting history of uid " + uid, e);
+ }
+ }
+ mUids = CollectionUtils.toIntArray(filteredUids);
+ mUidOrUidIndex = -1;
+ stepHistory();
+ }
+
+ /**
+ * Steps to next uid in enumeration and collects history for that.
+ */
+ private void stepHistory(){
+ if (hasNextUid()) {
+ stepUid();
+ mHistory = null;
+ try {
+ mHistory = mSession.getHistoryIntervalForUid(mTemplate, getUid(),
+ android.net.NetworkStats.SET_ALL, android.net.NetworkStats.TAG_NONE,
+ NetworkStatsHistory.FIELD_ALL, mStartTimeStamp, mEndTimeStamp);
+ } catch (RemoteException e) {
+ Log.w(TAG, e);
+ // Leaving mHistory null
+ }
+ mEnumerationIndex = 0;
+ }
+ }
+
+ private void fillBucketFromSummaryEntry(Bucket bucketOut) {
+ bucketOut.mUid = Bucket.convertUid(mRecycledSummaryEntry.uid);
+ bucketOut.mTag = Bucket.convertTag(mRecycledSummaryEntry.tag);
+ bucketOut.mState = Bucket.convertState(mRecycledSummaryEntry.set);
+ bucketOut.mDefaultNetworkStatus = Bucket.convertDefaultNetworkStatus(
+ mRecycledSummaryEntry.defaultNetwork);
+ bucketOut.mMetered = Bucket.convertMetered(mRecycledSummaryEntry.metered);
+ bucketOut.mRoaming = Bucket.convertRoaming(mRecycledSummaryEntry.roaming);
+ bucketOut.mBeginTimeStamp = mStartTimeStamp;
+ bucketOut.mEndTimeStamp = mEndTimeStamp;
+ bucketOut.mRxBytes = mRecycledSummaryEntry.rxBytes;
+ bucketOut.mRxPackets = mRecycledSummaryEntry.rxPackets;
+ bucketOut.mTxBytes = mRecycledSummaryEntry.txBytes;
+ bucketOut.mTxPackets = mRecycledSummaryEntry.txPackets;
+ }
+
+ /**
+ * Getting the next item in summary enumeration.
+ * @param bucketOut Next item will be set here.
+ * @return true if a next item could be set.
+ */
+ private boolean getNextSummaryBucket(@Nullable Bucket bucketOut) {
+ if (bucketOut != null && mEnumerationIndex < mSummary.size()) {
+ mRecycledSummaryEntry = mSummary.getValues(mEnumerationIndex++, mRecycledSummaryEntry);
+ fillBucketFromSummaryEntry(bucketOut);
+ return true;
+ }
+ return false;
+ }
+
+ Bucket getSummaryAggregate() {
+ if (mSummary == null) {
+ return null;
+ }
+ Bucket bucket = new Bucket();
+ if (mRecycledSummaryEntry == null) {
+ mRecycledSummaryEntry = new android.net.NetworkStats.Entry();
+ }
+ mSummary.getTotal(mRecycledSummaryEntry);
+ fillBucketFromSummaryEntry(bucket);
+ return bucket;
+ }
+
+ /**
+ * Getting the next item in a history enumeration.
+ * @param bucketOut Next item will be set here.
+ * @return true if a next item could be set.
+ */
+ private boolean getNextHistoryBucket(@Nullable Bucket bucketOut) {
+ if (bucketOut != null && mHistory != null) {
+ if (mEnumerationIndex < mHistory.size()) {
+ mRecycledHistoryEntry = mHistory.getValues(mEnumerationIndex++,
+ mRecycledHistoryEntry);
+ bucketOut.mUid = Bucket.convertUid(getUid());
+ bucketOut.mTag = Bucket.convertTag(mTag);
+ bucketOut.mState = mState;
+ bucketOut.mDefaultNetworkStatus = Bucket.DEFAULT_NETWORK_ALL;
+ bucketOut.mMetered = Bucket.METERED_ALL;
+ bucketOut.mRoaming = Bucket.ROAMING_ALL;
+ bucketOut.mBeginTimeStamp = mRecycledHistoryEntry.bucketStart;
+ bucketOut.mEndTimeStamp = mRecycledHistoryEntry.bucketStart +
+ mRecycledHistoryEntry.bucketDuration;
+ bucketOut.mRxBytes = mRecycledHistoryEntry.rxBytes;
+ bucketOut.mRxPackets = mRecycledHistoryEntry.rxPackets;
+ bucketOut.mTxBytes = mRecycledHistoryEntry.txBytes;
+ bucketOut.mTxPackets = mRecycledHistoryEntry.txPackets;
+ return true;
+ } else if (hasNextUid()) {
+ stepHistory();
+ return getNextHistoryBucket(bucketOut);
+ }
+ }
+ return false;
+ }
+
+ // ------------------ UID LOGIC------------------------
+
+ private boolean isUidEnumeration() {
+ return mUids != null;
+ }
+
+ private boolean hasNextUid() {
+ return isUidEnumeration() && (mUidOrUidIndex + 1) < mUids.length;
+ }
+
+ private int getUid() {
+ // Check if uid enumeration.
+ if (isUidEnumeration()) {
+ if (mUidOrUidIndex < 0 || mUidOrUidIndex >= mUids.length) {
+ throw new IndexOutOfBoundsException(
+ "Index=" + mUidOrUidIndex + " mUids.length=" + mUids.length);
+ }
+ return mUids[mUidOrUidIndex];
+ }
+ // Single uid mode.
+ return mUidOrUidIndex;
+ }
+
+ private void setSingleUidTagState(int uid, int tag, int state) {
+ mUidOrUidIndex = uid;
+ mTag = tag;
+ mState = state;
+ }
+
+ private void stepUid() {
+ if (mUids != null) {
+ ++mUidOrUidIndex;
+ }
+ }
+}
diff --git a/framework-t/src/android/app/usage/NetworkStatsManager.java b/framework-t/src/android/app/usage/NetworkStatsManager.java
new file mode 100644
index 0000000..f41475b
--- /dev/null
+++ b/framework-t/src/android/app/usage/NetworkStatsManager.java
@@ -0,0 +1,1238 @@
+/**
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package android.app.usage;
+
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+
+import android.Manifest;
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.annotation.WorkerThread;
+import android.app.usage.NetworkStats.Bucket;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.DataUsageRequest;
+import android.net.INetworkStatsService;
+import android.net.Network;
+import android.net.NetworkStack;
+import android.net.NetworkStateSnapshot;
+import android.net.NetworkTemplate;
+import android.net.UnderlyingNetworkInfo;
+import android.net.netstats.IUsageCallback;
+import android.net.netstats.NetworkStatsDataMigrationUtils;
+import android.net.netstats.provider.INetworkStatsProviderCallback;
+import android.net.netstats.provider.NetworkStatsProvider;
+import android.os.Build;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.NetworkIdentityUtils;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * Provides access to network usage history and statistics. Usage data is collected in
+ * discrete bins of time called 'Buckets'. See {@link NetworkStats.Bucket} for details.
+ * <p />
+ * Queries can define a time interval in the form of start and end timestamps (Long.MIN_VALUE and
+ * Long.MAX_VALUE can be used to simulate open ended intervals). By default, apps can only obtain
+ * data about themselves. See the below note for special cases in which apps can obtain data about
+ * other applications.
+ * <h3>
+ * Summary queries
+ * </h3>
+ * {@link #querySummaryForDevice} <p />
+ * {@link #querySummaryForUser} <p />
+ * {@link #querySummary} <p />
+ * These queries aggregate network usage across the whole interval. Therefore there will be only one
+ * bucket for a particular key, state, metered and roaming combination. In case of the user-wide
+ * and device-wide summaries a single bucket containing the totalised network usage is returned.
+ * <h3>
+ * History queries
+ * </h3>
+ * {@link #queryDetailsForUid} <p />
+ * {@link #queryDetails} <p />
+ * These queries do not aggregate over time but do aggregate over state, metered and roaming.
+ * Therefore there can be multiple buckets for a particular key. However, all Buckets will have
+ * {@code state} {@link NetworkStats.Bucket#STATE_ALL},
+ * {@code defaultNetwork} {@link NetworkStats.Bucket#DEFAULT_NETWORK_ALL},
+ * {@code metered } {@link NetworkStats.Bucket#METERED_ALL},
+ * {@code roaming} {@link NetworkStats.Bucket#ROAMING_ALL}.
+ * <p />
+ * <b>NOTE:</b> Calling {@link #querySummaryForDevice} or accessing stats for apps other than the
+ * calling app requires the permission {@link android.Manifest.permission#PACKAGE_USAGE_STATS},
+ * which is a system-level permission and will not be granted to third-party apps. However,
+ * declaring the permission implies intention to use the API and the user of the device can grant
+ * permission through the Settings application.
+ * <p />
+ * Profile owner apps are automatically granted permission to query data on the profile they manage
+ * (that is, for any query except {@link #querySummaryForDevice}). Device owner apps and carrier-
+ * privileged apps likewise get access to usage data for all users on the device.
+ * <p />
+ * In addition to tethering usage, usage by removed users and apps, and usage by the system
+ * is also included in the results for callers with one of these higher levels of access.
+ * <p />
+ * <b>NOTE:</b> Prior to API level {@value android.os.Build.VERSION_CODES#N}, all calls to these APIs required
+ * the above permission, even to access an app's own data usage, and carrier-privileged apps were
+ * not included.
+ */
+@SystemService(Context.NETWORK_STATS_SERVICE)
+public class NetworkStatsManager {
+ private static final String TAG = "NetworkStatsManager";
+ private static final boolean DBG = false;
+
+ /** @hide */
+ public static final int CALLBACK_LIMIT_REACHED = 0;
+ /** @hide */
+ public static final int CALLBACK_RELEASED = 1;
+
+ /**
+ * Minimum data usage threshold for registering usage callbacks.
+ *
+ * Requests registered with a threshold lower than this will only be triggered once this minimum
+ * is reached.
+ * @hide
+ */
+ public static final long MIN_THRESHOLD_BYTES = 2 * 1_048_576L; // 2MiB
+
+ private final Context mContext;
+ private final INetworkStatsService mService;
+
+ /**
+ * @deprecated Use {@link NetworkStatsDataMigrationUtils#PREFIX_XT}
+ * instead.
+ * @hide
+ */
+ @Deprecated
+ public static final String PREFIX_DEV = "dev";
+
+ /** @hide */
+ public static final int FLAG_POLL_ON_OPEN = 1 << 0;
+ /** @hide */
+ public static final int FLAG_POLL_FORCE = 1 << 1;
+ /** @hide */
+ public static final int FLAG_AUGMENT_WITH_SUBSCRIPTION_PLAN = 1 << 2;
+
+ /**
+ * Virtual RAT type to represent 5G NSA (Non Stand Alone) mode, where the primary cell is
+ * still LTE and network allocates a secondary 5G cell so telephony reports RAT = LTE along
+ * with NR state as connected. This is a concept added by NetworkStats on top of the telephony
+ * constants for backward compatibility of metrics so this should not be overlapped with any of
+ * the {@code TelephonyManager.NETWORK_TYPE_*} constants.
+ *
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static final int NETWORK_TYPE_5G_NSA = -2;
+
+ private int mFlags;
+
+ /** @hide */
+ @VisibleForTesting
+ public NetworkStatsManager(Context context, INetworkStatsService service) {
+ mContext = context;
+ mService = service;
+ setPollOnOpen(true);
+ setAugmentWithSubscriptionPlan(true);
+ }
+
+ /** @hide */
+ public INetworkStatsService getBinder() {
+ return mService;
+ }
+
+ /**
+ * Set poll on open flag to indicate the poll is needed before service gets statistics
+ * result. This is default enabled. However, for any non-privileged caller, the poll might
+ * be omitted in case of rate limiting.
+ *
+ * @param pollOnOpen true if poll is needed.
+ * @hide
+ */
+ // The system will ignore any non-default values for non-privileged
+ // processes, so processes that don't hold the appropriate permissions
+ // can make no use of this API.
+ @SystemApi(client = MODULE_LIBRARIES)
+ @RequiresPermission(anyOf = {
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ android.Manifest.permission.NETWORK_STACK})
+ public void setPollOnOpen(boolean pollOnOpen) {
+ if (pollOnOpen) {
+ mFlags |= FLAG_POLL_ON_OPEN;
+ } else {
+ mFlags &= ~FLAG_POLL_ON_OPEN;
+ }
+ }
+
+ /**
+ * Set poll force flag to indicate that calling any subsequent query method will force a stats
+ * poll.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @SystemApi(client = MODULE_LIBRARIES)
+ public void setPollForce(boolean pollForce) {
+ if (pollForce) {
+ mFlags |= FLAG_POLL_FORCE;
+ } else {
+ mFlags &= ~FLAG_POLL_FORCE;
+ }
+ }
+
+ /** @hide */
+ public void setAugmentWithSubscriptionPlan(boolean augmentWithSubscriptionPlan) {
+ if (augmentWithSubscriptionPlan) {
+ mFlags |= FLAG_AUGMENT_WITH_SUBSCRIPTION_PLAN;
+ } else {
+ mFlags &= ~FLAG_AUGMENT_WITH_SUBSCRIPTION_PLAN;
+ }
+ }
+
+ /**
+ * Query network usage statistics summaries.
+ *
+ * Result is summarised data usage for the whole
+ * device. Result is a single Bucket aggregated over time, state, uid, tag, metered, and
+ * roaming. This means the bucket's start and end timestamp will be the same as the
+ * 'startTime' and 'endTime' arguments. State is going to be
+ * {@link NetworkStats.Bucket#STATE_ALL}, uid {@link NetworkStats.Bucket#UID_ALL},
+ * tag {@link NetworkStats.Bucket#TAG_NONE},
+ * default network {@link NetworkStats.Bucket#DEFAULT_NETWORK_ALL},
+ * metered {@link NetworkStats.Bucket#METERED_ALL},
+ * and roaming {@link NetworkStats.Bucket#ROAMING_ALL}.
+ * This may take a long time, and apps should avoid calling this on their main thread.
+ *
+ * @param template Template used to match networks. See {@link NetworkTemplate}.
+ * @param startTime Start of period, in milliseconds since the Unix epoch, see
+ * {@link java.lang.System#currentTimeMillis}.
+ * @param endTime End of period, in milliseconds since the Unix epoch, see
+ * {@link java.lang.System#currentTimeMillis}.
+ * @return Bucket Summarised data usage.
+ *
+ * @hide
+ */
+ @NonNull
+ @WorkerThread
+ @SystemApi(client = MODULE_LIBRARIES)
+ public Bucket querySummaryForDevice(@NonNull NetworkTemplate template,
+ long startTime, long endTime) {
+ Objects.requireNonNull(template);
+ try {
+ NetworkStats stats =
+ new NetworkStats(mContext, template, mFlags, startTime, endTime, mService);
+ Bucket bucket = stats.getDeviceSummaryForNetwork();
+ stats.close();
+ return bucket;
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return null; // To make the compiler happy.
+ }
+
+ /**
+ * Query network usage statistics summaries. Result is summarised data usage for the whole
+ * device. Result is a single Bucket aggregated over time, state, uid, tag, metered, and
+ * roaming. This means the bucket's start and end timestamp are going to be the same as the
+ * 'startTime' and 'endTime' parameters. State is going to be
+ * {@link NetworkStats.Bucket#STATE_ALL}, uid {@link NetworkStats.Bucket#UID_ALL},
+ * tag {@link NetworkStats.Bucket#TAG_NONE},
+ * default network {@link NetworkStats.Bucket#DEFAULT_NETWORK_ALL},
+ * metered {@link NetworkStats.Bucket#METERED_ALL},
+ * and roaming {@link NetworkStats.Bucket#ROAMING_ALL}.
+ * This may take a long time, and apps should avoid calling this on their main thread.
+ *
+ * @param networkType As defined in {@link ConnectivityManager}, e.g.
+ * {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI}
+ * etc.
+ * @param subscriberId If applicable, the subscriber id of the network interface.
+ * <p>Starting with API level 29, the {@code subscriberId} is guarded by
+ * additional restrictions. Calling apps that do not meet the new
+ * requirements to access the {@code subscriberId} can provide a {@code
+ * null} value when querying for the mobile network type to receive usage
+ * for all mobile networks. For additional details see {@link
+ * TelephonyManager#getSubscriberId()}.
+ * <p>Starting with API level 31, calling apps can provide a
+ * {@code subscriberId} with wifi network type to receive usage for
+ * wifi networks which is under the given subscription if applicable.
+ * Otherwise, pass {@code null} when querying all wifi networks.
+ * @param startTime Start of period. Defined in terms of "Unix time", see
+ * {@link java.lang.System#currentTimeMillis}.
+ * @param endTime End of period. Defined in terms of "Unix time", see
+ * {@link java.lang.System#currentTimeMillis}.
+ * @return Bucket object or null if permissions are insufficient or error happened during
+ * statistics collection.
+ */
+ @WorkerThread
+ public Bucket querySummaryForDevice(int networkType, @Nullable String subscriberId,
+ long startTime, long endTime) throws SecurityException, RemoteException {
+ NetworkTemplate template;
+ try {
+ template = createTemplate(networkType, subscriberId);
+ } catch (IllegalArgumentException e) {
+ if (DBG) Log.e(TAG, "Cannot create template", e);
+ return null;
+ }
+
+ return querySummaryForDevice(template, startTime, endTime);
+ }
+
+ /**
+ * Query network usage statistics summaries. Result is summarised data usage for all uids
+ * belonging to calling user. Result is a single Bucket aggregated over time, state and uid.
+ * This means the bucket's start and end timestamp are going to be the same as the 'startTime'
+ * and 'endTime' parameters. State is going to be {@link NetworkStats.Bucket#STATE_ALL},
+ * uid {@link NetworkStats.Bucket#UID_ALL}, tag {@link NetworkStats.Bucket#TAG_NONE},
+ * metered {@link NetworkStats.Bucket#METERED_ALL}, and roaming
+ * {@link NetworkStats.Bucket#ROAMING_ALL}.
+ * This may take a long time, and apps should avoid calling this on their main thread.
+ *
+ * @param networkType As defined in {@link ConnectivityManager}, e.g.
+ * {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI}
+ * etc.
+ * @param subscriberId If applicable, the subscriber id of the network interface.
+ * <p>Starting with API level 29, the {@code subscriberId} is guarded by
+ * additional restrictions. Calling apps that do not meet the new
+ * requirements to access the {@code subscriberId} can provide a {@code
+ * null} value when querying for the mobile network type to receive usage
+ * for all mobile networks. For additional details see {@link
+ * TelephonyManager#getSubscriberId()}.
+ * <p>Starting with API level 31, calling apps can provide a
+ * {@code subscriberId} with wifi network type to receive usage for
+ * wifi networks which is under the given subscription if applicable.
+ * Otherwise, pass {@code null} when querying all wifi networks.
+ * @param startTime Start of period. Defined in terms of "Unix time", see
+ * {@link java.lang.System#currentTimeMillis}.
+ * @param endTime End of period. Defined in terms of "Unix time", see
+ * {@link java.lang.System#currentTimeMillis}.
+ * @return Bucket object or null if permissions are insufficient or error happened during
+ * statistics collection.
+ */
+ @WorkerThread
+ public Bucket querySummaryForUser(int networkType, @Nullable String subscriberId,
+ long startTime, long endTime) throws SecurityException, RemoteException {
+ NetworkTemplate template;
+ try {
+ template = createTemplate(networkType, subscriberId);
+ } catch (IllegalArgumentException e) {
+ if (DBG) Log.e(TAG, "Cannot create template", e);
+ return null;
+ }
+
+ NetworkStats stats;
+ stats = new NetworkStats(mContext, template, mFlags, startTime, endTime, mService);
+ stats.startSummaryEnumeration();
+
+ stats.close();
+ return stats.getSummaryAggregate();
+ }
+
+ /**
+ * Query network usage statistics summaries. Result filtered to include only uids belonging to
+ * calling user. Result is aggregated over time, hence all buckets will have the same start and
+ * end timestamps. Not aggregated over state, uid, default network, metered, or roaming. This
+ * means buckets' start and end timestamps are going to be the same as the 'startTime' and
+ * 'endTime' parameters. State, uid, metered, and roaming are going to vary, and tag is going to
+ * be the same.
+ * This may take a long time, and apps should avoid calling this on their main thread.
+ *
+ * @param networkType As defined in {@link ConnectivityManager}, e.g.
+ * {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI}
+ * etc.
+ * @param subscriberId If applicable, the subscriber id of the network interface.
+ * <p>Starting with API level 29, the {@code subscriberId} is guarded by
+ * additional restrictions. Calling apps that do not meet the new
+ * requirements to access the {@code subscriberId} can provide a {@code
+ * null} value when querying for the mobile network type to receive usage
+ * for all mobile networks. For additional details see {@link
+ * TelephonyManager#getSubscriberId()}.
+ * <p>Starting with API level 31, calling apps can provide a
+ * {@code subscriberId} with wifi network type to receive usage for
+ * wifi networks which is under the given subscription if applicable.
+ * Otherwise, pass {@code null} when querying all wifi networks.
+ * @param startTime Start of period. Defined in terms of "Unix time", see
+ * {@link java.lang.System#currentTimeMillis}.
+ * @param endTime End of period. Defined in terms of "Unix time", see
+ * {@link java.lang.System#currentTimeMillis}.
+ * @return Statistics object or null if permissions are insufficient or error happened during
+ * statistics collection.
+ */
+ @WorkerThread
+ public NetworkStats querySummary(int networkType, @Nullable String subscriberId, long startTime,
+ long endTime) throws SecurityException, RemoteException {
+ NetworkTemplate template;
+ try {
+ template = createTemplate(networkType, subscriberId);
+ } catch (IllegalArgumentException e) {
+ if (DBG) Log.e(TAG, "Cannot create template", e);
+ return null;
+ }
+
+ return querySummary(template, startTime, endTime);
+ }
+
+ /**
+ * Query network usage statistics summaries.
+ *
+ * The results will only include traffic made by UIDs belonging to the calling user profile.
+ * The results are aggregated over time, so that all buckets will have the same start and
+ * end timestamps as the passed arguments. Not aggregated over state, uid, default network,
+ * metered, or roaming.
+ * This may take a long time, and apps should avoid calling this on their main thread.
+ *
+ * @param template Template used to match networks. See {@link NetworkTemplate}.
+ * @param startTime Start of period, in milliseconds since the Unix epoch, see
+ * {@link java.lang.System#currentTimeMillis}.
+ * @param endTime End of period, in milliseconds since the Unix epoch, see
+ * {@link java.lang.System#currentTimeMillis}.
+ * @return Statistics which is described above.
+ * @hide
+ */
+ @NonNull
+ @SystemApi(client = MODULE_LIBRARIES)
+ @WorkerThread
+ public NetworkStats querySummary(@NonNull NetworkTemplate template, long startTime,
+ long endTime) throws SecurityException {
+ Objects.requireNonNull(template);
+ try {
+ NetworkStats result =
+ new NetworkStats(mContext, template, mFlags, startTime, endTime, mService);
+ result.startSummaryEnumeration();
+ return result;
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return null; // To make the compiler happy.
+ }
+
+ /**
+ * Query tagged network usage statistics summaries.
+ *
+ * The results will only include tagged traffic made by UIDs belonging to the calling user
+ * profile. The results are aggregated over time, so that all buckets will have the same
+ * start and end timestamps as the passed arguments. Not aggregated over state, uid,
+ * default network, metered, or roaming.
+ * This may take a long time, and apps should avoid calling this on their main thread.
+ *
+ * @param template Template used to match networks. See {@link NetworkTemplate}.
+ * @param startTime Start of period, in milliseconds since the Unix epoch, see
+ * {@link System#currentTimeMillis}.
+ * @param endTime End of period, in milliseconds since the Unix epoch, see
+ * {@link System#currentTimeMillis}.
+ * @return Statistics which is described above.
+ * @hide
+ */
+ @NonNull
+ @SystemApi(client = MODULE_LIBRARIES)
+ @WorkerThread
+ public NetworkStats queryTaggedSummary(@NonNull NetworkTemplate template, long startTime,
+ long endTime) throws SecurityException {
+ Objects.requireNonNull(template);
+ try {
+ NetworkStats result =
+ new NetworkStats(mContext, template, mFlags, startTime, endTime, mService);
+ result.startTaggedSummaryEnumeration();
+ return result;
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return null; // To make the compiler happy.
+ }
+
+ /**
+ * Query usage statistics details for networks matching a given {@link NetworkTemplate}.
+ *
+ * Result is not aggregated over time. This means buckets' start and
+ * end timestamps will be between 'startTime' and 'endTime' parameters.
+ * <p>Only includes buckets whose entire time period is included between
+ * startTime and endTime. Doesn't interpolate or return partial buckets.
+ * Since bucket length is in the order of hours, this
+ * method cannot be used to measure data usage on a fine grained time scale.
+ * This may take a long time, and apps should avoid calling this on their main thread.
+ *
+ * @param template Template used to match networks. See {@link NetworkTemplate}.
+ * @param startTime Start of period, in milliseconds since the Unix epoch, see
+ * {@link java.lang.System#currentTimeMillis}.
+ * @param endTime End of period, in milliseconds since the Unix epoch, see
+ * {@link java.lang.System#currentTimeMillis}.
+ * @return Statistics which is described above.
+ * @hide
+ */
+ @NonNull
+ @SystemApi(client = MODULE_LIBRARIES)
+ @WorkerThread
+ public NetworkStats queryDetailsForDevice(@NonNull NetworkTemplate template,
+ long startTime, long endTime) {
+ Objects.requireNonNull(template);
+ try {
+ final NetworkStats result =
+ new NetworkStats(mContext, template, mFlags, startTime, endTime, mService);
+ result.startHistoryDeviceEnumeration();
+ return result;
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+
+ return null; // To make the compiler happy.
+ }
+
+ /**
+ * Query network usage statistics details for a given uid.
+ * This may take a long time, and apps should avoid calling this on their main thread.
+ *
+ * @see #queryDetailsForUidTagState(int, String, long, long, int, int, int)
+ */
+ @NonNull
+ @WorkerThread
+ public NetworkStats queryDetailsForUid(int networkType, @Nullable String subscriberId,
+ long startTime, long endTime, int uid) throws SecurityException {
+ return queryDetailsForUidTagState(networkType, subscriberId, startTime, endTime, uid,
+ NetworkStats.Bucket.TAG_NONE, NetworkStats.Bucket.STATE_ALL);
+ }
+
+ /** @hide */
+ @NonNull
+ public NetworkStats queryDetailsForUid(@NonNull NetworkTemplate template,
+ long startTime, long endTime, int uid) throws SecurityException {
+ return queryDetailsForUidTagState(template, startTime, endTime, uid,
+ NetworkStats.Bucket.TAG_NONE, NetworkStats.Bucket.STATE_ALL);
+ }
+
+ /**
+ * Query network usage statistics details for a given uid and tag.
+ *
+ * This may take a long time, and apps should avoid calling this on their main thread.
+ * Only usable for uids belonging to calling user. Result is not aggregated over time.
+ * This means buckets' start and end timestamps are going to be between 'startTime' and
+ * 'endTime' parameters. The uid is going to be the same as the 'uid' parameter, the tag
+ * the same as the 'tag' parameter, and the state the same as the 'state' parameter.
+ * defaultNetwork is going to be {@link NetworkStats.Bucket#DEFAULT_NETWORK_ALL},
+ * metered is going to be {@link NetworkStats.Bucket#METERED_ALL}, and
+ * roaming is going to be {@link NetworkStats.Bucket#ROAMING_ALL}.
+ * <p>Only includes buckets that atomically occur in the inclusive time range. Doesn't
+ * interpolate across partial buckets. Since bucket length is in the order of hours, this
+ * method cannot be used to measure data usage on a fine grained time scale.
+ * This may take a long time, and apps should avoid calling this on their main thread.
+ *
+ * @param networkType As defined in {@link ConnectivityManager}, e.g.
+ * {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI}
+ * etc.
+ * @param subscriberId If applicable, the subscriber id of the network interface.
+ * <p>Starting with API level 29, the {@code subscriberId} is guarded by
+ * additional restrictions. Calling apps that do not meet the new
+ * requirements to access the {@code subscriberId} can provide a {@code
+ * null} value when querying for the mobile network type to receive usage
+ * for all mobile networks. For additional details see {@link
+ * TelephonyManager#getSubscriberId()}.
+ * <p>Starting with API level 31, calling apps can provide a
+ * {@code subscriberId} with wifi network type to receive usage for
+ * wifi networks which is under the given subscription if applicable.
+ * Otherwise, pass {@code null} when querying all wifi networks.
+ * @param startTime Start of period. Defined in terms of "Unix time", see
+ * {@link java.lang.System#currentTimeMillis}.
+ * @param endTime End of period. Defined in terms of "Unix time", see
+ * {@link java.lang.System#currentTimeMillis}.
+ * @param uid UID of app
+ * @param tag TAG of interest. Use {@link NetworkStats.Bucket#TAG_NONE} for aggregated data
+ * across all the tags.
+ * @return Statistics which is described above.
+ * @throws SecurityException if permissions are insufficient to read network statistics.
+ */
+ @NonNull
+ @WorkerThread
+ public NetworkStats queryDetailsForUidTag(int networkType, @Nullable String subscriberId,
+ long startTime, long endTime, int uid, int tag) throws SecurityException {
+ return queryDetailsForUidTagState(networkType, subscriberId, startTime, endTime, uid,
+ tag, NetworkStats.Bucket.STATE_ALL);
+ }
+
+ /**
+ * Query network usage statistics details for a given uid, tag, and state.
+ *
+ * Only usable for uids belonging to calling user. Result is not aggregated over time.
+ * This means buckets' start and end timestamps are going to be between 'startTime' and
+ * 'endTime' parameters. The uid is going to be the same as the 'uid' parameter, the tag
+ * the same as the 'tag' parameter, and the state the same as the 'state' parameter.
+ * defaultNetwork is going to be {@link NetworkStats.Bucket#DEFAULT_NETWORK_ALL},
+ * metered is going to be {@link NetworkStats.Bucket#METERED_ALL}, and
+ * roaming is going to be {@link NetworkStats.Bucket#ROAMING_ALL}.
+ * <p>Only includes buckets that atomically occur in the inclusive time range. Doesn't
+ * interpolate across partial buckets. Since bucket length is in the order of hours, this
+ * method cannot be used to measure data usage on a fine grained time scale.
+ * This may take a long time, and apps should avoid calling this on their main thread.
+ *
+ * @param networkType As defined in {@link ConnectivityManager}, e.g.
+ * {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI}
+ * etc.
+ * @param subscriberId If applicable, the subscriber id of the network interface.
+ * <p>Starting with API level 29, the {@code subscriberId} is guarded by
+ * additional restrictions. Calling apps that do not meet the new
+ * requirements to access the {@code subscriberId} can provide a {@code
+ * null} value when querying for the mobile network type to receive usage
+ * for all mobile networks. For additional details see {@link
+ * TelephonyManager#getSubscriberId()}.
+ * <p>Starting with API level 31, calling apps can provide a
+ * {@code subscriberId} with wifi network type to receive usage for
+ * wifi networks which is under the given subscription if applicable.
+ * Otherwise, pass {@code null} when querying all wifi networks.
+ * @param startTime Start of period. Defined in terms of "Unix time", see
+ * {@link java.lang.System#currentTimeMillis}.
+ * @param endTime End of period. Defined in terms of "Unix time", see
+ * {@link java.lang.System#currentTimeMillis}.
+ * @param uid UID of app
+ * @param tag TAG of interest. Use {@link NetworkStats.Bucket#TAG_NONE} for aggregated data
+ * across all the tags.
+ * @param state state of interest. Use {@link NetworkStats.Bucket#STATE_ALL} to aggregate
+ * traffic from all states.
+ * @return Statistics which is described above.
+ * @throws SecurityException if permissions are insufficient to read network statistics.
+ */
+ @NonNull
+ @WorkerThread
+ public NetworkStats queryDetailsForUidTagState(int networkType, @Nullable String subscriberId,
+ long startTime, long endTime, int uid, int tag, int state) throws SecurityException {
+ NetworkTemplate template;
+ template = createTemplate(networkType, subscriberId);
+
+ return queryDetailsForUidTagState(template, startTime, endTime, uid, tag, state);
+ }
+
+ /**
+ * Query network usage statistics details for a given template, uid, tag, and state.
+ *
+ * Only usable for uids belonging to calling user. Result is not aggregated over time.
+ * This means buckets' start and end timestamps are going to be between 'startTime' and
+ * 'endTime' parameters. The uid is going to be the same as the 'uid' parameter, the tag
+ * the same as the 'tag' parameter, and the state the same as the 'state' parameter.
+ * defaultNetwork is going to be {@link NetworkStats.Bucket#DEFAULT_NETWORK_ALL},
+ * metered is going to be {@link NetworkStats.Bucket#METERED_ALL}, and
+ * roaming is going to be {@link NetworkStats.Bucket#ROAMING_ALL}.
+ * <p>Only includes buckets that atomically occur in the inclusive time range. Doesn't
+ * interpolate across partial buckets. Since bucket length is in the order of hours, this
+ * method cannot be used to measure data usage on a fine grained time scale.
+ * This may take a long time, and apps should avoid calling this on their main thread.
+ *
+ * @param template Template used to match networks. See {@link NetworkTemplate}.
+ * @param startTime Start of period, in milliseconds since the Unix epoch, see
+ * {@link java.lang.System#currentTimeMillis}.
+ * @param endTime End of period, in milliseconds since the Unix epoch, see
+ * {@link java.lang.System#currentTimeMillis}.
+ * @param uid UID of app
+ * @param tag TAG of interest. Use {@link NetworkStats.Bucket#TAG_NONE} for aggregated data
+ * across all the tags.
+ * @param state state of interest. Use {@link NetworkStats.Bucket#STATE_ALL} to aggregate
+ * traffic from all states.
+ * @return Statistics which is described above.
+ * @hide
+ */
+ @NonNull
+ @SystemApi(client = MODULE_LIBRARIES)
+ @WorkerThread
+ public NetworkStats queryDetailsForUidTagState(@NonNull NetworkTemplate template,
+ long startTime, long endTime, int uid, int tag, int state) throws SecurityException {
+ Objects.requireNonNull(template);
+ try {
+ final NetworkStats result = new NetworkStats(
+ mContext, template, mFlags, startTime, endTime, mService);
+ result.startHistoryUidEnumeration(uid, tag, state);
+ return result;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error while querying stats for uid=" + uid + " tag=" + tag
+ + " state=" + state, e);
+ e.rethrowFromSystemServer();
+ }
+
+ return null; // To make the compiler happy.
+ }
+
+ /**
+ * Query network usage statistics details. Result filtered to include only uids belonging to
+ * calling user. Result is aggregated over state but not aggregated over time, uid, tag,
+ * metered, nor roaming. This means buckets' start and end timestamps are going to be between
+ * 'startTime' and 'endTime' parameters. State is going to be
+ * {@link NetworkStats.Bucket#STATE_ALL}, uid will vary,
+ * tag {@link NetworkStats.Bucket#TAG_NONE},
+ * default network is going to be {@link NetworkStats.Bucket#DEFAULT_NETWORK_ALL},
+ * metered is going to be {@link NetworkStats.Bucket#METERED_ALL},
+ * and roaming is going to be {@link NetworkStats.Bucket#ROAMING_ALL}.
+ * <p>Only includes buckets that atomically occur in the inclusive time range. Doesn't
+ * interpolate across partial buckets. Since bucket length is in the order of hours, this
+ * method cannot be used to measure data usage on a fine grained time scale.
+ * This may take a long time, and apps should avoid calling this on their main thread.
+ *
+ * @param networkType As defined in {@link ConnectivityManager}, e.g.
+ * {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI}
+ * etc.
+ * @param subscriberId If applicable, the subscriber id of the network interface.
+ * <p>Starting with API level 29, the {@code subscriberId} is guarded by
+ * additional restrictions. Calling apps that do not meet the new
+ * requirements to access the {@code subscriberId} can provide a {@code
+ * null} value when querying for the mobile network type to receive usage
+ * for all mobile networks. For additional details see {@link
+ * TelephonyManager#getSubscriberId()}.
+ * <p>Starting with API level 31, calling apps can provide a
+ * {@code subscriberId} with wifi network type to receive usage for
+ * wifi networks which is under the given subscription if applicable.
+ * Otherwise, pass {@code null} when querying all wifi networks.
+ * @param startTime Start of period. Defined in terms of "Unix time", see
+ * {@link java.lang.System#currentTimeMillis}.
+ * @param endTime End of period. Defined in terms of "Unix time", see
+ * {@link java.lang.System#currentTimeMillis}.
+ * @return Statistics object or null if permissions are insufficient or error happened during
+ * statistics collection.
+ */
+ @WorkerThread
+ public NetworkStats queryDetails(int networkType, @Nullable String subscriberId, long startTime,
+ long endTime) throws SecurityException, RemoteException {
+ NetworkTemplate template;
+ try {
+ template = createTemplate(networkType, subscriberId);
+ } catch (IllegalArgumentException e) {
+ if (DBG) Log.e(TAG, "Cannot create template", e);
+ return null;
+ }
+
+ NetworkStats result;
+ result = new NetworkStats(mContext, template, mFlags, startTime, endTime, mService);
+ result.startUserUidEnumeration();
+ return result;
+ }
+
+ /**
+ * Query realtime mobile network usage statistics.
+ *
+ * Return a snapshot of current UID network statistics, as it applies
+ * to the mobile radios of the device. The snapshot will include any
+ * tethering traffic, video calling data usage and count of
+ * network operations set by {@link TrafficStats#incrementOperationCount}
+ * made over a mobile radio.
+ * The snapshot will not include any statistics that cannot be seen by
+ * the kernel, e.g. statistics reported by {@link NetworkStatsProvider}s.
+ *
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ @RequiresPermission(anyOf = {
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ android.Manifest.permission.NETWORK_STACK})
+ @NonNull public android.net.NetworkStats getMobileUidStats() {
+ try {
+ return mService.getUidStatsForTransport(TRANSPORT_CELLULAR);
+ } catch (RemoteException e) {
+ if (DBG) Log.d(TAG, "Remote exception when get Mobile uid stats");
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Query realtime Wi-Fi network usage statistics.
+ *
+ * Return a snapshot of current UID network statistics, as it applies
+ * to the Wi-Fi radios of the device. The snapshot will include any
+ * tethering traffic, video calling data usage and count of
+ * network operations set by {@link TrafficStats#incrementOperationCount}
+ * made over a Wi-Fi radio.
+ * The snapshot will not include any statistics that cannot be seen by
+ * the kernel, e.g. statistics reported by {@link NetworkStatsProvider}s.
+ *
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ @RequiresPermission(anyOf = {
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ android.Manifest.permission.NETWORK_STACK})
+ @NonNull public android.net.NetworkStats getWifiUidStats() {
+ try {
+ return mService.getUidStatsForTransport(TRANSPORT_WIFI);
+ } catch (RemoteException e) {
+ if (DBG) Log.d(TAG, "Remote exception when get WiFi uid stats");
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Registers to receive notifications about data usage on specified networks.
+ *
+ * <p>The callbacks will continue to be called as long as the process is alive or
+ * {@link #unregisterUsageCallback} is called.
+ *
+ * @param template Template used to match networks. See {@link NetworkTemplate}.
+ * @param thresholdBytes Threshold in bytes to be notified on. Provided values lower than 2MiB
+ * will be clamped for callers except callers with the NETWORK_STACK
+ * permission.
+ * @param executor The executor on which callback will be invoked. The provided {@link Executor}
+ * must run callback sequentially, otherwise the order of callbacks cannot be
+ * guaranteed.
+ * @param callback The {@link UsageCallback} that the system will call when data usage
+ * has exceeded the specified threshold.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ @RequiresPermission(anyOf = {
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ android.Manifest.permission.NETWORK_STACK}, conditional = true)
+ public void registerUsageCallback(@NonNull NetworkTemplate template, long thresholdBytes,
+ @NonNull @CallbackExecutor Executor executor, @NonNull UsageCallback callback) {
+ Objects.requireNonNull(template, "NetworkTemplate cannot be null");
+ Objects.requireNonNull(callback, "UsageCallback cannot be null");
+ Objects.requireNonNull(executor, "Executor cannot be null");
+
+ final DataUsageRequest request = new DataUsageRequest(DataUsageRequest.REQUEST_ID_UNSET,
+ template, thresholdBytes);
+ try {
+ final UsageCallbackWrapper callbackWrapper =
+ new UsageCallbackWrapper(executor, callback);
+ callback.request = mService.registerUsageCallback(
+ mContext.getOpPackageName(), request, callbackWrapper);
+ if (DBG) Log.d(TAG, "registerUsageCallback returned " + callback.request);
+
+ if (callback.request == null) {
+ Log.e(TAG, "Request from callback is null; should not happen");
+ }
+ } catch (RemoteException e) {
+ if (DBG) Log.d(TAG, "Remote exception when registering callback");
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Registers to receive notifications about data usage on specified networks.
+ *
+ * <p>The callbacks will continue to be called as long as the process is live or
+ * {@link #unregisterUsageCallback} is called.
+ *
+ * @param networkType Type of network to monitor. Either
+ {@link ConnectivityManager#TYPE_MOBILE} or {@link ConnectivityManager#TYPE_WIFI}.
+ * @param subscriberId If applicable, the subscriber id of the network interface.
+ * <p>Starting with API level 29, the {@code subscriberId} is guarded by
+ * additional restrictions. Calling apps that do not meet the new
+ * requirements to access the {@code subscriberId} can provide a {@code
+ * null} value when registering for the mobile network type to receive
+ * notifications for all mobile networks. For additional details see {@link
+ * TelephonyManager#getSubscriberId()}.
+ * <p>Starting with API level 31, calling apps can provide a
+ * {@code subscriberId} with wifi network type to receive usage for
+ * wifi networks which is under the given subscription if applicable.
+ * Otherwise, pass {@code null} when querying all wifi networks.
+ * @param thresholdBytes Threshold in bytes to be notified on.
+ * @param callback The {@link UsageCallback} that the system will call when data usage
+ * has exceeded the specified threshold.
+ */
+ public void registerUsageCallback(int networkType, @Nullable String subscriberId,
+ long thresholdBytes, @NonNull UsageCallback callback) {
+ registerUsageCallback(networkType, subscriberId, thresholdBytes, callback,
+ null /* handler */);
+ }
+
+ /**
+ * Registers to receive notifications about data usage on specified networks.
+ *
+ * <p>The callbacks will continue to be called as long as the process is live or
+ * {@link #unregisterUsageCallback} is called.
+ *
+ * @param networkType Type of network to monitor. Either
+ {@link ConnectivityManager#TYPE_MOBILE} or {@link ConnectivityManager#TYPE_WIFI}.
+ * @param subscriberId If applicable, the subscriber id of the network interface.
+ * <p>Starting with API level 29, the {@code subscriberId} is guarded by
+ * additional restrictions. Calling apps that do not meet the new
+ * requirements to access the {@code subscriberId} can provide a {@code
+ * null} value when registering for the mobile network type to receive
+ * notifications for all mobile networks. For additional details see {@link
+ * TelephonyManager#getSubscriberId()}.
+ * <p>Starting with API level 31, calling apps can provide a
+ * {@code subscriberId} with wifi network type to receive usage for
+ * wifi networks which is under the given subscription if applicable.
+ * Otherwise, pass {@code null} when querying all wifi networks.
+ * @param thresholdBytes Threshold in bytes to be notified on.
+ * @param callback The {@link UsageCallback} that the system will call when data usage
+ * has exceeded the specified threshold.
+ * @param handler to dispatch callback events through, otherwise if {@code null} it uses
+ * the calling thread.
+ */
+ public void registerUsageCallback(int networkType, @Nullable String subscriberId,
+ long thresholdBytes, @NonNull UsageCallback callback, @Nullable Handler handler) {
+ NetworkTemplate template = createTemplate(networkType, subscriberId);
+ if (DBG) {
+ Log.d(TAG, "registerUsageCallback called with: {"
+ + " networkType=" + networkType
+ + " subscriberId=" + subscriberId
+ + " thresholdBytes=" + thresholdBytes
+ + " }");
+ }
+
+ final Executor executor = handler == null ? r -> r.run() : r -> handler.post(r);
+
+ registerUsageCallback(template, thresholdBytes, executor, callback);
+ }
+
+ /**
+ * Unregisters callbacks on data usage.
+ *
+ * @param callback The {@link UsageCallback} used when registering.
+ */
+ public void unregisterUsageCallback(@NonNull UsageCallback callback) {
+ if (callback == null || callback.request == null
+ || callback.request.requestId == DataUsageRequest.REQUEST_ID_UNSET) {
+ throw new IllegalArgumentException("Invalid UsageCallback");
+ }
+ try {
+ mService.unregisterUsageRequest(callback.request);
+ } catch (RemoteException e) {
+ if (DBG) Log.d(TAG, "Remote exception when unregistering callback");
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Base class for usage callbacks. Should be extended by applications wanting notifications.
+ */
+ public static abstract class UsageCallback {
+ /**
+ * Called when data usage has reached the given threshold.
+ *
+ * Called by {@code NetworkStatsService} when the registered threshold is reached.
+ * If a caller implements {@link #onThresholdReached(NetworkTemplate)}, the system
+ * will not call {@link #onThresholdReached(int, String)}.
+ *
+ * @param template The {@link NetworkTemplate} that associated with this callback.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public void onThresholdReached(@NonNull NetworkTemplate template) {
+ // Backward compatibility for those who didn't override this function.
+ final int networkType = networkTypeForTemplate(template);
+ if (networkType != ConnectivityManager.TYPE_NONE) {
+ final String subscriberId = template.getSubscriberIds().isEmpty() ? null
+ : template.getSubscriberIds().iterator().next();
+ onThresholdReached(networkType, subscriberId);
+ }
+ }
+
+ /**
+ * Called when data usage has reached the given threshold.
+ */
+ public abstract void onThresholdReached(int networkType, @Nullable String subscriberId);
+
+ /**
+ * @hide used for internal bookkeeping
+ */
+ private DataUsageRequest request;
+
+ /**
+ * Get network type from a template if feasible.
+ *
+ * @param template the target {@link NetworkTemplate}.
+ * @return legacy network type, only supports for the types which is already supported in
+ * {@link #registerUsageCallback(int, String, long, UsageCallback, Handler)}.
+ * {@link ConnectivityManager#TYPE_NONE} for other types.
+ */
+ private static int networkTypeForTemplate(@NonNull NetworkTemplate template) {
+ switch (template.getMatchRule()) {
+ case NetworkTemplate.MATCH_MOBILE:
+ return ConnectivityManager.TYPE_MOBILE;
+ case NetworkTemplate.MATCH_WIFI:
+ return ConnectivityManager.TYPE_WIFI;
+ default:
+ return ConnectivityManager.TYPE_NONE;
+ }
+ }
+ }
+
+ /**
+ * Registers a custom provider of {@link android.net.NetworkStats} to provide network statistics
+ * to the system. To unregister, invoke {@link #unregisterNetworkStatsProvider}.
+ * Note that no de-duplication of statistics between providers is performed, so each provider
+ * must only report network traffic that is not being reported by any other provider. Also note
+ * that the provider cannot be re-registered after unregistering.
+ *
+ * @param tag a human readable identifier of the custom network stats provider. This is only
+ * used for debugging.
+ * @param provider the subclass of {@link NetworkStatsProvider} that needs to be
+ * registered to the system.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_STATS_PROVIDER,
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK})
+ public void registerNetworkStatsProvider(
+ @NonNull String tag,
+ @NonNull NetworkStatsProvider provider) {
+ try {
+ if (provider.getProviderCallbackBinder() != null) {
+ throw new IllegalArgumentException("provider is already registered");
+ }
+ final INetworkStatsProviderCallback cbBinder =
+ mService.registerNetworkStatsProvider(tag, provider.getProviderBinder());
+ provider.setProviderCallbackBinder(cbBinder);
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Unregisters an instance of {@link NetworkStatsProvider}.
+ *
+ * @param provider the subclass of {@link NetworkStatsProvider} that needs to be
+ * unregistered to the system.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_STATS_PROVIDER,
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK})
+ public void unregisterNetworkStatsProvider(@NonNull NetworkStatsProvider provider) {
+ try {
+ provider.getProviderCallbackBinderOrThrow().unregister();
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+
+ private static NetworkTemplate createTemplate(int networkType, @Nullable String subscriberId) {
+ final NetworkTemplate template;
+ switch (networkType) {
+ case ConnectivityManager.TYPE_MOBILE:
+ template = subscriberId == null
+ ? NetworkTemplate.buildTemplateMobileWildcard()
+ : NetworkTemplate.buildTemplateMobileAll(subscriberId);
+ break;
+ case ConnectivityManager.TYPE_WIFI:
+ template = TextUtils.isEmpty(subscriberId)
+ ? NetworkTemplate.buildTemplateWifiWildcard()
+ : NetworkTemplate.buildTemplateWifi(NetworkTemplate.WIFI_NETWORKID_ALL,
+ subscriberId);
+ break;
+ default:
+ throw new IllegalArgumentException("Cannot create template for network type "
+ + networkType + ", subscriberId '"
+ + NetworkIdentityUtils.scrubSubscriberId(subscriberId) + "'.");
+ }
+ return template;
+ }
+
+ /**
+ * Notify {@code NetworkStatsService} about network status changed.
+ *
+ * Notifies NetworkStatsService of network state changes for data usage accounting purposes.
+ *
+ * To avoid races that attribute data usage to wrong network, such as new network with
+ * the same interface after SIM hot-swap, this function will not return until
+ * {@code NetworkStatsService} finishes its work of retrieving traffic statistics from
+ * all data sources.
+ *
+ * @param defaultNetworks the list of all networks that could be used by network traffic that
+ * does not explicitly select a network.
+ * @param networkStateSnapshots a list of {@link NetworkStateSnapshot}s, one for
+ * each network that is currently connected.
+ * @param activeIface the active (i.e., connected) default network interface for the calling
+ * uid. Used to determine on which network future calls to
+ * {@link android.net.TrafficStats#incrementOperationCount} applies to.
+ * @param underlyingNetworkInfos the list of underlying network information for all
+ * currently-connected VPNs.
+ *
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ @RequiresPermission(anyOf = {
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ android.Manifest.permission.NETWORK_STACK})
+ public void notifyNetworkStatus(
+ @NonNull List<Network> defaultNetworks,
+ @NonNull List<NetworkStateSnapshot> networkStateSnapshots,
+ @Nullable String activeIface,
+ @NonNull List<UnderlyingNetworkInfo> underlyingNetworkInfos) {
+ try {
+ Objects.requireNonNull(defaultNetworks);
+ Objects.requireNonNull(networkStateSnapshots);
+ Objects.requireNonNull(underlyingNetworkInfos);
+ mService.notifyNetworkStatus(defaultNetworks.toArray(new Network[0]),
+ networkStateSnapshots.toArray(new NetworkStateSnapshot[0]), activeIface,
+ underlyingNetworkInfos.toArray(new UnderlyingNetworkInfo[0]));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private static class UsageCallbackWrapper extends IUsageCallback.Stub {
+ // Null if unregistered.
+ private volatile UsageCallback mCallback;
+
+ private final Executor mExecutor;
+
+ UsageCallbackWrapper(@NonNull Executor executor, @NonNull UsageCallback callback) {
+ mCallback = callback;
+ mExecutor = executor;
+ }
+
+ @Override
+ public void onThresholdReached(DataUsageRequest request) {
+ // Copy it to a local variable in case mCallback changed inside the if condition.
+ final UsageCallback callback = mCallback;
+ if (callback != null) {
+ mExecutor.execute(() -> callback.onThresholdReached(request.template));
+ } else {
+ Log.e(TAG, "onThresholdReached with released callback for " + request);
+ }
+ }
+
+ @Override
+ public void onCallbackReleased(DataUsageRequest request) {
+ if (DBG) Log.d(TAG, "callback released for " + request);
+ mCallback = null;
+ }
+ }
+
+ /**
+ * Mark given UID as being in foreground for stats purposes.
+ *
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ @RequiresPermission(anyOf = {
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ android.Manifest.permission.NETWORK_STACK})
+ public void noteUidForeground(int uid, boolean uidForeground) {
+ try {
+ mService.noteUidForeground(uid, uidForeground);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Set default value of global alert bytes, the value will be clamped to [128kB, 2MB].
+ *
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ @RequiresPermission(anyOf = {
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ Manifest.permission.NETWORK_STACK})
+ public void setDefaultGlobalAlert(long alertBytes) {
+ try {
+ // TODO: Sync internal naming with the API surface.
+ mService.advisePersistThreshold(alertBytes);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Force update of statistics.
+ *
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ @RequiresPermission(anyOf = {
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ android.Manifest.permission.NETWORK_STACK})
+ public void forceUpdate() {
+ try {
+ mService.forceUpdate();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Set the warning and limit to all registered custom network stats providers.
+ * Note that invocation of any interface will be sent to all providers.
+ *
+ * Asynchronicity notes : because traffic may be happening on the device at the same time, it
+ * doesn't make sense to wait for the warning and limit to be set – a caller still wouldn't
+ * know when exactly it was effective. All that can matter is that it's done quickly. Also,
+ * this method can't fail, so there is no status to return. All providers will see the new
+ * values soon.
+ * As such, this method returns immediately and sends the warning and limit to all providers
+ * as soon as possible through a one-way binder call.
+ *
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ @RequiresPermission(anyOf = {
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ android.Manifest.permission.NETWORK_STACK})
+ public void setStatsProviderWarningAndLimitAsync(@NonNull String iface, long warning,
+ long limit) {
+ try {
+ mService.setStatsProviderWarningAndLimitAsync(iface, warning, limit);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get a RAT type representative of a group of RAT types for network statistics.
+ *
+ * Collapse the given Radio Access Technology (RAT) type into a bucket that
+ * is representative of the original RAT type for network statistics. The
+ * mapping mostly corresponds to {@code TelephonyManager#NETWORK_CLASS_BIT_MASK_*}
+ * but with adaptations specific to the virtual types introduced by
+ * networks stats.
+ *
+ * @param ratType An integer defined in {@code TelephonyManager#NETWORK_TYPE_*}.
+ *
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static int getCollapsedRatType(int ratType) {
+ switch (ratType) {
+ case TelephonyManager.NETWORK_TYPE_GPRS:
+ case TelephonyManager.NETWORK_TYPE_GSM:
+ case TelephonyManager.NETWORK_TYPE_EDGE:
+ case TelephonyManager.NETWORK_TYPE_IDEN:
+ case TelephonyManager.NETWORK_TYPE_CDMA:
+ case TelephonyManager.NETWORK_TYPE_1xRTT:
+ return TelephonyManager.NETWORK_TYPE_GSM;
+ case TelephonyManager.NETWORK_TYPE_EVDO_0:
+ case TelephonyManager.NETWORK_TYPE_EVDO_A:
+ case TelephonyManager.NETWORK_TYPE_EVDO_B:
+ case TelephonyManager.NETWORK_TYPE_EHRPD:
+ case TelephonyManager.NETWORK_TYPE_UMTS:
+ case TelephonyManager.NETWORK_TYPE_HSDPA:
+ case TelephonyManager.NETWORK_TYPE_HSUPA:
+ case TelephonyManager.NETWORK_TYPE_HSPA:
+ case TelephonyManager.NETWORK_TYPE_HSPAP:
+ case TelephonyManager.NETWORK_TYPE_TD_SCDMA:
+ return TelephonyManager.NETWORK_TYPE_UMTS;
+ case TelephonyManager.NETWORK_TYPE_LTE:
+ case TelephonyManager.NETWORK_TYPE_IWLAN:
+ return TelephonyManager.NETWORK_TYPE_LTE;
+ case TelephonyManager.NETWORK_TYPE_NR:
+ return TelephonyManager.NETWORK_TYPE_NR;
+ // Virtual RAT type for 5G NSA mode, see
+ // {@link NetworkStatsManager#NETWORK_TYPE_5G_NSA}.
+ case NetworkStatsManager.NETWORK_TYPE_5G_NSA:
+ return NetworkStatsManager.NETWORK_TYPE_5G_NSA;
+ default:
+ return TelephonyManager.NETWORK_TYPE_UNKNOWN;
+ }
+ }
+}
diff --git a/framework-t/src/android/net/ConnectivityFrameworkInitializerTiramisu.java b/framework-t/src/android/net/ConnectivityFrameworkInitializerTiramisu.java
new file mode 100644
index 0000000..d9c9d74
--- /dev/null
+++ b/framework-t/src/android/net/ConnectivityFrameworkInitializerTiramisu.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.SystemApi;
+import android.app.SystemServiceRegistry;
+import android.app.usage.NetworkStatsManager;
+import android.content.Context;
+import android.net.mdns.aidl.IMDns;
+import android.net.nsd.INsdManager;
+import android.net.nsd.MDnsManager;
+import android.net.nsd.NsdManager;
+
+/**
+ * Class for performing registration for Connectivity services which are exposed via updatable APIs
+ * since Android T.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public final class ConnectivityFrameworkInitializerTiramisu {
+ private ConnectivityFrameworkInitializerTiramisu() {}
+
+ /**
+ * Called by {@link SystemServiceRegistry}'s static initializer and registers NetworkStats, nsd,
+ * ipsec and ethernet services to {@link Context}, so that {@link Context#getSystemService} can
+ * return them.
+ *
+ * @throws IllegalStateException if this is called anywhere besides
+ * {@link SystemServiceRegistry}.
+ */
+ public static void registerServiceWrappers() {
+ SystemServiceRegistry.registerContextAwareService(
+ Context.NSD_SERVICE,
+ NsdManager.class,
+ (context, serviceBinder) -> {
+ INsdManager service = INsdManager.Stub.asInterface(serviceBinder);
+ return new NsdManager(context, service);
+ }
+ );
+
+ SystemServiceRegistry.registerContextAwareService(
+ Context.IPSEC_SERVICE,
+ IpSecManager.class,
+ (context, serviceBinder) -> {
+ IIpSecService service = IIpSecService.Stub.asInterface(serviceBinder);
+ return new IpSecManager(context, service);
+ }
+ );
+
+ SystemServiceRegistry.registerContextAwareService(
+ Context.NETWORK_STATS_SERVICE,
+ NetworkStatsManager.class,
+ (context, serviceBinder) -> {
+ INetworkStatsService service =
+ INetworkStatsService.Stub.asInterface(serviceBinder);
+ return new NetworkStatsManager(context, service);
+ }
+ );
+
+ SystemServiceRegistry.registerContextAwareService(
+ Context.ETHERNET_SERVICE,
+ EthernetManager.class,
+ (context, serviceBinder) -> {
+ IEthernetManager service = IEthernetManager.Stub.asInterface(serviceBinder);
+ return new EthernetManager(context, service);
+ }
+ );
+
+ SystemServiceRegistry.registerStaticService(
+ MDnsManager.MDNS_SERVICE,
+ MDnsManager.class,
+ (serviceBinder) -> {
+ IMDns service = IMDns.Stub.asInterface(serviceBinder);
+ return new MDnsManager(service);
+ }
+ );
+ }
+}
diff --git a/framework-t/src/android/net/DataUsageRequest.aidl b/framework-t/src/android/net/DataUsageRequest.aidl
new file mode 100644
index 0000000..d1937c7
--- /dev/null
+++ b/framework-t/src/android/net/DataUsageRequest.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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;
+
+parcelable DataUsageRequest;
diff --git a/framework-t/src/android/net/DataUsageRequest.java b/framework-t/src/android/net/DataUsageRequest.java
new file mode 100644
index 0000000..b06d515
--- /dev/null
+++ b/framework-t/src/android/net/DataUsageRequest.java
@@ -0,0 +1,112 @@
+/**
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package android.net;
+
+import android.annotation.Nullable;
+import android.net.NetworkTemplate;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Defines a request to register a callbacks. Used to be notified on data usage via
+ * {@link android.app.usage.NetworkStatsManager#registerDataUsageCallback}.
+ * If no {@code uid}s are set, callbacks are restricted to device-owners,
+ * carrier-privileged apps, or system apps.
+ *
+ * @hide
+ */
+public final class DataUsageRequest implements Parcelable {
+
+ public static final String PARCELABLE_KEY = "DataUsageRequest";
+ public static final int REQUEST_ID_UNSET = 0;
+
+ /**
+ * Identifies the request. {@link DataUsageRequest}s should only be constructed by
+ * the Framework and it is used internally to identify the request.
+ */
+ public final int requestId;
+
+ /**
+ * {@link NetworkTemplate} describing the network to monitor.
+ */
+ public final NetworkTemplate template;
+
+ /**
+ * Threshold in bytes to be notified on.
+ */
+ public final long thresholdInBytes;
+
+ public DataUsageRequest(int requestId, NetworkTemplate template, long thresholdInBytes) {
+ this.requestId = requestId;
+ this.template = template;
+ this.thresholdInBytes = thresholdInBytes;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(requestId);
+ dest.writeParcelable(template, flags);
+ dest.writeLong(thresholdInBytes);
+ }
+
+ public static final @android.annotation.NonNull Creator<DataUsageRequest> CREATOR =
+ new Creator<DataUsageRequest>() {
+ @Override
+ public DataUsageRequest createFromParcel(Parcel in) {
+ int requestId = in.readInt();
+ NetworkTemplate template = in.readParcelable(null);
+ long thresholdInBytes = in.readLong();
+ DataUsageRequest result = new DataUsageRequest(requestId, template,
+ thresholdInBytes);
+ return result;
+ }
+
+ @Override
+ public DataUsageRequest[] newArray(int size) {
+ return new DataUsageRequest[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return "DataUsageRequest [ requestId=" + requestId
+ + ", networkTemplate=" + template
+ + ", thresholdInBytes=" + thresholdInBytes + " ]";
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (obj instanceof DataUsageRequest == false) return false;
+ DataUsageRequest that = (DataUsageRequest) obj;
+ return that.requestId == this.requestId
+ && Objects.equals(that.template, this.template)
+ && that.thresholdInBytes == this.thresholdInBytes;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(requestId, template, thresholdInBytes);
+ }
+
+}
diff --git a/framework-t/src/android/net/EthernetManager.java b/framework-t/src/android/net/EthernetManager.java
new file mode 100644
index 0000000..b8070f0
--- /dev/null
+++ b/framework-t/src/android/net/EthernetManager.java
@@ -0,0 +1,705 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.os.Build;
+import android.os.OutcomeReceiver;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.modules.utils.BackgroundThread;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.function.IntConsumer;
+
+/**
+ * A class that manages and configures Ethernet interfaces.
+ *
+ * @hide
+ */
+@SystemApi
+@SystemService(Context.ETHERNET_SERVICE)
+public class EthernetManager {
+ private static final String TAG = "EthernetManager";
+
+ private final IEthernetManager mService;
+ @GuardedBy("mListenerLock")
+ private final ArrayMap<InterfaceStateListener, IEthernetServiceListener>
+ mIfaceServiceListeners = new ArrayMap<>();
+ @GuardedBy("mListenerLock")
+ private final ArrayMap<IntConsumer, IEthernetServiceListener> mStateServiceListeners =
+ new ArrayMap<>();
+ final Object mListenerLock = new Object();
+
+ /**
+ * Indicates that Ethernet is disabled.
+ *
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static final int ETHERNET_STATE_DISABLED = 0;
+
+ /**
+ * Indicates that Ethernet is enabled.
+ *
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static final int ETHERNET_STATE_ENABLED = 1;
+
+ /**
+ * The interface is absent.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static final int STATE_ABSENT = 0;
+
+ /**
+ * The interface is present but link is down.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static final int STATE_LINK_DOWN = 1;
+
+ /**
+ * The interface is present and link is up.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static final int STATE_LINK_UP = 2;
+
+ /** @hide */
+ @IntDef(prefix = "STATE_", value = {STATE_ABSENT, STATE_LINK_DOWN, STATE_LINK_UP})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface InterfaceState {}
+
+ /**
+ * The interface currently does not have any specific role.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static final int ROLE_NONE = 0;
+
+ /**
+ * The interface is in client mode (e.g., connected to the Internet).
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static final int ROLE_CLIENT = 1;
+
+ /**
+ * Ethernet interface is in server mode (e.g., providing Internet access to tethered devices).
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static final int ROLE_SERVER = 2;
+
+ /** @hide */
+ @IntDef(prefix = "ROLE_", value = {ROLE_NONE, ROLE_CLIENT, ROLE_SERVER})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Role {}
+
+ /**
+ * A listener that receives notifications about the state of Ethernet interfaces on the system.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public interface InterfaceStateListener {
+ /**
+ * Called when an Ethernet interface changes state.
+ *
+ * @param iface the name of the interface.
+ * @param state the current state of the interface, or {@link #STATE_ABSENT} if the
+ * interface was removed.
+ * @param role whether the interface is in client mode or server mode.
+ * @param configuration the current IP configuration of the interface.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ void onInterfaceStateChanged(@NonNull String iface, @InterfaceState int state,
+ @Role int role, @Nullable IpConfiguration configuration);
+ }
+
+ /**
+ * A listener interface to receive notification on changes in Ethernet.
+ * This has never been a supported API. Use {@link InterfaceStateListener} instead.
+ * @hide
+ */
+ public interface Listener extends InterfaceStateListener {
+ /**
+ * Called when Ethernet port's availability is changed.
+ * @param iface Ethernet interface name
+ * @param isAvailable {@code true} if Ethernet port exists.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ void onAvailabilityChanged(String iface, boolean isAvailable);
+
+ /** Default implementation for backwards compatibility. Only calls the legacy listener. */
+ default void onInterfaceStateChanged(@NonNull String iface, @InterfaceState int state,
+ @Role int role, @Nullable IpConfiguration configuration) {
+ onAvailabilityChanged(iface, (state >= STATE_LINK_UP));
+ }
+
+ }
+
+ /**
+ * Create a new EthernetManager instance.
+ * Applications will almost always want to use
+ * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
+ * the standard {@link android.content.Context#ETHERNET_SERVICE Context.ETHERNET_SERVICE}.
+ * @hide
+ */
+ public EthernetManager(Context context, IEthernetManager service) {
+ mService = service;
+ }
+
+ /**
+ * Get Ethernet configuration.
+ * @return the Ethernet Configuration, contained in {@link IpConfiguration}.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public IpConfiguration getConfiguration(String iface) {
+ try {
+ return mService.getConfiguration(iface);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Set Ethernet configuration.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public void setConfiguration(@NonNull String iface, @NonNull IpConfiguration config) {
+ try {
+ mService.setConfiguration(iface, config);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Indicates whether the system currently has one or more Ethernet interfaces.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public boolean isAvailable() {
+ return getAvailableInterfaces().length > 0;
+ }
+
+ /**
+ * Indicates whether the system has given interface.
+ *
+ * @param iface Ethernet interface name
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public boolean isAvailable(String iface) {
+ try {
+ return mService.isAvailable(iface);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Adds a listener.
+ * This has never been a supported API. Use {@link #addInterfaceStateListener} instead.
+ *
+ * @param listener A {@link Listener} to add.
+ * @throws IllegalArgumentException If the listener is null.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public void addListener(@NonNull Listener listener) {
+ addListener(listener, BackgroundThread.getExecutor());
+ }
+
+ /**
+ * Adds a listener.
+ * This has never been a supported API. Use {@link #addInterfaceStateListener} instead.
+ *
+ * @param listener A {@link Listener} to add.
+ * @param executor Executor to run callbacks on.
+ * @throws IllegalArgumentException If the listener or executor is null.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public void addListener(@NonNull Listener listener, @NonNull Executor executor) {
+ addInterfaceStateListener(executor, listener);
+ }
+
+ /**
+ * Listen to changes in the state of Ethernet interfaces.
+ *
+ * Adds a listener to receive notification for any state change of all existing Ethernet
+ * interfaces.
+ * <p>{@link Listener#onInterfaceStateChanged} will be triggered immediately for all
+ * existing interfaces upon adding a listener. The same method will be called on the
+ * listener every time any of the interface changes state. In particular, if an
+ * interface is removed, it will be called with state {@link #STATE_ABSENT}.
+ * <p>Use {@link #removeInterfaceStateListener} with the same object to stop listening.
+ *
+ * @param executor Executor to run callbacks on.
+ * @param listener A {@link Listener} to add.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @SystemApi(client = MODULE_LIBRARIES)
+ public void addInterfaceStateListener(@NonNull Executor executor,
+ @NonNull InterfaceStateListener listener) {
+ if (listener == null || executor == null) {
+ throw new NullPointerException("listener and executor must not be null");
+ }
+
+ final IEthernetServiceListener.Stub serviceListener = new IEthernetServiceListener.Stub() {
+ @Override
+ public void onEthernetStateChanged(int state) {}
+
+ @Override
+ public void onInterfaceStateChanged(String iface, int state, int role,
+ IpConfiguration configuration) {
+ executor.execute(() ->
+ listener.onInterfaceStateChanged(iface, state, role, configuration));
+ }
+ };
+ synchronized (mListenerLock) {
+ addServiceListener(serviceListener);
+ mIfaceServiceListeners.put(listener, serviceListener);
+ }
+ }
+
+ @GuardedBy("mListenerLock")
+ private void addServiceListener(@NonNull final IEthernetServiceListener listener) {
+ try {
+ mService.addListener(listener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ }
+
+ /**
+ * Returns an array of available Ethernet interface names.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public String[] getAvailableInterfaces() {
+ try {
+ return mService.getAvailableInterfaces();
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Removes a listener.
+ *
+ * @param listener A {@link Listener} to remove.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public void removeInterfaceStateListener(@NonNull InterfaceStateListener listener) {
+ Objects.requireNonNull(listener);
+ synchronized (mListenerLock) {
+ maybeRemoveServiceListener(mIfaceServiceListeners.remove(listener));
+ }
+ }
+
+ @GuardedBy("mListenerLock")
+ private void maybeRemoveServiceListener(@Nullable final IEthernetServiceListener listener) {
+ if (listener == null) return;
+
+ try {
+ mService.removeListener(listener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Removes a listener.
+ * This has never been a supported API. Use {@link #removeInterfaceStateListener} instead.
+ * @param listener A {@link Listener} to remove.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public void removeListener(@NonNull Listener listener) {
+ if (listener == null) {
+ throw new IllegalArgumentException("listener must not be null");
+ }
+ removeInterfaceStateListener(listener);
+ }
+
+ /**
+ * Whether to treat interfaces created by {@link TestNetworkManager#createTapInterface}
+ * as Ethernet interfaces. The effects of this method apply to any test interfaces that are
+ * already present on the system.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public void setIncludeTestInterfaces(boolean include) {
+ try {
+ mService.setIncludeTestInterfaces(include);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * A request for a tethered interface.
+ */
+ public static class TetheredInterfaceRequest {
+ private final IEthernetManager mService;
+ private final ITetheredInterfaceCallback mCb;
+
+ private TetheredInterfaceRequest(@NonNull IEthernetManager service,
+ @NonNull ITetheredInterfaceCallback cb) {
+ this.mService = service;
+ this.mCb = cb;
+ }
+
+ /**
+ * Release the request, causing the interface to revert back from tethering mode if there
+ * is no other requestor.
+ */
+ public void release() {
+ try {
+ mService.releaseTetheredInterface(mCb);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Callback for {@link #requestTetheredInterface(TetheredInterfaceCallback)}.
+ */
+ public interface TetheredInterfaceCallback {
+ /**
+ * Called when the tethered interface is available.
+ * @param iface The name of the interface.
+ */
+ void onAvailable(@NonNull String iface);
+
+ /**
+ * Called when the tethered interface is now unavailable.
+ */
+ void onUnavailable();
+ }
+
+ /**
+ * Request a tethered interface in tethering mode.
+ *
+ * <p>When this method is called and there is at least one ethernet interface available, the
+ * system will designate one to act as a tethered interface. If there is already a tethered
+ * interface, the existing interface will be used.
+ * @param callback A callback to be called once the request has been fulfilled.
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_STACK,
+ android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK
+ })
+ @NonNull
+ public TetheredInterfaceRequest requestTetheredInterface(@NonNull final Executor executor,
+ @NonNull final TetheredInterfaceCallback callback) {
+ Objects.requireNonNull(callback, "Callback must be non-null");
+ Objects.requireNonNull(executor, "Executor must be non-null");
+ final ITetheredInterfaceCallback cbInternal = new ITetheredInterfaceCallback.Stub() {
+ @Override
+ public void onAvailable(String iface) {
+ executor.execute(() -> callback.onAvailable(iface));
+ }
+
+ @Override
+ public void onUnavailable() {
+ executor.execute(() -> callback.onUnavailable());
+ }
+ };
+
+ try {
+ mService.requestTetheredInterface(cbInternal);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ return new TetheredInterfaceRequest(mService, cbInternal);
+ }
+
+ private static final class NetworkInterfaceOutcomeReceiver
+ extends INetworkInterfaceOutcomeReceiver.Stub {
+ @NonNull
+ private final Executor mExecutor;
+ @NonNull
+ private final OutcomeReceiver<String, EthernetNetworkManagementException> mCallback;
+
+ NetworkInterfaceOutcomeReceiver(
+ @NonNull final Executor executor,
+ @NonNull final OutcomeReceiver<String, EthernetNetworkManagementException>
+ callback) {
+ Objects.requireNonNull(executor, "Pass a non-null executor");
+ Objects.requireNonNull(callback, "Pass a non-null callback");
+ mExecutor = executor;
+ mCallback = callback;
+ }
+
+ @Override
+ public void onResult(@NonNull String iface) {
+ mExecutor.execute(() -> mCallback.onResult(iface));
+ }
+
+ @Override
+ public void onError(@NonNull EthernetNetworkManagementException e) {
+ mExecutor.execute(() -> mCallback.onError(e));
+ }
+ }
+
+ private NetworkInterfaceOutcomeReceiver makeNetworkInterfaceOutcomeReceiver(
+ @Nullable final Executor executor,
+ @Nullable final OutcomeReceiver<String, EthernetNetworkManagementException> callback) {
+ if (null != callback) {
+ Objects.requireNonNull(executor, "Pass a non-null executor, or a null callback");
+ }
+ final NetworkInterfaceOutcomeReceiver proxy;
+ if (null == callback) {
+ proxy = null;
+ } else {
+ proxy = new NetworkInterfaceOutcomeReceiver(executor, callback);
+ }
+ return proxy;
+ }
+
+ /**
+ * Updates the configuration of an automotive device's ethernet network.
+ *
+ * The {@link EthernetNetworkUpdateRequest} {@code request} argument describes how to update the
+ * configuration for this network.
+ * Use {@link StaticIpConfiguration.Builder} to build a {@code StaticIpConfiguration} object for
+ * this network to put inside the {@code request}.
+ * Similarly, use {@link NetworkCapabilities.Builder} to build a {@code NetworkCapabilities}
+ * object for this network to put inside the {@code request}.
+ *
+ * The provided {@link OutcomeReceiver} is called once the operation has finished execution.
+ *
+ * @param iface the name of the interface to act upon.
+ * @param request the {@link EthernetNetworkUpdateRequest} used to set an ethernet network's
+ * {@link StaticIpConfiguration} and {@link NetworkCapabilities} values.
+ * @param executor an {@link Executor} to execute the callback on. Optional if callback is null.
+ * @param callback an optional {@link OutcomeReceiver} to listen for completion of the
+ * operation. On success, {@link OutcomeReceiver#onResult} is called with the
+ * interface name. On error, {@link OutcomeReceiver#onError} is called with more
+ * information about the error.
+ * @throws SecurityException if the process doesn't hold
+ * {@link android.Manifest.permission.MANAGE_ETHERNET_NETWORKS}.
+ * @throws UnsupportedOperationException if the {@link NetworkCapabilities} are updated on a
+ * non-automotive device or this function is called on an
+ * unsupported interface.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ android.Manifest.permission.NETWORK_STACK,
+ android.Manifest.permission.MANAGE_ETHERNET_NETWORKS})
+ public void updateConfiguration(
+ @NonNull String iface,
+ @NonNull EthernetNetworkUpdateRequest request,
+ @Nullable @CallbackExecutor Executor executor,
+ @Nullable OutcomeReceiver<String, EthernetNetworkManagementException> callback) {
+ Objects.requireNonNull(iface, "iface must be non-null");
+ Objects.requireNonNull(request, "request must be non-null");
+ final NetworkInterfaceOutcomeReceiver proxy = makeNetworkInterfaceOutcomeReceiver(
+ executor, callback);
+ try {
+ mService.updateConfiguration(iface, request, proxy);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Enable a network interface.
+ *
+ * Enables a previously disabled network interface. An attempt to enable an already-enabled
+ * interface is ignored.
+ * The provided {@link OutcomeReceiver} is called once the operation has finished execution.
+ *
+ * @param iface the name of the interface to enable.
+ * @param executor an {@link Executor} to execute the callback on. Optional if callback is null.
+ * @param callback an optional {@link OutcomeReceiver} to listen for completion of the
+ * operation. On success, {@link OutcomeReceiver#onResult} is called with the
+ * interface name. On error, {@link OutcomeReceiver#onError} is called with more
+ * information about the error.
+ * @throws SecurityException if the process doesn't hold
+ * {@link android.Manifest.permission.MANAGE_ETHERNET_NETWORKS}.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ android.Manifest.permission.NETWORK_STACK,
+ android.Manifest.permission.MANAGE_ETHERNET_NETWORKS})
+ public void enableInterface(
+ @NonNull String iface,
+ @Nullable @CallbackExecutor Executor executor,
+ @Nullable OutcomeReceiver<String, EthernetNetworkManagementException> callback) {
+ Objects.requireNonNull(iface, "iface must be non-null");
+ final NetworkInterfaceOutcomeReceiver proxy = makeNetworkInterfaceOutcomeReceiver(
+ executor, callback);
+ try {
+ mService.enableInterface(iface, proxy);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Disable a network interface.
+ *
+ * Disables the specified interface. If this interface is in use in a connected
+ * {@link android.net.Network}, then that {@code Network} will be torn down.
+ * The provided {@link OutcomeReceiver} is called once the operation has finished execution.
+ *
+ * @param iface the name of the interface to disable.
+ * @param executor an {@link Executor} to execute the callback on. Optional if callback is null.
+ * @param callback an optional {@link OutcomeReceiver} to listen for completion of the
+ * operation. On success, {@link OutcomeReceiver#onResult} is called with the
+ * interface name. On error, {@link OutcomeReceiver#onError} is called with more
+ * information about the error.
+ * @throws SecurityException if the process doesn't hold
+ * {@link android.Manifest.permission.MANAGE_ETHERNET_NETWORKS}.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ android.Manifest.permission.NETWORK_STACK,
+ android.Manifest.permission.MANAGE_ETHERNET_NETWORKS})
+ public void disableInterface(
+ @NonNull String iface,
+ @Nullable @CallbackExecutor Executor executor,
+ @Nullable OutcomeReceiver<String, EthernetNetworkManagementException> callback) {
+ Objects.requireNonNull(iface, "iface must be non-null");
+ final NetworkInterfaceOutcomeReceiver proxy = makeNetworkInterfaceOutcomeReceiver(
+ executor, callback);
+ try {
+ mService.disableInterface(iface, proxy);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Change ethernet setting.
+ *
+ * @param enabled enable or disable ethernet settings.
+ *
+ * @hide
+ */
+ @RequiresPermission(anyOf = {
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ android.Manifest.permission.NETWORK_STACK,
+ android.Manifest.permission.NETWORK_SETTINGS})
+ @SystemApi(client = MODULE_LIBRARIES)
+ public void setEthernetEnabled(boolean enabled) {
+ try {
+ mService.setEthernetEnabled(enabled);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Listen to changes in the state of ethernet.
+ *
+ * @param executor to run callbacks on.
+ * @param listener to listen ethernet state changed.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @SystemApi(client = MODULE_LIBRARIES)
+ public void addEthernetStateListener(@NonNull Executor executor,
+ @NonNull IntConsumer listener) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(listener);
+ final IEthernetServiceListener.Stub serviceListener = new IEthernetServiceListener.Stub() {
+ @Override
+ public void onEthernetStateChanged(int state) {
+ executor.execute(() -> listener.accept(state));
+ }
+
+ @Override
+ public void onInterfaceStateChanged(String iface, int state, int role,
+ IpConfiguration configuration) {}
+ };
+ synchronized (mListenerLock) {
+ addServiceListener(serviceListener);
+ mStateServiceListeners.put(listener, serviceListener);
+ }
+ }
+
+ /**
+ * Removes a listener.
+ *
+ * @param listener to listen ethernet state changed.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @SystemApi(client = MODULE_LIBRARIES)
+ public void removeEthernetStateListener(@NonNull IntConsumer listener) {
+ Objects.requireNonNull(listener);
+ synchronized (mListenerLock) {
+ maybeRemoveServiceListener(mStateServiceListeners.remove(listener));
+ }
+ }
+
+ /**
+ * Returns an array of existing Ethernet interface names regardless whether the interface
+ * is available or not currently.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @SystemApi(client = MODULE_LIBRARIES)
+ @NonNull
+ public List<String> getInterfaceList() {
+ try {
+ return mService.getInterfaceList();
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+}
diff --git a/framework-t/src/android/net/EthernetNetworkManagementException.aidl b/framework-t/src/android/net/EthernetNetworkManagementException.aidl
new file mode 100644
index 0000000..adf9e5a
--- /dev/null
+++ b/framework-t/src/android/net/EthernetNetworkManagementException.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ package android.net;
+
+ parcelable EthernetNetworkManagementException;
\ No newline at end of file
diff --git a/framework-t/src/android/net/EthernetNetworkManagementException.java b/framework-t/src/android/net/EthernetNetworkManagementException.java
new file mode 100644
index 0000000..a69cc55
--- /dev/null
+++ b/framework-t/src/android/net/EthernetNetworkManagementException.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/** @hide */
+@SystemApi
+public final class EthernetNetworkManagementException
+ extends RuntimeException implements Parcelable {
+
+ /* @hide */
+ public EthernetNetworkManagementException(@NonNull final String errorMessage) {
+ super(errorMessage);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getMessage());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null || getClass() != obj.getClass()) return false;
+ final EthernetNetworkManagementException that = (EthernetNetworkManagementException) obj;
+
+ return Objects.equals(getMessage(), that.getMessage());
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(getMessage());
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<EthernetNetworkManagementException> CREATOR =
+ new Parcelable.Creator<EthernetNetworkManagementException>() {
+ @Override
+ public EthernetNetworkManagementException[] newArray(int size) {
+ return new EthernetNetworkManagementException[size];
+ }
+
+ @Override
+ public EthernetNetworkManagementException createFromParcel(@NonNull Parcel source) {
+ return new EthernetNetworkManagementException(source.readString());
+ }
+ };
+}
diff --git a/framework-t/src/android/net/EthernetNetworkSpecifier.java b/framework-t/src/android/net/EthernetNetworkSpecifier.java
new file mode 100644
index 0000000..e4d6e24
--- /dev/null
+++ b/framework-t/src/android/net/EthernetNetworkSpecifier.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.Objects;
+
+/**
+ * A {@link NetworkSpecifier} used to identify ethernet interfaces.
+ *
+ * @see EthernetManager
+ */
+public final class EthernetNetworkSpecifier extends NetworkSpecifier implements Parcelable {
+
+ /**
+ * Name of the network interface.
+ */
+ @NonNull
+ private final String mInterfaceName;
+
+ /**
+ * Create a new EthernetNetworkSpecifier.
+ * @param interfaceName Name of the ethernet interface the specifier refers to.
+ */
+ public EthernetNetworkSpecifier(@NonNull String interfaceName) {
+ if (TextUtils.isEmpty(interfaceName)) {
+ throw new IllegalArgumentException();
+ }
+ mInterfaceName = interfaceName;
+ }
+
+ /**
+ * Get the name of the ethernet interface the specifier refers to.
+ */
+ @Nullable
+ public String getInterfaceName() {
+ // This may be null in the future to support specifiers based on data other than the
+ // interface name.
+ return mInterfaceName;
+ }
+
+ /** @hide */
+ @Override
+ public boolean canBeSatisfiedBy(@Nullable NetworkSpecifier other) {
+ return equals(other);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (!(o instanceof EthernetNetworkSpecifier)) return false;
+ return TextUtils.equals(mInterfaceName, ((EthernetNetworkSpecifier) o).mInterfaceName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(mInterfaceName);
+ }
+
+ @Override
+ public String toString() {
+ return "EthernetNetworkSpecifier (" + mInterfaceName + ")";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(mInterfaceName);
+ }
+
+ public static final @NonNull Parcelable.Creator<EthernetNetworkSpecifier> CREATOR =
+ new Parcelable.Creator<EthernetNetworkSpecifier>() {
+ public EthernetNetworkSpecifier createFromParcel(Parcel in) {
+ return new EthernetNetworkSpecifier(in.readString());
+ }
+ public EthernetNetworkSpecifier[] newArray(int size) {
+ return new EthernetNetworkSpecifier[size];
+ }
+ };
+}
diff --git a/framework-t/src/android/net/EthernetNetworkUpdateRequest.aidl b/framework-t/src/android/net/EthernetNetworkUpdateRequest.aidl
new file mode 100644
index 0000000..debc348
--- /dev/null
+++ b/framework-t/src/android/net/EthernetNetworkUpdateRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ package android.net;
+
+ parcelable EthernetNetworkUpdateRequest;
\ No newline at end of file
diff --git a/framework-t/src/android/net/EthernetNetworkUpdateRequest.java b/framework-t/src/android/net/EthernetNetworkUpdateRequest.java
new file mode 100644
index 0000000..1691942
--- /dev/null
+++ b/framework-t/src/android/net/EthernetNetworkUpdateRequest.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Represents a request to update an existing Ethernet interface.
+ *
+ * @see EthernetManager#updateConfiguration
+ *
+ * @hide
+ */
+@SystemApi
+public final class EthernetNetworkUpdateRequest implements Parcelable {
+ @Nullable
+ private final IpConfiguration mIpConfig;
+ @Nullable
+ private final NetworkCapabilities mNetworkCapabilities;
+
+ /**
+ * Setting the {@link IpConfiguration} is optional in {@link EthernetNetworkUpdateRequest}.
+ * When set to null, the existing IpConfiguration is not updated.
+ *
+ * @return the new {@link IpConfiguration} or null.
+ */
+ @Nullable
+ public IpConfiguration getIpConfiguration() {
+ return mIpConfig == null ? null : new IpConfiguration(mIpConfig);
+ }
+
+ /**
+ * Setting the {@link NetworkCapabilities} is optional in {@link EthernetNetworkUpdateRequest}.
+ * When set to null, the existing NetworkCapabilities are not updated.
+ *
+ * @return the new {@link NetworkCapabilities} or null.
+ */
+ @Nullable
+ public NetworkCapabilities getNetworkCapabilities() {
+ return mNetworkCapabilities == null ? null : new NetworkCapabilities(mNetworkCapabilities);
+ }
+
+ private EthernetNetworkUpdateRequest(@Nullable final IpConfiguration ipConfig,
+ @Nullable final NetworkCapabilities networkCapabilities) {
+ mIpConfig = ipConfig;
+ mNetworkCapabilities = networkCapabilities;
+ }
+
+ private EthernetNetworkUpdateRequest(@NonNull final Parcel source) {
+ Objects.requireNonNull(source);
+ mIpConfig = source.readParcelable(IpConfiguration.class.getClassLoader(),
+ IpConfiguration.class);
+ mNetworkCapabilities = source.readParcelable(NetworkCapabilities.class.getClassLoader(),
+ NetworkCapabilities.class);
+ }
+
+ /**
+ * Builder used to create {@link EthernetNetworkUpdateRequest} objects.
+ */
+ public static final class Builder {
+ @Nullable
+ private IpConfiguration mBuilderIpConfig;
+ @Nullable
+ private NetworkCapabilities mBuilderNetworkCapabilities;
+
+ public Builder(){}
+
+ /**
+ * Constructor to populate the builder's values with an already built
+ * {@link EthernetNetworkUpdateRequest}.
+ * @param request the {@link EthernetNetworkUpdateRequest} to populate with.
+ */
+ public Builder(@NonNull final EthernetNetworkUpdateRequest request) {
+ Objects.requireNonNull(request);
+ mBuilderIpConfig = null == request.mIpConfig
+ ? null : new IpConfiguration(request.mIpConfig);
+ mBuilderNetworkCapabilities = null == request.mNetworkCapabilities
+ ? null : new NetworkCapabilities(request.mNetworkCapabilities);
+ }
+
+ /**
+ * Set the {@link IpConfiguration} to be used with the {@code Builder}.
+ * @param ipConfig the {@link IpConfiguration} to set.
+ * @return The builder to facilitate chaining.
+ */
+ @NonNull
+ public Builder setIpConfiguration(@Nullable final IpConfiguration ipConfig) {
+ mBuilderIpConfig = ipConfig == null ? null : new IpConfiguration(ipConfig);
+ return this;
+ }
+
+ /**
+ * Set the {@link NetworkCapabilities} to be used with the {@code Builder}.
+ * @param nc the {@link NetworkCapabilities} to set.
+ * @return The builder to facilitate chaining.
+ */
+ @NonNull
+ public Builder setNetworkCapabilities(@Nullable final NetworkCapabilities nc) {
+ mBuilderNetworkCapabilities = nc == null ? null : new NetworkCapabilities(nc);
+ return this;
+ }
+
+ /**
+ * Build {@link EthernetNetworkUpdateRequest} return the current update request.
+ *
+ * @throws IllegalStateException when both mBuilderNetworkCapabilities and mBuilderIpConfig
+ * are null.
+ */
+ @NonNull
+ public EthernetNetworkUpdateRequest build() {
+ if (mBuilderIpConfig == null && mBuilderNetworkCapabilities == null) {
+ throw new IllegalStateException(
+ "Cannot construct an empty EthernetNetworkUpdateRequest");
+ }
+ return new EthernetNetworkUpdateRequest(mBuilderIpConfig, mBuilderNetworkCapabilities);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "EthernetNetworkUpdateRequest{"
+ + "mIpConfig=" + mIpConfig
+ + ", mNetworkCapabilities=" + mNetworkCapabilities + '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ EthernetNetworkUpdateRequest that = (EthernetNetworkUpdateRequest) o;
+
+ return Objects.equals(that.getIpConfiguration(), mIpConfig)
+ && Objects.equals(that.getNetworkCapabilities(), mNetworkCapabilities);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mIpConfig, mNetworkCapabilities);
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeParcelable(mIpConfig, flags);
+ dest.writeParcelable(mNetworkCapabilities, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<EthernetNetworkUpdateRequest> CREATOR =
+ new Parcelable.Creator<EthernetNetworkUpdateRequest>() {
+ @Override
+ public EthernetNetworkUpdateRequest[] newArray(int size) {
+ return new EthernetNetworkUpdateRequest[size];
+ }
+
+ @Override
+ public EthernetNetworkUpdateRequest createFromParcel(@NonNull Parcel source) {
+ return new EthernetNetworkUpdateRequest(source);
+ }
+ };
+}
diff --git a/framework-t/src/android/net/IEthernetManager.aidl b/framework-t/src/android/net/IEthernetManager.aidl
new file mode 100644
index 0000000..c1efc29
--- /dev/null
+++ b/framework-t/src/android/net/IEthernetManager.aidl
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.net.IpConfiguration;
+import android.net.IEthernetServiceListener;
+import android.net.EthernetNetworkManagementException;
+import android.net.EthernetNetworkUpdateRequest;
+import android.net.INetworkInterfaceOutcomeReceiver;
+import android.net.ITetheredInterfaceCallback;
+
+import java.util.List;
+
+/**
+ * Interface that answers queries about, and allows changing
+ * ethernet configuration.
+ */
+/** {@hide} */
+interface IEthernetManager
+{
+ String[] getAvailableInterfaces();
+ IpConfiguration getConfiguration(String iface);
+ void setConfiguration(String iface, in IpConfiguration config);
+ boolean isAvailable(String iface);
+ void addListener(in IEthernetServiceListener listener);
+ void removeListener(in IEthernetServiceListener listener);
+ void setIncludeTestInterfaces(boolean include);
+ void requestTetheredInterface(in ITetheredInterfaceCallback callback);
+ void releaseTetheredInterface(in ITetheredInterfaceCallback callback);
+ void updateConfiguration(String iface, in EthernetNetworkUpdateRequest request,
+ in INetworkInterfaceOutcomeReceiver listener);
+ void enableInterface(String iface, in INetworkInterfaceOutcomeReceiver listener);
+ void disableInterface(String iface, in INetworkInterfaceOutcomeReceiver listener);
+ void setEthernetEnabled(boolean enabled);
+ List<String> getInterfaceList();
+}
diff --git a/framework-t/src/android/net/IEthernetServiceListener.aidl b/framework-t/src/android/net/IEthernetServiceListener.aidl
new file mode 100644
index 0000000..751605b
--- /dev/null
+++ b/framework-t/src/android/net/IEthernetServiceListener.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.net.IpConfiguration;
+
+/** @hide */
+oneway interface IEthernetServiceListener
+{
+ void onEthernetStateChanged(int state);
+ void onInterfaceStateChanged(String iface, int state, int role,
+ in IpConfiguration configuration);
+}
diff --git a/framework-t/src/android/net/IIpSecService.aidl b/framework-t/src/android/net/IIpSecService.aidl
new file mode 100644
index 0000000..933256a
--- /dev/null
+++ b/framework-t/src/android/net/IIpSecService.aidl
@@ -0,0 +1,78 @@
+/*
+** Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.net;
+
+import android.net.LinkAddress;
+import android.net.Network;
+import android.net.IpSecConfig;
+import android.net.IpSecUdpEncapResponse;
+import android.net.IpSecSpiResponse;
+import android.net.IpSecTransformResponse;
+import android.net.IpSecTunnelInterfaceResponse;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+
+/**
+ * @hide
+ */
+interface IIpSecService
+{
+ IpSecSpiResponse allocateSecurityParameterIndex(
+ in String destinationAddress, int requestedSpi, in IBinder binder);
+
+ void releaseSecurityParameterIndex(int resourceId);
+
+ IpSecUdpEncapResponse openUdpEncapsulationSocket(int port, in IBinder binder);
+
+ void closeUdpEncapsulationSocket(int resourceId);
+
+ IpSecTunnelInterfaceResponse createTunnelInterface(
+ in String localAddr,
+ in String remoteAddr,
+ in Network underlyingNetwork,
+ in IBinder binder,
+ in String callingPackage);
+
+ void addAddressToTunnelInterface(
+ int tunnelResourceId,
+ in LinkAddress localAddr,
+ in String callingPackage);
+
+ void removeAddressFromTunnelInterface(
+ int tunnelResourceId,
+ in LinkAddress localAddr,
+ in String callingPackage);
+
+ void setNetworkForTunnelInterface(
+ int tunnelResourceId, in Network underlyingNetwork, in String callingPackage);
+
+ void deleteTunnelInterface(int resourceId, in String callingPackage);
+
+ IpSecTransformResponse createTransform(
+ in IpSecConfig c, in IBinder binder, in String callingPackage);
+
+ void deleteTransform(int transformId);
+
+ void applyTransportModeTransform(
+ in ParcelFileDescriptor socket, int direction, int transformId);
+
+ void applyTunnelModeTransform(
+ int tunnelResourceId, int direction, int transformResourceId, in String callingPackage);
+
+ void removeTransportModeTransforms(in ParcelFileDescriptor socket);
+}
diff --git a/framework-t/src/android/net/INetworkInterfaceOutcomeReceiver.aidl b/framework-t/src/android/net/INetworkInterfaceOutcomeReceiver.aidl
new file mode 100644
index 0000000..85795ea
--- /dev/null
+++ b/framework-t/src/android/net/INetworkInterfaceOutcomeReceiver.aidl
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.net.EthernetNetworkManagementException;
+
+/** @hide */
+oneway interface INetworkInterfaceOutcomeReceiver {
+ void onResult(in String iface);
+ void onError(in EthernetNetworkManagementException e);
+}
\ No newline at end of file
diff --git a/framework-t/src/android/net/INetworkStatsService.aidl b/framework-t/src/android/net/INetworkStatsService.aidl
new file mode 100644
index 0000000..c86f7fd
--- /dev/null
+++ b/framework-t/src/android/net/INetworkStatsService.aidl
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.net.DataUsageRequest;
+import android.net.INetworkStatsSession;
+import android.net.Network;
+import android.net.NetworkStateSnapshot;
+import android.net.NetworkStats;
+import android.net.NetworkStatsHistory;
+import android.net.NetworkTemplate;
+import android.net.UnderlyingNetworkInfo;
+import android.net.netstats.IUsageCallback;
+import android.net.netstats.provider.INetworkStatsProvider;
+import android.net.netstats.provider.INetworkStatsProviderCallback;
+import android.os.IBinder;
+import android.os.Messenger;
+
+/** {@hide} */
+interface INetworkStatsService {
+
+ /** Start a statistics query session. */
+ @UnsupportedAppUsage
+ INetworkStatsSession openSession();
+
+ /** Start a statistics query session. If calling package is profile or device owner then it is
+ * granted automatic access if apiLevel is NetworkStatsManager.API_LEVEL_DPC_ALLOWED. If
+ * apiLevel is at least NetworkStatsManager.API_LEVEL_REQUIRES_PACKAGE_USAGE_STATS then
+ * PACKAGE_USAGE_STATS permission is always checked. If PACKAGE_USAGE_STATS is not granted
+ * READ_NETWORK_USAGE_STATS is checked for.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
+ INetworkStatsSession openSessionForUsageStats(int flags, String callingPackage);
+
+ /** Return data layer snapshot of UID network usage. */
+ @UnsupportedAppUsage
+ NetworkStats getDataLayerSnapshotForUid(int uid);
+
+ /** Get the transport NetworkStats for all UIDs since boot. */
+ NetworkStats getUidStatsForTransport(int transport);
+
+ /** Return set of any ifaces associated with mobile networks since boot. */
+ @UnsupportedAppUsage
+ String[] getMobileIfaces();
+
+ /** Increment data layer count of operations performed for UID and tag. */
+ void incrementOperationCount(int uid, int tag, int operationCount);
+
+ /** Notify {@code NetworkStatsService} about network status changed. */
+ void notifyNetworkStatus(
+ in Network[] defaultNetworks,
+ in NetworkStateSnapshot[] snapshots,
+ in String activeIface,
+ in UnderlyingNetworkInfo[] underlyingNetworkInfos);
+ /** Force update of statistics. */
+ @UnsupportedAppUsage
+ void forceUpdate();
+
+ /** Registers a callback on data usage. */
+ DataUsageRequest registerUsageCallback(String callingPackage,
+ in DataUsageRequest request, in IUsageCallback callback);
+
+ /** Unregisters a callback on data usage. */
+ void unregisterUsageRequest(in DataUsageRequest request);
+
+ /** Get the uid stats information since boot */
+ long getUidStats(int uid, int type);
+
+ /** Get the iface stats information since boot */
+ long getIfaceStats(String iface, int type);
+
+ /** Get the total network stats information since boot */
+ long getTotalStats(int type);
+
+ /** Registers a network stats provider */
+ INetworkStatsProviderCallback registerNetworkStatsProvider(String tag,
+ in INetworkStatsProvider provider);
+
+ /** Mark given UID as being in foreground for stats purposes. */
+ void noteUidForeground(int uid, boolean uidForeground);
+
+ /** Advise persistence threshold; may be overridden internally. */
+ void advisePersistThreshold(long thresholdBytes);
+
+ /**
+ * Set the warning and limit to all registered custom network stats providers.
+ * Note that invocation of any interface will be sent to all providers.
+ */
+ void setStatsProviderWarningAndLimitAsync(String iface, long warning, long limit);
+}
diff --git a/framework-t/src/android/net/INetworkStatsSession.aidl b/framework-t/src/android/net/INetworkStatsSession.aidl
new file mode 100644
index 0000000..ab70be8
--- /dev/null
+++ b/framework-t/src/android/net/INetworkStatsSession.aidl
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.net.NetworkStats;
+import android.net.NetworkStatsHistory;
+import android.net.NetworkTemplate;
+
+/** {@hide} */
+interface INetworkStatsSession {
+
+ /** Return device aggregated network layer usage summary for traffic that matches template. */
+ NetworkStats getDeviceSummaryForNetwork(in NetworkTemplate template, long start, long end);
+
+ /** Return network layer usage summary for traffic that matches template. */
+ @UnsupportedAppUsage
+ NetworkStats getSummaryForNetwork(in NetworkTemplate template, long start, long end);
+ /** Return historical network layer stats for traffic that matches template. */
+ @UnsupportedAppUsage
+ NetworkStatsHistory getHistoryForNetwork(in NetworkTemplate template, int fields);
+ /**
+ * Return historical network layer stats for traffic that matches template, start and end
+ * timestamp.
+ */
+ NetworkStatsHistory getHistoryIntervalForNetwork(in NetworkTemplate template, int fields, long start, long end);
+
+ /**
+ * Return network layer usage summary per UID for traffic that matches template.
+ *
+ * <p>The resulting {@code NetworkStats#getElapsedRealtime()} contains time delta between
+ * {@code start} and {@code end}.
+ *
+ * @param template - a predicate to filter netstats.
+ * @param start - start of the range, timestamp in milliseconds since the epoch.
+ * @param end - end of the range, timestamp in milliseconds since the epoch.
+ * @param includeTags - includes data usage tags if true.
+ */
+ @UnsupportedAppUsage
+ NetworkStats getSummaryForAllUid(in NetworkTemplate template, long start, long end, boolean includeTags);
+
+ /** Return network layer usage summary per UID for tagged traffic that matches template. */
+ NetworkStats getTaggedSummaryForAllUid(in NetworkTemplate template, long start, long end);
+
+ /** Return historical network layer stats for specific UID traffic that matches template. */
+ @UnsupportedAppUsage
+ NetworkStatsHistory getHistoryForUid(in NetworkTemplate template, int uid, int set, int tag, int fields);
+ /** Return historical network layer stats for specific UID traffic that matches template. */
+ NetworkStatsHistory getHistoryIntervalForUid(in NetworkTemplate template, int uid, int set, int tag, int fields, long start, long end);
+
+ /** Return array of uids that have stats and are accessible to the calling user */
+ int[] getRelevantUids();
+
+ @UnsupportedAppUsage
+ void close();
+
+}
diff --git a/framework-t/src/android/net/ITetheredInterfaceCallback.aidl b/framework-t/src/android/net/ITetheredInterfaceCallback.aidl
new file mode 100644
index 0000000..14aa023
--- /dev/null
+++ b/framework-t/src/android/net/ITetheredInterfaceCallback.aidl
@@ -0,0 +1,23 @@
+/*
+ * 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;
+
+/** @hide */
+oneway interface ITetheredInterfaceCallback {
+ void onAvailable(in String iface);
+ void onUnavailable();
+}
\ No newline at end of file
diff --git a/framework-t/src/android/net/IpSecAlgorithm.java b/framework-t/src/android/net/IpSecAlgorithm.java
new file mode 100644
index 0000000..10a22ac
--- /dev/null
+++ b/framework-t/src/android/net/IpSecAlgorithm.java
@@ -0,0 +1,491 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net;
+
+import android.annotation.NonNull;
+import android.annotation.StringDef;
+import android.content.res.Resources;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * This class represents a single algorithm that can be used by an {@link IpSecTransform}.
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the
+ * Internet Protocol</a>
+ */
+public final class IpSecAlgorithm implements Parcelable {
+ private static final String TAG = "IpSecAlgorithm";
+
+ /**
+ * Null cipher.
+ *
+ * @hide
+ */
+ public static final String CRYPT_NULL = "ecb(cipher_null)";
+
+ /**
+ * AES-CBC Encryption/Ciphering Algorithm.
+ *
+ * <p>Valid lengths for this key are {128, 192, 256}.
+ */
+ public static final String CRYPT_AES_CBC = "cbc(aes)";
+
+ /**
+ * AES-CTR Encryption/Ciphering Algorithm.
+ *
+ * <p>Valid lengths for keying material are {160, 224, 288}.
+ *
+ * <p>As per <a href="https://tools.ietf.org/html/rfc3686#section-5.1">RFC3686 (Section
+ * 5.1)</a>, keying material consists of a 128, 192, or 256 bit AES key followed by a 32-bit
+ * nonce. RFC compliance requires that the nonce must be unique per security association.
+ *
+ * <p>This algorithm may be available on the device. Caller MUST check if it is supported before
+ * using it by calling {@link #getSupportedAlgorithms()} and checking if this algorithm is
+ * included in the returned algorithm set. The returned algorithm set will not change unless the
+ * device is rebooted. {@link IllegalArgumentException} will be thrown if this algorithm is
+ * requested on an unsupported device.
+ *
+ * <p>@see {@link #getSupportedAlgorithms()}
+ */
+ // This algorithm may be available on devices released before Android 12, and is guaranteed
+ // to be available on devices first shipped with Android 12 or later.
+ public static final String CRYPT_AES_CTR = "rfc3686(ctr(aes))";
+
+ /**
+ * MD5 HMAC Authentication/Integrity Algorithm. <b>This algorithm is not recommended for use in
+ * new applications and is provided for legacy compatibility with 3gpp infrastructure.</b>
+ *
+ * <p>Keys for this algorithm must be 128 bits in length.
+ *
+ * <p>Valid truncation lengths are multiples of 8 bits from 96 to 128.
+ */
+ public static final String AUTH_HMAC_MD5 = "hmac(md5)";
+
+ /**
+ * SHA1 HMAC Authentication/Integrity Algorithm. <b>This algorithm is not recommended for use in
+ * new applications and is provided for legacy compatibility with 3gpp infrastructure.</b>
+ *
+ * <p>Keys for this algorithm must be 160 bits in length.
+ *
+ * <p>Valid truncation lengths are multiples of 8 bits from 96 to 160.
+ */
+ public static final String AUTH_HMAC_SHA1 = "hmac(sha1)";
+
+ /**
+ * SHA256 HMAC Authentication/Integrity Algorithm.
+ *
+ * <p>Keys for this algorithm must be 256 bits in length.
+ *
+ * <p>Valid truncation lengths are multiples of 8 bits from 96 to 256.
+ */
+ public static final String AUTH_HMAC_SHA256 = "hmac(sha256)";
+
+ /**
+ * SHA384 HMAC Authentication/Integrity Algorithm.
+ *
+ * <p>Keys for this algorithm must be 384 bits in length.
+ *
+ * <p>Valid truncation lengths are multiples of 8 bits from 192 to 384.
+ */
+ public static final String AUTH_HMAC_SHA384 = "hmac(sha384)";
+
+ /**
+ * SHA512 HMAC Authentication/Integrity Algorithm.
+ *
+ * <p>Keys for this algorithm must be 512 bits in length.
+ *
+ * <p>Valid truncation lengths are multiples of 8 bits from 256 to 512.
+ */
+ public static final String AUTH_HMAC_SHA512 = "hmac(sha512)";
+
+ /**
+ * AES-XCBC Authentication/Integrity Algorithm.
+ *
+ * <p>Keys for this algorithm must be 128 bits in length.
+ *
+ * <p>The only valid truncation length is 96 bits.
+ *
+ * <p>This algorithm may be available on the device. Caller MUST check if it is supported before
+ * using it by calling {@link #getSupportedAlgorithms()} and checking if this algorithm is
+ * included in the returned algorithm set. The returned algorithm set will not change unless the
+ * device is rebooted. {@link IllegalArgumentException} will be thrown if this algorithm is
+ * requested on an unsupported device.
+ *
+ * <p>@see {@link #getSupportedAlgorithms()}
+ */
+ // This algorithm may be available on devices released before Android 12, and is guaranteed
+ // to be available on devices first shipped with Android 12 or later.
+ public static final String AUTH_AES_XCBC = "xcbc(aes)";
+
+ /**
+ * AES-CMAC Authentication/Integrity Algorithm.
+ *
+ * <p>Keys for this algorithm must be 128 bits in length.
+ *
+ * <p>The only valid truncation length is 96 bits.
+ *
+ * <p>This algorithm may be available on the device. Caller MUST check if it is supported before
+ * using it by calling {@link #getSupportedAlgorithms()} and checking if this algorithm is
+ * included in the returned algorithm set. The returned algorithm set will not change unless the
+ * device is rebooted. {@link IllegalArgumentException} will be thrown if this algorithm is
+ * requested on an unsupported device.
+ *
+ * <p>@see {@link #getSupportedAlgorithms()}
+ */
+ // This algorithm may be available on devices released before Android 12, and is guaranteed
+ // to be available on devices first shipped with Android 12 or later.
+ public static final String AUTH_AES_CMAC = "cmac(aes)";
+
+ /**
+ * AES-GCM Authentication/Integrity + Encryption/Ciphering Algorithm.
+ *
+ * <p>Valid lengths for keying material are {160, 224, 288}.
+ *
+ * <p>As per <a href="https://tools.ietf.org/html/rfc4106#section-8.1">RFC4106 (Section
+ * 8.1)</a>, keying material consists of a 128, 192, or 256 bit AES key followed by a 32-bit
+ * salt. RFC compliance requires that the salt must be unique per invocation with the same key.
+ *
+ * <p>Valid ICV (truncation) lengths are {64, 96, 128}.
+ */
+ public static final String AUTH_CRYPT_AES_GCM = "rfc4106(gcm(aes))";
+
+ /**
+ * ChaCha20-Poly1305 Authentication/Integrity + Encryption/Ciphering Algorithm.
+ *
+ * <p>Keys for this algorithm must be 288 bits in length.
+ *
+ * <p>As per <a href="https://tools.ietf.org/html/rfc7634#section-2">RFC7634 (Section 2)</a>,
+ * keying material consists of a 256 bit key followed by a 32-bit salt. The salt is fixed per
+ * security association.
+ *
+ * <p>The only valid ICV (truncation) length is 128 bits.
+ *
+ * <p>This algorithm may be available on the device. Caller MUST check if it is supported before
+ * using it by calling {@link #getSupportedAlgorithms()} and checking if this algorithm is
+ * included in the returned algorithm set. The returned algorithm set will not change unless the
+ * device is rebooted. {@link IllegalArgumentException} will be thrown if this algorithm is
+ * requested on an unsupported device.
+ *
+ * <p>@see {@link #getSupportedAlgorithms()}
+ */
+ // This algorithm may be available on devices released before Android 12, and is guaranteed
+ // to be available on devices first shipped with Android 12 or later.
+ public static final String AUTH_CRYPT_CHACHA20_POLY1305 = "rfc7539esp(chacha20,poly1305)";
+
+ /** @hide */
+ @StringDef({
+ CRYPT_AES_CBC,
+ CRYPT_AES_CTR,
+ AUTH_HMAC_MD5,
+ AUTH_HMAC_SHA1,
+ AUTH_HMAC_SHA256,
+ AUTH_HMAC_SHA384,
+ AUTH_HMAC_SHA512,
+ AUTH_AES_XCBC,
+ AUTH_AES_CMAC,
+ AUTH_CRYPT_AES_GCM,
+ AUTH_CRYPT_CHACHA20_POLY1305
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AlgorithmName {}
+
+ /** @hide */
+ @VisibleForTesting
+ public static final Map<String, Integer> ALGO_TO_REQUIRED_FIRST_SDK = new HashMap<>();
+
+ private static final int SDK_VERSION_ZERO = 0;
+
+ static {
+ ALGO_TO_REQUIRED_FIRST_SDK.put(CRYPT_AES_CBC, SDK_VERSION_ZERO);
+ ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_HMAC_MD5, SDK_VERSION_ZERO);
+ ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_HMAC_SHA1, SDK_VERSION_ZERO);
+ ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_HMAC_SHA256, SDK_VERSION_ZERO);
+ ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_HMAC_SHA384, SDK_VERSION_ZERO);
+ ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_HMAC_SHA512, SDK_VERSION_ZERO);
+ ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_CRYPT_AES_GCM, SDK_VERSION_ZERO);
+
+ ALGO_TO_REQUIRED_FIRST_SDK.put(CRYPT_AES_CTR, Build.VERSION_CODES.S);
+ ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_AES_XCBC, Build.VERSION_CODES.S);
+ ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_AES_CMAC, Build.VERSION_CODES.S);
+ ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_CRYPT_CHACHA20_POLY1305, Build.VERSION_CODES.S);
+ }
+
+ private static final Set<String> ENABLED_ALGOS =
+ Collections.unmodifiableSet(loadAlgos(Resources.getSystem()));
+
+ private final String mName;
+ private final byte[] mKey;
+ private final int mTruncLenBits;
+
+ /**
+ * Creates an IpSecAlgorithm of one of the supported types. Supported algorithm names are
+ * defined as constants in this class.
+ *
+ * <p>For algorithms that produce an integrity check value, the truncation length is a required
+ * parameter. See {@link #IpSecAlgorithm(String algorithm, byte[] key, int truncLenBits)}
+ *
+ * @param algorithm name of the algorithm.
+ * @param key key padded to a multiple of 8 bits.
+ * @throws IllegalArgumentException if algorithm or key length is invalid.
+ */
+ public IpSecAlgorithm(@NonNull @AlgorithmName String algorithm, @NonNull byte[] key) {
+ this(algorithm, key, 0);
+ }
+
+ /**
+ * Creates an IpSecAlgorithm of one of the supported types. Supported algorithm names are
+ * defined as constants in this class.
+ *
+ * <p>This constructor only supports algorithms that use a truncation length. i.e.
+ * Authentication and Authenticated Encryption algorithms.
+ *
+ * @param algorithm name of the algorithm.
+ * @param key key padded to a multiple of 8 bits.
+ * @param truncLenBits number of bits of output hash to use.
+ * @throws IllegalArgumentException if algorithm, key length or truncation length is invalid.
+ */
+ public IpSecAlgorithm(
+ @NonNull @AlgorithmName String algorithm, @NonNull byte[] key, int truncLenBits) {
+ mName = algorithm;
+ mKey = key.clone();
+ mTruncLenBits = truncLenBits;
+ checkValidOrThrow(mName, mKey.length * 8, mTruncLenBits);
+ }
+
+ /** Get the algorithm name */
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ /** Get the key for this algorithm */
+ @NonNull
+ public byte[] getKey() {
+ return mKey.clone();
+ }
+
+ /** Get the truncation length of this algorithm, in bits */
+ public int getTruncationLengthBits() {
+ return mTruncLenBits;
+ }
+
+ /** Parcelable Implementation */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Write to parcel */
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeString(mName);
+ out.writeByteArray(mKey);
+ out.writeInt(mTruncLenBits);
+ }
+
+ /** Parcelable Creator */
+ public static final @android.annotation.NonNull Parcelable.Creator<IpSecAlgorithm> CREATOR =
+ new Parcelable.Creator<IpSecAlgorithm>() {
+ public IpSecAlgorithm createFromParcel(Parcel in) {
+ final String name = in.readString();
+ final byte[] key = in.createByteArray();
+ final int truncLenBits = in.readInt();
+
+ return new IpSecAlgorithm(name, key, truncLenBits);
+ }
+
+ public IpSecAlgorithm[] newArray(int size) {
+ return new IpSecAlgorithm[size];
+ }
+ };
+
+ /**
+ * Returns supported IPsec algorithms for the current device.
+ *
+ * <p>Some algorithms may not be supported on old devices. Callers MUST check if an algorithm is
+ * supported before using it.
+ */
+ @NonNull
+ public static Set<String> getSupportedAlgorithms() {
+ return ENABLED_ALGOS;
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public static Set<String> loadAlgos(Resources systemResources) {
+ final Set<String> enabledAlgos = new HashSet<>();
+
+ // Load and validate the optional algorithm resource. Undefined or duplicate algorithms in
+ // the resource are not allowed.
+ final String[] resourceAlgos = systemResources.getStringArray(
+ android.R.array.config_optionalIpSecAlgorithms);
+ for (String str : resourceAlgos) {
+ if (!ALGO_TO_REQUIRED_FIRST_SDK.containsKey(str) || !enabledAlgos.add(str)) {
+ // This error should be caught by CTS and never be thrown to API callers
+ throw new IllegalArgumentException("Invalid or repeated algorithm " + str);
+ }
+ }
+
+ for (Entry<String, Integer> entry : ALGO_TO_REQUIRED_FIRST_SDK.entrySet()) {
+ if (Build.VERSION.DEVICE_INITIAL_SDK_INT >= entry.getValue()) {
+ enabledAlgos.add(entry.getKey());
+ }
+ }
+
+ return enabledAlgos;
+ }
+
+ private static void checkValidOrThrow(String name, int keyLen, int truncLen) {
+ final boolean isValidLen;
+ final boolean isValidTruncLen;
+
+ if (!getSupportedAlgorithms().contains(name)) {
+ throw new IllegalArgumentException("Unsupported algorithm: " + name);
+ }
+
+ switch (name) {
+ case CRYPT_AES_CBC:
+ isValidLen = keyLen == 128 || keyLen == 192 || keyLen == 256;
+ isValidTruncLen = true;
+ break;
+ case CRYPT_AES_CTR:
+ // The keying material for AES-CTR is a key plus a 32-bit salt
+ isValidLen = keyLen == 128 + 32 || keyLen == 192 + 32 || keyLen == 256 + 32;
+ isValidTruncLen = true;
+ break;
+ case AUTH_HMAC_MD5:
+ isValidLen = keyLen == 128;
+ isValidTruncLen = truncLen >= 96 && truncLen <= 128;
+ break;
+ case AUTH_HMAC_SHA1:
+ isValidLen = keyLen == 160;
+ isValidTruncLen = truncLen >= 96 && truncLen <= 160;
+ break;
+ case AUTH_HMAC_SHA256:
+ isValidLen = keyLen == 256;
+ isValidTruncLen = truncLen >= 96 && truncLen <= 256;
+ break;
+ case AUTH_HMAC_SHA384:
+ isValidLen = keyLen == 384;
+ isValidTruncLen = truncLen >= 192 && truncLen <= 384;
+ break;
+ case AUTH_HMAC_SHA512:
+ isValidLen = keyLen == 512;
+ isValidTruncLen = truncLen >= 256 && truncLen <= 512;
+ break;
+ case AUTH_AES_XCBC:
+ isValidLen = keyLen == 128;
+ isValidTruncLen = truncLen == 96;
+ break;
+ case AUTH_AES_CMAC:
+ isValidLen = keyLen == 128;
+ isValidTruncLen = truncLen == 96;
+ break;
+ case AUTH_CRYPT_AES_GCM:
+ // The keying material for GCM is a key plus a 32-bit salt
+ isValidLen = keyLen == 128 + 32 || keyLen == 192 + 32 || keyLen == 256 + 32;
+ isValidTruncLen = truncLen == 64 || truncLen == 96 || truncLen == 128;
+ break;
+ case AUTH_CRYPT_CHACHA20_POLY1305:
+ // The keying material for ChaCha20Poly1305 is a key plus a 32-bit salt
+ isValidLen = keyLen == 256 + 32;
+ isValidTruncLen = truncLen == 128;
+ break;
+ default:
+ // Should never hit here.
+ throw new IllegalArgumentException("Couldn't find an algorithm: " + name);
+ }
+
+ if (!isValidLen) {
+ throw new IllegalArgumentException("Invalid key material keyLength: " + keyLen);
+ }
+ if (!isValidTruncLen) {
+ throw new IllegalArgumentException("Invalid truncation keyLength: " + truncLen);
+ }
+ }
+
+ /** @hide */
+ public boolean isAuthentication() {
+ switch (getName()) {
+ // Fallthrough
+ case AUTH_HMAC_MD5:
+ case AUTH_HMAC_SHA1:
+ case AUTH_HMAC_SHA256:
+ case AUTH_HMAC_SHA384:
+ case AUTH_HMAC_SHA512:
+ case AUTH_AES_XCBC:
+ case AUTH_AES_CMAC:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /** @hide */
+ public boolean isEncryption() {
+ switch (getName()) {
+ case CRYPT_AES_CBC: // fallthrough
+ case CRYPT_AES_CTR:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /** @hide */
+ public boolean isAead() {
+ switch (getName()) {
+ case AUTH_CRYPT_AES_GCM: // fallthrough
+ case AUTH_CRYPT_CHACHA20_POLY1305:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ @NonNull
+ public String toString() {
+ return new StringBuilder()
+ .append("{mName=")
+ .append(mName)
+ .append(", mTruncLenBits=")
+ .append(mTruncLenBits)
+ .append("}")
+ .toString();
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public static boolean equals(IpSecAlgorithm lhs, IpSecAlgorithm rhs) {
+ if (lhs == null || rhs == null) return (lhs == rhs);
+ return (lhs.mName.equals(rhs.mName)
+ && Arrays.equals(lhs.mKey, rhs.mKey)
+ && lhs.mTruncLenBits == rhs.mTruncLenBits);
+ }
+};
diff --git a/framework-t/src/android/net/IpSecConfig.aidl b/framework-t/src/android/net/IpSecConfig.aidl
new file mode 100644
index 0000000..eaefca7
--- /dev/null
+++ b/framework-t/src/android/net/IpSecConfig.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+/** @hide */
+parcelable IpSecConfig;
diff --git a/framework-t/src/android/net/IpSecConfig.java b/framework-t/src/android/net/IpSecConfig.java
new file mode 100644
index 0000000..575c5ed
--- /dev/null
+++ b/framework-t/src/android/net/IpSecConfig.java
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net;
+
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * This class encapsulates all the configuration parameters needed to create IPsec transforms and
+ * policies.
+ *
+ * @hide
+ */
+public final class IpSecConfig implements Parcelable {
+ private static final String TAG = "IpSecConfig";
+
+ // MODE_TRANSPORT or MODE_TUNNEL
+ private int mMode = IpSecTransform.MODE_TRANSPORT;
+
+ // Preventing this from being null simplifies Java->Native binder
+ private String mSourceAddress = "";
+
+ // Preventing this from being null simplifies Java->Native binder
+ private String mDestinationAddress = "";
+
+ // The underlying Network that represents the "gateway" Network
+ // for outbound packets. It may also be used to select packets.
+ private Network mNetwork;
+
+ // Minimum requirements for identifying a transform
+ // SPI identifying the IPsec SA in packet processing
+ // and a destination IP address
+ private int mSpiResourceId = IpSecManager.INVALID_RESOURCE_ID;
+
+ // Encryption Algorithm
+ private IpSecAlgorithm mEncryption;
+
+ // Authentication Algorithm
+ private IpSecAlgorithm mAuthentication;
+
+ // Authenticated Encryption Algorithm
+ private IpSecAlgorithm mAuthenticatedEncryption;
+
+ // For tunnel mode IPv4 UDP Encapsulation
+ // IpSecTransform#ENCAP_ESP_*, such as ENCAP_ESP_OVER_UDP_IKE
+ private int mEncapType = IpSecTransform.ENCAP_NONE;
+ private int mEncapSocketResourceId = IpSecManager.INVALID_RESOURCE_ID;
+ private int mEncapRemotePort;
+
+ // An interval, in seconds between the NattKeepalive packets
+ private int mNattKeepaliveInterval;
+
+ // XFRM mark and mask; defaults to 0 (no mark/mask)
+ private int mMarkValue;
+ private int mMarkMask;
+
+ // XFRM interface id
+ private int mXfrmInterfaceId;
+
+ /** Set the mode for this IPsec transform */
+ public void setMode(int mode) {
+ mMode = mode;
+ }
+
+ /** Set the source IP addres for this IPsec transform */
+ public void setSourceAddress(String sourceAddress) {
+ mSourceAddress = sourceAddress;
+ }
+
+ /** Set the destination IP address for this IPsec transform */
+ public void setDestinationAddress(String destinationAddress) {
+ mDestinationAddress = destinationAddress;
+ }
+
+ /** Set the SPI by resource ID */
+ public void setSpiResourceId(int resourceId) {
+ mSpiResourceId = resourceId;
+ }
+
+ /** Set the encryption algorithm */
+ public void setEncryption(IpSecAlgorithm encryption) {
+ mEncryption = encryption;
+ }
+
+ /** Set the authentication algorithm */
+ public void setAuthentication(IpSecAlgorithm authentication) {
+ mAuthentication = authentication;
+ }
+
+ /** Set the authenticated encryption algorithm */
+ public void setAuthenticatedEncryption(IpSecAlgorithm authenticatedEncryption) {
+ mAuthenticatedEncryption = authenticatedEncryption;
+ }
+
+ /** Set the underlying network that will carry traffic for this transform */
+ public void setNetwork(Network network) {
+ mNetwork = network;
+ }
+
+ public void setEncapType(int encapType) {
+ mEncapType = encapType;
+ }
+
+ public void setEncapSocketResourceId(int resourceId) {
+ mEncapSocketResourceId = resourceId;
+ }
+
+ public void setEncapRemotePort(int port) {
+ mEncapRemotePort = port;
+ }
+
+ public void setNattKeepaliveInterval(int interval) {
+ mNattKeepaliveInterval = interval;
+ }
+
+ /**
+ * Sets the mark value
+ *
+ * <p>Internal (System server) use only. Marks passed in by users will be overwritten or
+ * ignored.
+ */
+ public void setMarkValue(int mark) {
+ mMarkValue = mark;
+ }
+
+ /**
+ * Sets the mark mask
+ *
+ * <p>Internal (System server) use only. Marks passed in by users will be overwritten or
+ * ignored.
+ */
+ public void setMarkMask(int mask) {
+ mMarkMask = mask;
+ }
+
+ public void setXfrmInterfaceId(int xfrmInterfaceId) {
+ mXfrmInterfaceId = xfrmInterfaceId;
+ }
+
+ // Transport or Tunnel
+ public int getMode() {
+ return mMode;
+ }
+
+ public String getSourceAddress() {
+ return mSourceAddress;
+ }
+
+ public int getSpiResourceId() {
+ return mSpiResourceId;
+ }
+
+ public String getDestinationAddress() {
+ return mDestinationAddress;
+ }
+
+ public IpSecAlgorithm getEncryption() {
+ return mEncryption;
+ }
+
+ public IpSecAlgorithm getAuthentication() {
+ return mAuthentication;
+ }
+
+ public IpSecAlgorithm getAuthenticatedEncryption() {
+ return mAuthenticatedEncryption;
+ }
+
+ public Network getNetwork() {
+ return mNetwork;
+ }
+
+ public int getEncapType() {
+ return mEncapType;
+ }
+
+ public int getEncapSocketResourceId() {
+ return mEncapSocketResourceId;
+ }
+
+ public int getEncapRemotePort() {
+ return mEncapRemotePort;
+ }
+
+ public int getNattKeepaliveInterval() {
+ return mNattKeepaliveInterval;
+ }
+
+ public int getMarkValue() {
+ return mMarkValue;
+ }
+
+ public int getMarkMask() {
+ return mMarkMask;
+ }
+
+ public int getXfrmInterfaceId() {
+ return mXfrmInterfaceId;
+ }
+
+ // Parcelable Methods
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mMode);
+ out.writeString(mSourceAddress);
+ out.writeString(mDestinationAddress);
+ out.writeParcelable(mNetwork, flags);
+ out.writeInt(mSpiResourceId);
+ out.writeParcelable(mEncryption, flags);
+ out.writeParcelable(mAuthentication, flags);
+ out.writeParcelable(mAuthenticatedEncryption, flags);
+ out.writeInt(mEncapType);
+ out.writeInt(mEncapSocketResourceId);
+ out.writeInt(mEncapRemotePort);
+ out.writeInt(mNattKeepaliveInterval);
+ out.writeInt(mMarkValue);
+ out.writeInt(mMarkMask);
+ out.writeInt(mXfrmInterfaceId);
+ }
+
+ @VisibleForTesting
+ public IpSecConfig() {}
+
+ /** Copy constructor */
+ @VisibleForTesting
+ public IpSecConfig(IpSecConfig c) {
+ mMode = c.mMode;
+ mSourceAddress = c.mSourceAddress;
+ mDestinationAddress = c.mDestinationAddress;
+ mNetwork = c.mNetwork;
+ mSpiResourceId = c.mSpiResourceId;
+ mEncryption = c.mEncryption;
+ mAuthentication = c.mAuthentication;
+ mAuthenticatedEncryption = c.mAuthenticatedEncryption;
+ mEncapType = c.mEncapType;
+ mEncapSocketResourceId = c.mEncapSocketResourceId;
+ mEncapRemotePort = c.mEncapRemotePort;
+ mNattKeepaliveInterval = c.mNattKeepaliveInterval;
+ mMarkValue = c.mMarkValue;
+ mMarkMask = c.mMarkMask;
+ mXfrmInterfaceId = c.mXfrmInterfaceId;
+ }
+
+ private IpSecConfig(Parcel in) {
+ mMode = in.readInt();
+ mSourceAddress = in.readString();
+ mDestinationAddress = in.readString();
+ mNetwork = (Network) in.readParcelable(Network.class.getClassLoader());
+ mSpiResourceId = in.readInt();
+ mEncryption =
+ (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
+ mAuthentication =
+ (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
+ mAuthenticatedEncryption =
+ (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
+ mEncapType = in.readInt();
+ mEncapSocketResourceId = in.readInt();
+ mEncapRemotePort = in.readInt();
+ mNattKeepaliveInterval = in.readInt();
+ mMarkValue = in.readInt();
+ mMarkMask = in.readInt();
+ mXfrmInterfaceId = in.readInt();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder strBuilder = new StringBuilder();
+ strBuilder
+ .append("{mMode=")
+ .append(mMode == IpSecTransform.MODE_TUNNEL ? "TUNNEL" : "TRANSPORT")
+ .append(", mSourceAddress=")
+ .append(mSourceAddress)
+ .append(", mDestinationAddress=")
+ .append(mDestinationAddress)
+ .append(", mNetwork=")
+ .append(mNetwork)
+ .append(", mEncapType=")
+ .append(mEncapType)
+ .append(", mEncapSocketResourceId=")
+ .append(mEncapSocketResourceId)
+ .append(", mEncapRemotePort=")
+ .append(mEncapRemotePort)
+ .append(", mNattKeepaliveInterval=")
+ .append(mNattKeepaliveInterval)
+ .append("{mSpiResourceId=")
+ .append(mSpiResourceId)
+ .append(", mEncryption=")
+ .append(mEncryption)
+ .append(", mAuthentication=")
+ .append(mAuthentication)
+ .append(", mAuthenticatedEncryption=")
+ .append(mAuthenticatedEncryption)
+ .append(", mMarkValue=")
+ .append(mMarkValue)
+ .append(", mMarkMask=")
+ .append(mMarkMask)
+ .append(", mXfrmInterfaceId=")
+ .append(mXfrmInterfaceId)
+ .append("}");
+
+ return strBuilder.toString();
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<IpSecConfig> CREATOR =
+ new Parcelable.Creator<IpSecConfig>() {
+ public IpSecConfig createFromParcel(Parcel in) {
+ return new IpSecConfig(in);
+ }
+
+ public IpSecConfig[] newArray(int size) {
+ return new IpSecConfig[size];
+ }
+ };
+
+ @Override
+ public boolean equals(@Nullable Object other) {
+ if (!(other instanceof IpSecConfig)) return false;
+ final IpSecConfig rhs = (IpSecConfig) other;
+ return (mMode == rhs.mMode
+ && mSourceAddress.equals(rhs.mSourceAddress)
+ && mDestinationAddress.equals(rhs.mDestinationAddress)
+ && ((mNetwork != null && mNetwork.equals(rhs.mNetwork))
+ || (mNetwork == rhs.mNetwork))
+ && mEncapType == rhs.mEncapType
+ && mEncapSocketResourceId == rhs.mEncapSocketResourceId
+ && mEncapRemotePort == rhs.mEncapRemotePort
+ && mNattKeepaliveInterval == rhs.mNattKeepaliveInterval
+ && mSpiResourceId == rhs.mSpiResourceId
+ && IpSecAlgorithm.equals(mEncryption, rhs.mEncryption)
+ && IpSecAlgorithm.equals(mAuthenticatedEncryption, rhs.mAuthenticatedEncryption)
+ && IpSecAlgorithm.equals(mAuthentication, rhs.mAuthentication)
+ && mMarkValue == rhs.mMarkValue
+ && mMarkMask == rhs.mMarkMask
+ && mXfrmInterfaceId == rhs.mXfrmInterfaceId);
+ }
+}
diff --git a/framework-t/src/android/net/IpSecManager.java b/framework-t/src/android/net/IpSecManager.java
new file mode 100644
index 0000000..9cceac2
--- /dev/null
+++ b/framework-t/src/android/net/IpSecManager.java
@@ -0,0 +1,1064 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net;
+
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresFeature;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.annotation.TestApi;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.system.ErrnoException;
+import android.system.OsConstants;
+import android.util.AndroidException;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import dalvik.system.CloseGuard;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.util.Objects;
+
+/**
+ * This class contains methods for managing IPsec sessions. Once configured, the kernel will apply
+ * confidentiality (encryption) and integrity (authentication) to IP traffic.
+ *
+ * <p>Note that not all aspects of IPsec are permitted by this API. Applications may create
+ * transport mode security associations and apply them to individual sockets. Applications looking
+ * to create an IPsec VPN should use {@link VpnManager} and {@link Ikev2VpnProfile}.
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the
+ * Internet Protocol</a>
+ */
+@SystemService(Context.IPSEC_SERVICE)
+public class IpSecManager {
+ private static final String TAG = "IpSecManager";
+
+ /**
+ * Used when applying a transform to direct traffic through an {@link IpSecTransform}
+ * towards the host.
+ *
+ * <p>See {@link #applyTransportModeTransform(Socket, int, IpSecTransform)}.
+ */
+ public static final int DIRECTION_IN = 0;
+
+ /**
+ * Used when applying a transform to direct traffic through an {@link IpSecTransform}
+ * away from the host.
+ *
+ * <p>See {@link #applyTransportModeTransform(Socket, int, IpSecTransform)}.
+ */
+ public static final int DIRECTION_OUT = 1;
+
+ /**
+ * Used when applying a transform to direct traffic through an {@link IpSecTransform} for
+ * forwarding between interfaces.
+ *
+ * <p>See {@link #applyTransportModeTransform(Socket, int, IpSecTransform)}.
+ *
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static final int DIRECTION_FWD = 2;
+
+ /** @hide */
+ @IntDef(value = {DIRECTION_IN, DIRECTION_OUT})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PolicyDirection {}
+
+ /**
+ * The Security Parameter Index (SPI) 0 indicates an unknown or invalid index.
+ *
+ * <p>No IPsec packet may contain an SPI of 0.
+ *
+ * @hide
+ */
+ @TestApi public static final int INVALID_SECURITY_PARAMETER_INDEX = 0;
+
+ /** @hide */
+ public interface Status {
+ int OK = 0;
+ int RESOURCE_UNAVAILABLE = 1;
+ int SPI_UNAVAILABLE = 2;
+ }
+
+ /** @hide */
+ public static final int INVALID_RESOURCE_ID = -1;
+
+ /**
+ * Thrown to indicate that a requested SPI is in use.
+ *
+ * <p>The combination of remote {@code InetAddress} and SPI must be unique across all apps on
+ * one device. If this error is encountered, a new SPI is required before a transform may be
+ * created. This error can be avoided by calling {@link
+ * IpSecManager#allocateSecurityParameterIndex}.
+ */
+ public static final class SpiUnavailableException extends AndroidException {
+ private final int mSpi;
+
+ /**
+ * Construct an exception indicating that a transform with the given SPI is already in use
+ * or otherwise unavailable.
+ *
+ * @param msg description indicating the colliding SPI
+ * @param spi the SPI that could not be used due to a collision
+ */
+ SpiUnavailableException(String msg, int spi) {
+ super(msg + " (spi: " + spi + ")");
+ mSpi = spi;
+ }
+
+ /** Get the SPI that caused a collision. */
+ public int getSpi() {
+ return mSpi;
+ }
+ }
+
+ /**
+ * Thrown to indicate that an IPsec resource is unavailable.
+ *
+ * <p>This could apply to resources such as sockets, {@link SecurityParameterIndex}, {@link
+ * IpSecTransform}, or other system resources. If this exception is thrown, users should release
+ * allocated objects of the type requested.
+ */
+ public static final class ResourceUnavailableException extends AndroidException {
+
+ ResourceUnavailableException(String msg) {
+ super(msg);
+ }
+ }
+
+ private final Context mContext;
+ private final IIpSecService mService;
+
+ /**
+ * This class represents a reserved SPI.
+ *
+ * <p>Objects of this type are used to track reserved security parameter indices. They can be
+ * obtained by calling {@link IpSecManager#allocateSecurityParameterIndex} and must be released
+ * by calling {@link #close()} when they are no longer needed.
+ */
+ public static final class SecurityParameterIndex implements AutoCloseable {
+ private final IIpSecService mService;
+ private final InetAddress mDestinationAddress;
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+ private int mSpi = INVALID_SECURITY_PARAMETER_INDEX;
+ private int mResourceId = INVALID_RESOURCE_ID;
+
+ /** Get the underlying SPI held by this object. */
+ public int getSpi() {
+ return mSpi;
+ }
+
+ /**
+ * Release an SPI that was previously reserved.
+ *
+ * <p>Release an SPI for use by other users in the system. If a SecurityParameterIndex is
+ * applied to an IpSecTransform, it will become unusable for future transforms but should
+ * still be closed to ensure system resources are released.
+ */
+ @Override
+ public void close() {
+ try {
+ mService.releaseSecurityParameterIndex(mResourceId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (Exception e) {
+ // On close we swallow all random exceptions since failure to close is not
+ // actionable by the user.
+ Log.e(TAG, "Failed to close " + this + ", Exception=" + e);
+ } finally {
+ mResourceId = INVALID_RESOURCE_ID;
+ mCloseGuard.close();
+ }
+ }
+
+ /** Check that the SPI was closed properly. */
+ @Override
+ protected void finalize() throws Throwable {
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+
+ close();
+ }
+
+ private SecurityParameterIndex(
+ @NonNull IIpSecService service, InetAddress destinationAddress, int spi)
+ throws ResourceUnavailableException, SpiUnavailableException {
+ mService = service;
+ mDestinationAddress = destinationAddress;
+ try {
+ IpSecSpiResponse result =
+ mService.allocateSecurityParameterIndex(
+ destinationAddress.getHostAddress(), spi, new Binder());
+
+ if (result == null) {
+ throw new NullPointerException("Received null response from IpSecService");
+ }
+
+ int status = result.status;
+ switch (status) {
+ case Status.OK:
+ break;
+ case Status.RESOURCE_UNAVAILABLE:
+ throw new ResourceUnavailableException(
+ "No more SPIs may be allocated by this requester.");
+ case Status.SPI_UNAVAILABLE:
+ throw new SpiUnavailableException("Requested SPI is unavailable", spi);
+ default:
+ throw new RuntimeException(
+ "Unknown status returned by IpSecService: " + status);
+ }
+ mSpi = result.spi;
+ mResourceId = result.resourceId;
+
+ if (mSpi == INVALID_SECURITY_PARAMETER_INDEX) {
+ throw new RuntimeException("Invalid SPI returned by IpSecService: " + status);
+ }
+
+ if (mResourceId == INVALID_RESOURCE_ID) {
+ throw new RuntimeException(
+ "Invalid Resource ID returned by IpSecService: " + status);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ mCloseGuard.open("open");
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public int getResourceId() {
+ return mResourceId;
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder()
+ .append("SecurityParameterIndex{spi=")
+ .append(mSpi)
+ .append(",resourceId=")
+ .append(mResourceId)
+ .append("}")
+ .toString();
+ }
+ }
+
+ /**
+ * Reserve a random SPI for traffic bound to or from the specified destination address.
+ *
+ * <p>If successful, this SPI is guaranteed available until released by a call to {@link
+ * SecurityParameterIndex#close()}.
+ *
+ * @param destinationAddress the destination address for traffic bearing the requested SPI.
+ * For inbound traffic, the destination should be an address currently assigned on-device.
+ * @return the reserved SecurityParameterIndex
+ * @throws ResourceUnavailableException indicating that too many SPIs are
+ * currently allocated for this user
+ */
+ @NonNull
+ public SecurityParameterIndex allocateSecurityParameterIndex(
+ @NonNull InetAddress destinationAddress) throws ResourceUnavailableException {
+ try {
+ return new SecurityParameterIndex(
+ mService,
+ destinationAddress,
+ IpSecManager.INVALID_SECURITY_PARAMETER_INDEX);
+ } catch (ServiceSpecificException e) {
+ throw rethrowUncheckedExceptionFromServiceSpecificException(e);
+ } catch (SpiUnavailableException unlikely) {
+ // Because this function allocates a totally random SPI, it really shouldn't ever
+ // fail to allocate an SPI; we simply need this because the exception is checked.
+ throw new ResourceUnavailableException("No SPIs available");
+ }
+ }
+
+ /**
+ * Reserve the requested SPI for traffic bound to or from the specified destination address.
+ *
+ * <p>If successful, this SPI is guaranteed available until released by a call to {@link
+ * SecurityParameterIndex#close()}.
+ *
+ * @param destinationAddress the destination address for traffic bearing the requested SPI.
+ * For inbound traffic, the destination should be an address currently assigned on-device.
+ * @param requestedSpi the requested SPI. The range 1-255 is reserved and may not be used. See
+ * RFC 4303 Section 2.1.
+ * @return the reserved SecurityParameterIndex
+ * @throws ResourceUnavailableException indicating that too many SPIs are
+ * currently allocated for this user
+ * @throws SpiUnavailableException indicating that the requested SPI could not be
+ * reserved
+ */
+ @NonNull
+ public SecurityParameterIndex allocateSecurityParameterIndex(
+ @NonNull InetAddress destinationAddress, int requestedSpi)
+ throws SpiUnavailableException, ResourceUnavailableException {
+ if (requestedSpi == IpSecManager.INVALID_SECURITY_PARAMETER_INDEX) {
+ throw new IllegalArgumentException("Requested SPI must be a valid (non-zero) SPI");
+ }
+ try {
+ return new SecurityParameterIndex(mService, destinationAddress, requestedSpi);
+ } catch (ServiceSpecificException e) {
+ throw rethrowUncheckedExceptionFromServiceSpecificException(e);
+ }
+ }
+
+ /**
+ * Apply an IPsec transform to a stream socket.
+ *
+ * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the
+ * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When
+ * the transform is removed from the socket by calling {@link #removeTransportModeTransforms},
+ * unprotected traffic can resume on that socket.
+ *
+ * <p>For security reasons, the destination address of any traffic on the socket must match the
+ * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any
+ * other IP address will result in an IOException. In addition, reads and writes on the socket
+ * will throw IOException if the user deactivates the transform (by calling {@link
+ * IpSecTransform#close()}) without calling {@link #removeTransportModeTransforms}.
+ *
+ * <p>Note that when applied to TCP sockets, calling {@link IpSecTransform#close()} on an
+ * applied transform before completion of graceful shutdown may result in the shutdown sequence
+ * failing to complete. As such, applications requiring graceful shutdown MUST close the socket
+ * prior to deactivating the applied transform. Socket closure may be performed asynchronously
+ * (in batches), so the returning of a close function does not guarantee shutdown of a socket.
+ * Setting an SO_LINGER timeout results in socket closure being performed synchronously, and is
+ * sufficient to ensure shutdown.
+ *
+ * Specifically, if the transform is deactivated (by calling {@link IpSecTransform#close()}),
+ * prior to the socket being closed, the standard [FIN - FIN/ACK - ACK], or the reset [RST]
+ * packets are dropped due to the lack of a valid Transform. Similarly, if a socket without the
+ * SO_LINGER option set is closed, the delayed/batched FIN packets may be dropped.
+ *
+ * <h4>Rekey Procedure</h4>
+ *
+ * <p>When applying a new tranform to a socket in the outbound direction, the previous transform
+ * will be removed and the new transform will take effect immediately, sending all traffic on
+ * the new transform; however, when applying a transform in the inbound direction, traffic
+ * on the old transform will continue to be decrypted and delivered until that transform is
+ * deallocated by calling {@link IpSecTransform#close()}. This overlap allows lossless rekey
+ * procedures where both transforms are valid until both endpoints are using the new transform
+ * and all in-flight packets have been received.
+ *
+ * @param socket a stream socket
+ * @param direction the direction in which the transform should be applied
+ * @param transform a transport mode {@code IpSecTransform}
+ * @throws IOException indicating that the transform could not be applied
+ */
+ public void applyTransportModeTransform(@NonNull Socket socket,
+ @PolicyDirection int direction, @NonNull IpSecTransform transform) throws IOException {
+ // Ensure creation of FD. See b/77548890 for more details.
+ socket.getSoLinger();
+
+ applyTransportModeTransform(socket.getFileDescriptor$(), direction, transform);
+ }
+
+ /**
+ * Apply an IPsec transform to a datagram socket.
+ *
+ * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the
+ * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When
+ * the transform is removed from the socket by calling {@link #removeTransportModeTransforms},
+ * unprotected traffic can resume on that socket.
+ *
+ * <p>For security reasons, the destination address of any traffic on the socket must match the
+ * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any
+ * other IP address will result in an IOException. In addition, reads and writes on the socket
+ * will throw IOException if the user deactivates the transform (by calling {@link
+ * IpSecTransform#close()}) without calling {@link #removeTransportModeTransforms}.
+ *
+ * <h4>Rekey Procedure</h4>
+ *
+ * <p>When applying a new tranform to a socket in the outbound direction, the previous transform
+ * will be removed and the new transform will take effect immediately, sending all traffic on
+ * the new transform; however, when applying a transform in the inbound direction, traffic
+ * on the old transform will continue to be decrypted and delivered until that transform is
+ * deallocated by calling {@link IpSecTransform#close()}. This overlap allows lossless rekey
+ * procedures where both transforms are valid until both endpoints are using the new transform
+ * and all in-flight packets have been received.
+ *
+ * @param socket a datagram socket
+ * @param direction the direction in which the transform should be applied
+ * @param transform a transport mode {@code IpSecTransform}
+ * @throws IOException indicating that the transform could not be applied
+ */
+ public void applyTransportModeTransform(@NonNull DatagramSocket socket,
+ @PolicyDirection int direction, @NonNull IpSecTransform transform) throws IOException {
+ applyTransportModeTransform(socket.getFileDescriptor$(), direction, transform);
+ }
+
+ /**
+ * Apply an IPsec transform to a socket.
+ *
+ * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the
+ * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When
+ * the transform is removed from the socket by calling {@link #removeTransportModeTransforms},
+ * unprotected traffic can resume on that socket.
+ *
+ * <p>For security reasons, the destination address of any traffic on the socket must match the
+ * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any
+ * other IP address will result in an IOException. In addition, reads and writes on the socket
+ * will throw IOException if the user deactivates the transform (by calling {@link
+ * IpSecTransform#close()}) without calling {@link #removeTransportModeTransforms}.
+ *
+ * <p>Note that when applied to TCP sockets, calling {@link IpSecTransform#close()} on an
+ * applied transform before completion of graceful shutdown may result in the shutdown sequence
+ * failing to complete. As such, applications requiring graceful shutdown MUST close the socket
+ * prior to deactivating the applied transform. Socket closure may be performed asynchronously
+ * (in batches), so the returning of a close function does not guarantee shutdown of a socket.
+ * Setting an SO_LINGER timeout results in socket closure being performed synchronously, and is
+ * sufficient to ensure shutdown.
+ *
+ * Specifically, if the transform is deactivated (by calling {@link IpSecTransform#close()}),
+ * prior to the socket being closed, the standard [FIN - FIN/ACK - ACK], or the reset [RST]
+ * packets are dropped due to the lack of a valid Transform. Similarly, if a socket without the
+ * SO_LINGER option set is closed, the delayed/batched FIN packets may be dropped.
+ *
+ * <h4>Rekey Procedure</h4>
+ *
+ * <p>When applying a new tranform to a socket in the outbound direction, the previous transform
+ * will be removed and the new transform will take effect immediately, sending all traffic on
+ * the new transform; however, when applying a transform in the inbound direction, traffic
+ * on the old transform will continue to be decrypted and delivered until that transform is
+ * deallocated by calling {@link IpSecTransform#close()}. This overlap allows lossless rekey
+ * procedures where both transforms are valid until both endpoints are using the new transform
+ * and all in-flight packets have been received.
+ *
+ * @param socket a socket file descriptor
+ * @param direction the direction in which the transform should be applied
+ * @param transform a transport mode {@code IpSecTransform}
+ * @throws IOException indicating that the transform could not be applied
+ */
+ public void applyTransportModeTransform(@NonNull FileDescriptor socket,
+ @PolicyDirection int direction, @NonNull IpSecTransform transform) throws IOException {
+ // We dup() the FileDescriptor here because if we don't, then the ParcelFileDescriptor()
+ // constructor takes control and closes the user's FD when we exit the method.
+ try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(socket)) {
+ mService.applyTransportModeTransform(pfd, direction, transform.getResourceId());
+ } catch (ServiceSpecificException e) {
+ throw rethrowCheckedExceptionFromServiceSpecificException(e);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Remove an IPsec transform from a stream socket.
+ *
+ * <p>Once removed, traffic on the socket will not be encrypted. Removing transforms from a
+ * socket allows the socket to be reused for communication in the clear.
+ *
+ * <p>If an {@code IpSecTransform} object applied to this socket was deallocated by calling
+ * {@link IpSecTransform#close()}, then communication on the socket will fail until this method
+ * is called.
+ *
+ * @param socket a socket that previously had a transform applied to it
+ * @throws IOException indicating that the transform could not be removed from the socket
+ */
+ public void removeTransportModeTransforms(@NonNull Socket socket) throws IOException {
+ // Ensure creation of FD. See b/77548890 for more details.
+ socket.getSoLinger();
+
+ removeTransportModeTransforms(socket.getFileDescriptor$());
+ }
+
+ /**
+ * Remove an IPsec transform from a datagram socket.
+ *
+ * <p>Once removed, traffic on the socket will not be encrypted. Removing transforms from a
+ * socket allows the socket to be reused for communication in the clear.
+ *
+ * <p>If an {@code IpSecTransform} object applied to this socket was deallocated by calling
+ * {@link IpSecTransform#close()}, then communication on the socket will fail until this method
+ * is called.
+ *
+ * @param socket a socket that previously had a transform applied to it
+ * @throws IOException indicating that the transform could not be removed from the socket
+ */
+ public void removeTransportModeTransforms(@NonNull DatagramSocket socket) throws IOException {
+ removeTransportModeTransforms(socket.getFileDescriptor$());
+ }
+
+ /**
+ * Remove an IPsec transform from a socket.
+ *
+ * <p>Once removed, traffic on the socket will not be encrypted. Removing transforms from a
+ * socket allows the socket to be reused for communication in the clear.
+ *
+ * <p>If an {@code IpSecTransform} object applied to this socket was deallocated by calling
+ * {@link IpSecTransform#close()}, then communication on the socket will fail until this method
+ * is called.
+ *
+ * @param socket a socket that previously had a transform applied to it
+ * @throws IOException indicating that the transform could not be removed from the socket
+ */
+ public void removeTransportModeTransforms(@NonNull FileDescriptor socket) throws IOException {
+ try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(socket)) {
+ mService.removeTransportModeTransforms(pfd);
+ } catch (ServiceSpecificException e) {
+ throw rethrowCheckedExceptionFromServiceSpecificException(e);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Remove a Tunnel Mode IPsec Transform from a {@link Network}. This must be used as part of
+ * cleanup if a tunneled Network experiences a change in default route. The Network will drop
+ * all traffic that cannot be routed to the Tunnel's outbound interface. If that interface is
+ * lost, all traffic will drop.
+ *
+ * <p>TODO: Update javadoc for tunnel mode APIs at the same time the APIs are re-worked.
+ *
+ * @param net a network that currently has transform applied to it.
+ * @param transform a Tunnel Mode IPsec Transform that has been previously applied to the given
+ * network
+ * @hide
+ */
+ public void removeTunnelModeTransform(Network net, IpSecTransform transform) {}
+
+ /**
+ * This class provides access to a UDP encapsulation Socket.
+ *
+ * <p>{@code UdpEncapsulationSocket} wraps a system-provided datagram socket intended for IKEv2
+ * signalling and UDP encapsulated IPsec traffic. Instances can be obtained by calling {@link
+ * IpSecManager#openUdpEncapsulationSocket}. The provided socket cannot be re-bound by the
+ * caller. The caller should not close the {@code FileDescriptor} returned by {@link
+ * #getFileDescriptor}, but should use {@link #close} instead.
+ *
+ * <p>Allowing the user to close or unbind a UDP encapsulation socket could impact the traffic
+ * of the next user who binds to that port. To prevent this scenario, these sockets are held
+ * open by the system so that they may only be closed by calling {@link #close} or when the user
+ * process exits.
+ */
+ public static final class UdpEncapsulationSocket implements AutoCloseable {
+ private final ParcelFileDescriptor mPfd;
+ private final IIpSecService mService;
+ private int mResourceId = INVALID_RESOURCE_ID;
+ private final int mPort;
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+
+ private UdpEncapsulationSocket(@NonNull IIpSecService service, int port)
+ throws ResourceUnavailableException, IOException {
+ mService = service;
+ try {
+ IpSecUdpEncapResponse result =
+ mService.openUdpEncapsulationSocket(port, new Binder());
+ switch (result.status) {
+ case Status.OK:
+ break;
+ case Status.RESOURCE_UNAVAILABLE:
+ throw new ResourceUnavailableException(
+ "No more Sockets may be allocated by this requester.");
+ default:
+ throw new RuntimeException(
+ "Unknown status returned by IpSecService: " + result.status);
+ }
+ mResourceId = result.resourceId;
+ mPort = result.port;
+ mPfd = result.fileDescriptor;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ mCloseGuard.open("constructor");
+ }
+
+ /** Get the encapsulation socket's file descriptor. */
+ public FileDescriptor getFileDescriptor() {
+ if (mPfd == null) {
+ return null;
+ }
+ return mPfd.getFileDescriptor();
+ }
+
+ /** Get the bound port of the wrapped socket. */
+ public int getPort() {
+ return mPort;
+ }
+
+ /**
+ * Close this socket.
+ *
+ * <p>This closes the wrapped socket. Open encapsulation sockets count against a user's
+ * resource limits, and forgetting to close them eventually will result in {@link
+ * ResourceUnavailableException} being thrown.
+ */
+ @Override
+ public void close() throws IOException {
+ try {
+ mService.closeUdpEncapsulationSocket(mResourceId);
+ mResourceId = INVALID_RESOURCE_ID;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (Exception e) {
+ // On close we swallow all random exceptions since failure to close is not
+ // actionable by the user.
+ Log.e(TAG, "Failed to close " + this + ", Exception=" + e);
+ } finally {
+ mResourceId = INVALID_RESOURCE_ID;
+ mCloseGuard.close();
+ }
+
+ try {
+ mPfd.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to close UDP Encapsulation Socket with Port= " + mPort);
+ throw e;
+ }
+ }
+
+ /** Check that the socket was closed properly. */
+ @Override
+ protected void finalize() throws Throwable {
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+ close();
+ }
+
+ /** @hide */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public int getResourceId() {
+ return mResourceId;
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder()
+ .append("UdpEncapsulationSocket{port=")
+ .append(mPort)
+ .append(",resourceId=")
+ .append(mResourceId)
+ .append("}")
+ .toString();
+ }
+ };
+
+ /**
+ * Open a socket for UDP encapsulation and bind to the given port.
+ *
+ * <p>See {@link UdpEncapsulationSocket} for the proper way to close the returned socket.
+ *
+ * @param port a local UDP port
+ * @return a socket that is bound to the given port
+ * @throws IOException indicating that the socket could not be opened or bound
+ * @throws ResourceUnavailableException indicating that too many encapsulation sockets are open
+ */
+ // Returning a socket in this fashion that has been created and bound by the system
+ // is the only safe way to ensure that a socket is both accessible to the user and
+ // safely usable for Encapsulation without allowing a user to possibly unbind from/close
+ // the port, which could potentially impact the traffic of the next user who binds to that
+ // socket.
+ @NonNull
+ public UdpEncapsulationSocket openUdpEncapsulationSocket(int port)
+ throws IOException, ResourceUnavailableException {
+ /*
+ * Most range checking is done in the service, but this version of the constructor expects
+ * a valid port number, and zero cannot be checked after being passed to the service.
+ */
+ if (port == 0) {
+ throw new IllegalArgumentException("Specified port must be a valid port number!");
+ }
+ try {
+ return new UdpEncapsulationSocket(mService, port);
+ } catch (ServiceSpecificException e) {
+ throw rethrowCheckedExceptionFromServiceSpecificException(e);
+ }
+ }
+
+ /**
+ * Open a socket for UDP encapsulation.
+ *
+ * <p>See {@link UdpEncapsulationSocket} for the proper way to close the returned socket.
+ *
+ * <p>The local port of the returned socket can be obtained by calling {@link
+ * UdpEncapsulationSocket#getPort()}.
+ *
+ * @return a socket that is bound to a local port
+ * @throws IOException indicating that the socket could not be opened or bound
+ * @throws ResourceUnavailableException indicating that too many encapsulation sockets are open
+ */
+ // Returning a socket in this fashion that has been created and bound by the system
+ // is the only safe way to ensure that a socket is both accessible to the user and
+ // safely usable for Encapsulation without allowing a user to possibly unbind from/close
+ // the port, which could potentially impact the traffic of the next user who binds to that
+ // socket.
+ @NonNull
+ public UdpEncapsulationSocket openUdpEncapsulationSocket()
+ throws IOException, ResourceUnavailableException {
+ try {
+ return new UdpEncapsulationSocket(mService, 0);
+ } catch (ServiceSpecificException e) {
+ throw rethrowCheckedExceptionFromServiceSpecificException(e);
+ }
+ }
+
+ /**
+ * This class represents an IpSecTunnelInterface
+ *
+ * <p>IpSecTunnelInterface objects track tunnel interfaces that serve as
+ * local endpoints for IPsec tunnels.
+ *
+ * <p>Creating an IpSecTunnelInterface creates a device to which IpSecTransforms may be
+ * applied to provide IPsec security to packets sent through the tunnel. While a tunnel
+ * cannot be used in standalone mode within Android, the higher layers may use the tunnel
+ * to create Network objects which are accessible to the Android system.
+ * @hide
+ */
+ @SystemApi
+ public static final class IpSecTunnelInterface implements AutoCloseable {
+ private final String mOpPackageName;
+ private final IIpSecService mService;
+ private final InetAddress mRemoteAddress;
+ private final InetAddress mLocalAddress;
+ private final Network mUnderlyingNetwork;
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+ private String mInterfaceName;
+ private int mResourceId = INVALID_RESOURCE_ID;
+
+ /** Get the underlying SPI held by this object. */
+ @NonNull
+ public String getInterfaceName() {
+ return mInterfaceName;
+ }
+
+ /**
+ * Add an address to the IpSecTunnelInterface
+ *
+ * <p>Add an address which may be used as the local inner address for
+ * tunneled traffic.
+ *
+ * @param address the local address for traffic inside the tunnel
+ * @param prefixLen length of the InetAddress prefix
+ * @hide
+ */
+ @SystemApi
+ @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
+ @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS)
+ public void addAddress(@NonNull InetAddress address, int prefixLen) throws IOException {
+ try {
+ mService.addAddressToTunnelInterface(
+ mResourceId, new LinkAddress(address, prefixLen), mOpPackageName);
+ } catch (ServiceSpecificException e) {
+ throw rethrowCheckedExceptionFromServiceSpecificException(e);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Remove an address from the IpSecTunnelInterface
+ *
+ * <p>Remove an address which was previously added to the IpSecTunnelInterface
+ *
+ * @param address to be removed
+ * @param prefixLen length of the InetAddress prefix
+ * @hide
+ */
+ @SystemApi
+ @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
+ @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS)
+ public void removeAddress(@NonNull InetAddress address, int prefixLen) throws IOException {
+ try {
+ mService.removeAddressFromTunnelInterface(
+ mResourceId, new LinkAddress(address, prefixLen), mOpPackageName);
+ } catch (ServiceSpecificException e) {
+ throw rethrowCheckedExceptionFromServiceSpecificException(e);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Update the underlying network for this IpSecTunnelInterface.
+ *
+ * <p>This new underlying network will be used for all transforms applied AFTER this call is
+ * complete. Before new {@link IpSecTransform}(s) with matching addresses are applied to
+ * this tunnel interface, traffic will still use the old SA, and be routed on the old
+ * underlying network.
+ *
+ * <p>To migrate IPsec tunnel mode traffic, a caller should:
+ *
+ * <ol>
+ * <li>Update the IpSecTunnelInterface’s underlying network.
+ * <li>Apply {@link IpSecTransform}(s) with matching addresses to this
+ * IpSecTunnelInterface.
+ * </ol>
+ *
+ * @param underlyingNetwork the new {@link Network} that will carry traffic for this tunnel.
+ * This network MUST be a functional {@link Network} with valid {@link LinkProperties},
+ * and MUST never be the network exposing this IpSecTunnelInterface, otherwise this
+ * method will throw an {@link IllegalArgumentException}. If the IpSecTunnelInterface is
+ * later added to this network, all outbound traffic will be blackholed.
+ */
+ // TODO: b/169171001 Update the documentation when transform migration is supported.
+ // The purpose of making updating network and applying transforms separate is to leave open
+ // the possibility to support lossless migration procedures. To do that, Android platform
+ // will need to support multiple inbound tunnel mode transforms, just like it can support
+ // multiple transport mode transforms.
+ @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
+ @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS)
+ public void setUnderlyingNetwork(@NonNull Network underlyingNetwork) throws IOException {
+ try {
+ mService.setNetworkForTunnelInterface(
+ mResourceId, underlyingNetwork, mOpPackageName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private IpSecTunnelInterface(@NonNull Context ctx, @NonNull IIpSecService service,
+ @NonNull InetAddress localAddress, @NonNull InetAddress remoteAddress,
+ @NonNull Network underlyingNetwork)
+ throws ResourceUnavailableException, IOException {
+ mOpPackageName = ctx.getOpPackageName();
+ mService = service;
+ mLocalAddress = localAddress;
+ mRemoteAddress = remoteAddress;
+ mUnderlyingNetwork = underlyingNetwork;
+
+ try {
+ IpSecTunnelInterfaceResponse result =
+ mService.createTunnelInterface(
+ localAddress.getHostAddress(),
+ remoteAddress.getHostAddress(),
+ underlyingNetwork,
+ new Binder(),
+ mOpPackageName);
+ switch (result.status) {
+ case Status.OK:
+ break;
+ case Status.RESOURCE_UNAVAILABLE:
+ throw new ResourceUnavailableException(
+ "No more tunnel interfaces may be allocated by this requester.");
+ default:
+ throw new RuntimeException(
+ "Unknown status returned by IpSecService: " + result.status);
+ }
+ mResourceId = result.resourceId;
+ mInterfaceName = result.interfaceName;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ mCloseGuard.open("constructor");
+ }
+
+ /**
+ * Delete an IpSecTunnelInterface
+ *
+ * <p>Calling close will deallocate the IpSecTunnelInterface and all of its system
+ * resources. Any packets bound for this interface either inbound or outbound will
+ * all be lost.
+ */
+ @Override
+ public void close() {
+ try {
+ mService.deleteTunnelInterface(mResourceId, mOpPackageName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (Exception e) {
+ // On close we swallow all random exceptions since failure to close is not
+ // actionable by the user.
+ Log.e(TAG, "Failed to close " + this + ", Exception=" + e);
+ } finally {
+ mResourceId = INVALID_RESOURCE_ID;
+ mCloseGuard.close();
+ }
+ }
+
+ /** Check that the Interface was closed properly. */
+ @Override
+ protected void finalize() throws Throwable {
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+ close();
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public int getResourceId() {
+ return mResourceId;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return new StringBuilder()
+ .append("IpSecTunnelInterface{ifname=")
+ .append(mInterfaceName)
+ .append(",resourceId=")
+ .append(mResourceId)
+ .append("}")
+ .toString();
+ }
+ }
+
+ /**
+ * Create a new IpSecTunnelInterface as a local endpoint for tunneled IPsec traffic.
+ *
+ * <p>An application that creates tunnels is responsible for cleaning up the tunnel when the
+ * underlying network goes away, and the onLost() callback is received.
+ *
+ * @param localAddress The local addres of the tunnel
+ * @param remoteAddress The local addres of the tunnel
+ * @param underlyingNetwork the {@link Network} that will carry traffic for this tunnel.
+ * This network should almost certainly be a network such as WiFi with an L2 address.
+ * @return a new {@link IpSecManager#IpSecTunnelInterface} with the specified properties
+ * @throws IOException indicating that the socket could not be opened or bound
+ * @throws ResourceUnavailableException indicating that too many encapsulation sockets are open
+ * @hide
+ */
+ @SystemApi
+ @NonNull
+ @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
+ @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS)
+ public IpSecTunnelInterface createIpSecTunnelInterface(@NonNull InetAddress localAddress,
+ @NonNull InetAddress remoteAddress, @NonNull Network underlyingNetwork)
+ throws ResourceUnavailableException, IOException {
+ try {
+ return new IpSecTunnelInterface(
+ mContext, mService, localAddress, remoteAddress, underlyingNetwork);
+ } catch (ServiceSpecificException e) {
+ throw rethrowCheckedExceptionFromServiceSpecificException(e);
+ }
+ }
+
+ /**
+ * Apply an active Tunnel Mode IPsec Transform to a {@link IpSecTunnelInterface}, which will
+ * tunnel all traffic for the given direction through the underlying network's interface with
+ * IPsec (applies an outer IP header and IPsec Header to all traffic, and expects an additional
+ * IP header and IPsec Header on all inbound traffic).
+ * <p>Applications should probably not use this API directly.
+ *
+ * @param tunnel The {@link IpSecManager#IpSecTunnelInterface} that will use the supplied
+ * transform.
+ * @param direction the direction, {@link DIRECTION_OUT} or {@link #DIRECTION_IN} in which
+ * the transform will be used.
+ * @param transform an {@link IpSecTransform} created in tunnel mode
+ * @throws IOException indicating that the transform could not be applied due to a lower
+ * layer failure.
+ * @hide
+ */
+ @SystemApi
+ @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
+ @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS)
+ public void applyTunnelModeTransform(@NonNull IpSecTunnelInterface tunnel,
+ @PolicyDirection int direction, @NonNull IpSecTransform transform) throws IOException {
+ try {
+ mService.applyTunnelModeTransform(
+ tunnel.getResourceId(), direction,
+ transform.getResourceId(), mContext.getOpPackageName());
+ } catch (ServiceSpecificException e) {
+ throw rethrowCheckedExceptionFromServiceSpecificException(e);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public IpSecTransformResponse createTransform(IpSecConfig config, IBinder binder,
+ String callingPackage) {
+ try {
+ return mService.createTransform(config, binder, callingPackage);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void deleteTransform(int resourceId) {
+ try {
+ mService.deleteTransform(resourceId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Construct an instance of IpSecManager within an application context.
+ *
+ * @param context the application context for this manager
+ * @hide
+ */
+ public IpSecManager(Context ctx, IIpSecService service) {
+ mContext = ctx;
+ mService = Objects.requireNonNull(service, "missing service");
+ }
+
+ private static void maybeHandleServiceSpecificException(ServiceSpecificException sse) {
+ // OsConstants are late binding, so switch statements can't be used.
+ if (sse.errorCode == OsConstants.EINVAL) {
+ throw new IllegalArgumentException(sse);
+ } else if (sse.errorCode == OsConstants.EAGAIN) {
+ throw new IllegalStateException(sse);
+ } else if (sse.errorCode == OsConstants.EOPNOTSUPP
+ || sse.errorCode == OsConstants.EPROTONOSUPPORT) {
+ throw new UnsupportedOperationException(sse);
+ }
+ }
+
+ /**
+ * Convert an Errno SSE to the correct Unchecked exception type.
+ *
+ * This method never actually returns.
+ */
+ // package
+ static RuntimeException
+ rethrowUncheckedExceptionFromServiceSpecificException(ServiceSpecificException sse) {
+ maybeHandleServiceSpecificException(sse);
+ throw new RuntimeException(sse);
+ }
+
+ /**
+ * Convert an Errno SSE to the correct Checked or Unchecked exception type.
+ *
+ * This method may throw IOException, or it may throw an unchecked exception; it will never
+ * actually return.
+ */
+ // package
+ static IOException rethrowCheckedExceptionFromServiceSpecificException(
+ ServiceSpecificException sse) throws IOException {
+ // First see if this is an unchecked exception of a type we know.
+ // If so, then we prefer the unchecked (specific) type of exception.
+ maybeHandleServiceSpecificException(sse);
+ // If not, then all we can do is provide the SSE in the form of an IOException.
+ throw new ErrnoException(
+ "IpSec encountered errno=" + sse.errorCode, sse.errorCode).rethrowAsIOException();
+ }
+}
diff --git a/framework-t/src/android/net/IpSecSpiResponse.aidl b/framework-t/src/android/net/IpSecSpiResponse.aidl
new file mode 100644
index 0000000..6484a00
--- /dev/null
+++ b/framework-t/src/android/net/IpSecSpiResponse.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+/** @hide */
+parcelable IpSecSpiResponse;
diff --git a/framework-t/src/android/net/IpSecSpiResponse.java b/framework-t/src/android/net/IpSecSpiResponse.java
new file mode 100644
index 0000000..f99e570
--- /dev/null
+++ b/framework-t/src/android/net/IpSecSpiResponse.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class is used to return an SPI and corresponding status from the IpSecService to an
+ * IpSecManager.SecurityParameterIndex.
+ *
+ * @hide
+ */
+public final class IpSecSpiResponse implements Parcelable {
+ private static final String TAG = "IpSecSpiResponse";
+
+ public final int resourceId;
+ public final int status;
+ public final int spi;
+ // Parcelable Methods
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(status);
+ out.writeInt(resourceId);
+ out.writeInt(spi);
+ }
+
+ public IpSecSpiResponse(int inStatus, int inResourceId, int inSpi) {
+ status = inStatus;
+ resourceId = inResourceId;
+ spi = inSpi;
+ }
+
+ public IpSecSpiResponse(int inStatus) {
+ if (inStatus == IpSecManager.Status.OK) {
+ throw new IllegalArgumentException("Valid status implies other args must be provided");
+ }
+ status = inStatus;
+ resourceId = IpSecManager.INVALID_RESOURCE_ID;
+ spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
+ }
+
+ private IpSecSpiResponse(Parcel in) {
+ status = in.readInt();
+ resourceId = in.readInt();
+ spi = in.readInt();
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<IpSecSpiResponse> CREATOR =
+ new Parcelable.Creator<IpSecSpiResponse>() {
+ public IpSecSpiResponse createFromParcel(Parcel in) {
+ return new IpSecSpiResponse(in);
+ }
+
+ public IpSecSpiResponse[] newArray(int size) {
+ return new IpSecSpiResponse[size];
+ }
+ };
+}
diff --git a/framework-t/src/android/net/IpSecTransform.java b/framework-t/src/android/net/IpSecTransform.java
new file mode 100644
index 0000000..68ae5de
--- /dev/null
+++ b/framework-t/src/android/net/IpSecTransform.java
@@ -0,0 +1,405 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net;
+
+import static android.net.IpSecManager.INVALID_RESOURCE_ID;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresFeature;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.ServiceSpecificException;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import dalvik.system.CloseGuard;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.net.InetAddress;
+import java.util.Objects;
+
+/**
+ * This class represents a transform, which roughly corresponds to an IPsec Security Association.
+ *
+ * <p>Transforms are created using {@link IpSecTransform.Builder}. Each {@code IpSecTransform}
+ * object encapsulates the properties and state of an IPsec security association. That includes,
+ * but is not limited to, algorithm choice, key material, and allocated system resources.
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the
+ * Internet Protocol</a>
+ */
+public final class IpSecTransform implements AutoCloseable {
+ private static final String TAG = "IpSecTransform";
+
+ /** @hide */
+ public static final int MODE_TRANSPORT = 0;
+
+ /** @hide */
+ public static final int MODE_TUNNEL = 1;
+
+ /** @hide */
+ public static final int ENCAP_NONE = 0;
+
+ /**
+ * IPsec traffic will be encapsulated within UDP, but with 8 zero-value bytes between the UDP
+ * header and payload. This prevents traffic from being interpreted as ESP or IKEv2.
+ *
+ * @hide
+ */
+ public static final int ENCAP_ESPINUDP_NON_IKE = 1;
+
+ /**
+ * IPsec traffic will be encapsulated within UDP as per
+ * <a href="https://tools.ietf.org/html/rfc3948">RFC 3498</a>.
+ *
+ * @hide
+ */
+ public static final int ENCAP_ESPINUDP = 2;
+
+ /** @hide */
+ @IntDef(value = {ENCAP_NONE, ENCAP_ESPINUDP, ENCAP_ESPINUDP_NON_IKE})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EncapType {}
+
+ /** @hide */
+ @VisibleForTesting
+ public IpSecTransform(Context context, IpSecConfig config) {
+ mContext = context;
+ mConfig = new IpSecConfig(config);
+ mResourceId = INVALID_RESOURCE_ID;
+ }
+
+ private IpSecManager getIpSecManager(Context context) {
+ return context.getSystemService(IpSecManager.class);
+ }
+ /**
+ * Checks the result status and throws an appropriate exception if the status is not Status.OK.
+ */
+ private void checkResultStatus(int status)
+ throws IOException, IpSecManager.ResourceUnavailableException,
+ IpSecManager.SpiUnavailableException {
+ switch (status) {
+ case IpSecManager.Status.OK:
+ return;
+ // TODO: Pass Error string back from bundle so that errors can be more specific
+ case IpSecManager.Status.RESOURCE_UNAVAILABLE:
+ throw new IpSecManager.ResourceUnavailableException(
+ "Failed to allocate a new IpSecTransform");
+ case IpSecManager.Status.SPI_UNAVAILABLE:
+ Log.wtf(TAG, "Attempting to use an SPI that was somehow not reserved");
+ // Fall through
+ default:
+ throw new IllegalStateException(
+ "Failed to Create a Transform with status code " + status);
+ }
+ }
+
+ private IpSecTransform activate()
+ throws IOException, IpSecManager.ResourceUnavailableException,
+ IpSecManager.SpiUnavailableException {
+ synchronized (this) {
+ try {
+ IpSecTransformResponse result = getIpSecManager(mContext).createTransform(
+ mConfig, new Binder(), mContext.getOpPackageName());
+ int status = result.status;
+ checkResultStatus(status);
+ mResourceId = result.resourceId;
+ Log.d(TAG, "Added Transform with Id " + mResourceId);
+ mCloseGuard.open("build");
+ } catch (ServiceSpecificException e) {
+ throw IpSecManager.rethrowUncheckedExceptionFromServiceSpecificException(e);
+ }
+ }
+
+ return this;
+ }
+
+ /**
+ * Standard equals.
+ */
+ public boolean equals(@Nullable Object other) {
+ if (this == other) return true;
+ if (!(other instanceof IpSecTransform)) return false;
+ final IpSecTransform rhs = (IpSecTransform) other;
+ return getConfig().equals(rhs.getConfig()) && mResourceId == rhs.mResourceId;
+ }
+
+ /**
+ * Deactivate this {@code IpSecTransform} and free allocated resources.
+ *
+ * <p>Deactivating a transform while it is still applied to a socket will result in errors on
+ * that socket. Make sure to remove transforms by calling {@link
+ * IpSecManager#removeTransportModeTransforms}. Note, removing an {@code IpSecTransform} from a
+ * socket will not deactivate it (because one transform may be applied to multiple sockets).
+ *
+ * <p>It is safe to call this method on a transform that has already been deactivated.
+ */
+ public void close() {
+ Log.d(TAG, "Removing Transform with Id " + mResourceId);
+
+ // Always safe to attempt cleanup
+ if (mResourceId == INVALID_RESOURCE_ID) {
+ mCloseGuard.close();
+ return;
+ }
+ try {
+ getIpSecManager(mContext).deleteTransform(mResourceId);
+ } catch (Exception e) {
+ // On close we swallow all random exceptions since failure to close is not
+ // actionable by the user.
+ Log.e(TAG, "Failed to close " + this + ", Exception=" + e);
+ } finally {
+ mResourceId = INVALID_RESOURCE_ID;
+ mCloseGuard.close();
+ }
+ }
+
+ /** Check that the transform was closed properly. */
+ @Override
+ protected void finalize() throws Throwable {
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+ close();
+ }
+
+ /* Package */
+ IpSecConfig getConfig() {
+ return mConfig;
+ }
+
+ private final IpSecConfig mConfig;
+ private int mResourceId;
+ private final Context mContext;
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+
+ /** @hide */
+ @VisibleForTesting
+ public int getResourceId() {
+ return mResourceId;
+ }
+
+ /**
+ * A callback class to provide status information regarding a NAT-T keepalive session
+ *
+ * <p>Use this callback to receive status information regarding a NAT-T keepalive session
+ * by registering it when calling {@link #startNattKeepalive}.
+ *
+ * @hide
+ */
+ public static class NattKeepaliveCallback {
+ /** The specified {@code Network} is not connected. */
+ public static final int ERROR_INVALID_NETWORK = 1;
+ /** The hardware does not support this request. */
+ public static final int ERROR_HARDWARE_UNSUPPORTED = 2;
+ /** The hardware returned an error. */
+ public static final int ERROR_HARDWARE_ERROR = 3;
+
+ /** The requested keepalive was successfully started. */
+ public void onStarted() {}
+ /** The keepalive was successfully stopped. */
+ public void onStopped() {}
+ /** An error occurred. */
+ public void onError(int error) {}
+ }
+
+ /** This class is used to build {@link IpSecTransform} objects. */
+ public static class Builder {
+ private Context mContext;
+ private IpSecConfig mConfig;
+
+ /**
+ * Set the encryption algorithm.
+ *
+ * <p>Encryption is mutually exclusive with authenticated encryption.
+ *
+ * @param algo {@link IpSecAlgorithm} specifying the encryption to be applied.
+ */
+ @NonNull
+ public IpSecTransform.Builder setEncryption(@NonNull IpSecAlgorithm algo) {
+ // TODO: throw IllegalArgumentException if algo is not an encryption algorithm.
+ Objects.requireNonNull(algo);
+ mConfig.setEncryption(algo);
+ return this;
+ }
+
+ /**
+ * Set the authentication (integrity) algorithm.
+ *
+ * <p>Authentication is mutually exclusive with authenticated encryption.
+ *
+ * @param algo {@link IpSecAlgorithm} specifying the authentication to be applied.
+ */
+ @NonNull
+ public IpSecTransform.Builder setAuthentication(@NonNull IpSecAlgorithm algo) {
+ // TODO: throw IllegalArgumentException if algo is not an authentication algorithm.
+ Objects.requireNonNull(algo);
+ mConfig.setAuthentication(algo);
+ return this;
+ }
+
+ /**
+ * Set the authenticated encryption algorithm.
+ *
+ * <p>The Authenticated Encryption (AE) class of algorithms are also known as
+ * Authenticated Encryption with Associated Data (AEAD) algorithms, or Combined mode
+ * algorithms (as referred to in
+ * <a href="https://tools.ietf.org/html/rfc4301">RFC 4301</a>).
+ *
+ * <p>Authenticated encryption is mutually exclusive with encryption and authentication.
+ *
+ * @param algo {@link IpSecAlgorithm} specifying the authenticated encryption algorithm to
+ * be applied.
+ */
+ @NonNull
+ public IpSecTransform.Builder setAuthenticatedEncryption(@NonNull IpSecAlgorithm algo) {
+ Objects.requireNonNull(algo);
+ mConfig.setAuthenticatedEncryption(algo);
+ return this;
+ }
+
+ /**
+ * Add UDP encapsulation to an IPv4 transform.
+ *
+ * <p>This allows IPsec traffic to pass through a NAT.
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc3948">RFC 3948, UDP Encapsulation of IPsec
+ * ESP Packets</a>
+ * @see <a href="https://tools.ietf.org/html/rfc7296#section-2.23">RFC 7296 section 2.23,
+ * NAT Traversal of IKEv2</a>
+ * @param localSocket a socket for sending and receiving encapsulated traffic
+ * @param remotePort the UDP port number of the remote host that will send and receive
+ * encapsulated traffic. In the case of IKEv2, this should be port 4500.
+ */
+ @NonNull
+ public IpSecTransform.Builder setIpv4Encapsulation(
+ @NonNull IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) {
+ Objects.requireNonNull(localSocket);
+ mConfig.setEncapType(ENCAP_ESPINUDP);
+ if (localSocket.getResourceId() == INVALID_RESOURCE_ID) {
+ throw new IllegalArgumentException("Invalid UdpEncapsulationSocket");
+ }
+ mConfig.setEncapSocketResourceId(localSocket.getResourceId());
+ mConfig.setEncapRemotePort(remotePort);
+ return this;
+ }
+
+ /**
+ * Build a transport mode {@link IpSecTransform}.
+ *
+ * <p>This builds and activates a transport mode transform. Note that an active transform
+ * will not affect any network traffic until it has been applied to one or more sockets.
+ *
+ * @see IpSecManager#applyTransportModeTransform
+ * @param sourceAddress the source {@code InetAddress} of traffic on sockets that will use
+ * this transform; this address must belong to the Network used by all sockets that
+ * utilize this transform; if provided, then only traffic originating from the
+ * specified source address will be processed.
+ * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed
+ * traffic
+ * @throws IllegalArgumentException indicating that a particular combination of transform
+ * properties is invalid
+ * @throws IpSecManager.ResourceUnavailableException indicating that too many transforms
+ * are active
+ * @throws IpSecManager.SpiUnavailableException indicating the rare case where an SPI
+ * collides with an existing transform
+ * @throws IOException indicating other errors
+ */
+ @NonNull
+ public IpSecTransform buildTransportModeTransform(
+ @NonNull InetAddress sourceAddress,
+ @NonNull IpSecManager.SecurityParameterIndex spi)
+ throws IpSecManager.ResourceUnavailableException,
+ IpSecManager.SpiUnavailableException, IOException {
+ Objects.requireNonNull(sourceAddress);
+ Objects.requireNonNull(spi);
+ if (spi.getResourceId() == INVALID_RESOURCE_ID) {
+ throw new IllegalArgumentException("Invalid SecurityParameterIndex");
+ }
+ mConfig.setMode(MODE_TRANSPORT);
+ mConfig.setSourceAddress(sourceAddress.getHostAddress());
+ mConfig.setSpiResourceId(spi.getResourceId());
+ // FIXME: modifying a builder after calling build can change the built transform.
+ return new IpSecTransform(mContext, mConfig).activate();
+ }
+
+ /**
+ * Build and return an {@link IpSecTransform} object as a Tunnel Mode Transform. Some
+ * parameters have interdependencies that are checked at build time.
+ *
+ * @param sourceAddress the {@link InetAddress} that provides the source address for this
+ * IPsec tunnel. This is almost certainly an address belonging to the {@link Network}
+ * that will originate the traffic, which is set as the {@link #setUnderlyingNetwork}.
+ * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed
+ * traffic
+ * @throws IllegalArgumentException indicating that a particular combination of transform
+ * properties is invalid.
+ * @throws IpSecManager.ResourceUnavailableException indicating that too many transforms
+ * are active
+ * @throws IpSecManager.SpiUnavailableException indicating the rare case where an SPI
+ * collides with an existing transform
+ * @throws IOException indicating other errors
+ * @hide
+ */
+ @SystemApi
+ @NonNull
+ @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
+ @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS)
+ public IpSecTransform buildTunnelModeTransform(
+ @NonNull InetAddress sourceAddress,
+ @NonNull IpSecManager.SecurityParameterIndex spi)
+ throws IpSecManager.ResourceUnavailableException,
+ IpSecManager.SpiUnavailableException, IOException {
+ Objects.requireNonNull(sourceAddress);
+ Objects.requireNonNull(spi);
+ if (spi.getResourceId() == INVALID_RESOURCE_ID) {
+ throw new IllegalArgumentException("Invalid SecurityParameterIndex");
+ }
+ mConfig.setMode(MODE_TUNNEL);
+ mConfig.setSourceAddress(sourceAddress.getHostAddress());
+ mConfig.setSpiResourceId(spi.getResourceId());
+ return new IpSecTransform(mContext, mConfig).activate();
+ }
+
+ /**
+ * Create a new IpSecTransform.Builder.
+ *
+ * @param context current context
+ */
+ public Builder(@NonNull Context context) {
+ Objects.requireNonNull(context);
+ mContext = context;
+ mConfig = new IpSecConfig();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder()
+ .append("IpSecTransform{resourceId=")
+ .append(mResourceId)
+ .append("}")
+ .toString();
+ }
+}
diff --git a/framework-t/src/android/net/IpSecTransformResponse.aidl b/framework-t/src/android/net/IpSecTransformResponse.aidl
new file mode 100644
index 0000000..546230d
--- /dev/null
+++ b/framework-t/src/android/net/IpSecTransformResponse.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+/** @hide */
+parcelable IpSecTransformResponse;
diff --git a/framework-t/src/android/net/IpSecTransformResponse.java b/framework-t/src/android/net/IpSecTransformResponse.java
new file mode 100644
index 0000000..363f316
--- /dev/null
+++ b/framework-t/src/android/net/IpSecTransformResponse.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class is used to return an IpSecTransform resource Id and and corresponding status from the
+ * IpSecService to an IpSecTransform object.
+ *
+ * @hide
+ */
+public final class IpSecTransformResponse implements Parcelable {
+ private static final String TAG = "IpSecTransformResponse";
+
+ public final int resourceId;
+ public final int status;
+ // Parcelable Methods
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(status);
+ out.writeInt(resourceId);
+ }
+
+ public IpSecTransformResponse(int inStatus) {
+ if (inStatus == IpSecManager.Status.OK) {
+ throw new IllegalArgumentException("Valid status implies other args must be provided");
+ }
+ status = inStatus;
+ resourceId = IpSecManager.INVALID_RESOURCE_ID;
+ }
+
+ public IpSecTransformResponse(int inStatus, int inResourceId) {
+ status = inStatus;
+ resourceId = inResourceId;
+ }
+
+ private IpSecTransformResponse(Parcel in) {
+ status = in.readInt();
+ resourceId = in.readInt();
+ }
+
+ @android.annotation.NonNull
+ public static final Parcelable.Creator<IpSecTransformResponse> CREATOR =
+ new Parcelable.Creator<IpSecTransformResponse>() {
+ public IpSecTransformResponse createFromParcel(Parcel in) {
+ return new IpSecTransformResponse(in);
+ }
+
+ public IpSecTransformResponse[] newArray(int size) {
+ return new IpSecTransformResponse[size];
+ }
+ };
+}
diff --git a/framework-t/src/android/net/IpSecTunnelInterfaceResponse.aidl b/framework-t/src/android/net/IpSecTunnelInterfaceResponse.aidl
new file mode 100644
index 0000000..7239221
--- /dev/null
+++ b/framework-t/src/android/net/IpSecTunnelInterfaceResponse.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+/** @hide */
+parcelable IpSecTunnelInterfaceResponse;
diff --git a/framework-t/src/android/net/IpSecTunnelInterfaceResponse.java b/framework-t/src/android/net/IpSecTunnelInterfaceResponse.java
new file mode 100644
index 0000000..127e30a
--- /dev/null
+++ b/framework-t/src/android/net/IpSecTunnelInterfaceResponse.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class is used to return an IpSecTunnelInterface resource Id and and corresponding status
+ * from the IpSecService to an IpSecTunnelInterface object.
+ *
+ * @hide
+ */
+public final class IpSecTunnelInterfaceResponse implements Parcelable {
+ private static final String TAG = "IpSecTunnelInterfaceResponse";
+
+ public final int resourceId;
+ public final String interfaceName;
+ public final int status;
+ // Parcelable Methods
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(status);
+ out.writeInt(resourceId);
+ out.writeString(interfaceName);
+ }
+
+ public IpSecTunnelInterfaceResponse(int inStatus) {
+ if (inStatus == IpSecManager.Status.OK) {
+ throw new IllegalArgumentException("Valid status implies other args must be provided");
+ }
+ status = inStatus;
+ resourceId = IpSecManager.INVALID_RESOURCE_ID;
+ interfaceName = "";
+ }
+
+ public IpSecTunnelInterfaceResponse(int inStatus, int inResourceId, String inInterfaceName) {
+ status = inStatus;
+ resourceId = inResourceId;
+ interfaceName = inInterfaceName;
+ }
+
+ private IpSecTunnelInterfaceResponse(Parcel in) {
+ status = in.readInt();
+ resourceId = in.readInt();
+ interfaceName = in.readString();
+ }
+
+ @android.annotation.NonNull
+ public static final Parcelable.Creator<IpSecTunnelInterfaceResponse> CREATOR =
+ new Parcelable.Creator<IpSecTunnelInterfaceResponse>() {
+ public IpSecTunnelInterfaceResponse createFromParcel(Parcel in) {
+ return new IpSecTunnelInterfaceResponse(in);
+ }
+
+ public IpSecTunnelInterfaceResponse[] newArray(int size) {
+ return new IpSecTunnelInterfaceResponse[size];
+ }
+ };
+}
diff --git a/framework-t/src/android/net/IpSecUdpEncapResponse.aidl b/framework-t/src/android/net/IpSecUdpEncapResponse.aidl
new file mode 100644
index 0000000..5e451f3
--- /dev/null
+++ b/framework-t/src/android/net/IpSecUdpEncapResponse.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+/** @hide */
+parcelable IpSecUdpEncapResponse;
diff --git a/framework-t/src/android/net/IpSecUdpEncapResponse.java b/framework-t/src/android/net/IpSecUdpEncapResponse.java
new file mode 100644
index 0000000..732cf19
--- /dev/null
+++ b/framework-t/src/android/net/IpSecUdpEncapResponse.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net;
+
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+/**
+ * This class is used to return a UDP Socket and corresponding status from the IpSecService to an
+ * IpSecManager.UdpEncapsulationSocket.
+ *
+ * @hide
+ */
+public final class IpSecUdpEncapResponse implements Parcelable {
+ private static final String TAG = "IpSecUdpEncapResponse";
+
+ public final int resourceId;
+ public final int port;
+ public final int status;
+ // There is a weird asymmetry with FileDescriptor: you can write a FileDescriptor
+ // but you read a ParcelFileDescriptor. To circumvent this, when we receive a FD
+ // from the user, we immediately create a ParcelFileDescriptor DUP, which we invalidate
+ // on writeParcel() by setting the flag to do close-on-write.
+ // TODO: tests to ensure this doesn't leak
+ public final ParcelFileDescriptor fileDescriptor;
+
+ // Parcelable Methods
+
+ @Override
+ public int describeContents() {
+ return (fileDescriptor != null) ? Parcelable.CONTENTS_FILE_DESCRIPTOR : 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(status);
+ out.writeInt(resourceId);
+ out.writeInt(port);
+ out.writeParcelable(fileDescriptor, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+ }
+
+ public IpSecUdpEncapResponse(int inStatus) {
+ if (inStatus == IpSecManager.Status.OK) {
+ throw new IllegalArgumentException("Valid status implies other args must be provided");
+ }
+ status = inStatus;
+ resourceId = IpSecManager.INVALID_RESOURCE_ID;
+ port = -1;
+ fileDescriptor = null; // yes I know it's redundant, but readability
+ }
+
+ public IpSecUdpEncapResponse(int inStatus, int inResourceId, int inPort, FileDescriptor inFd)
+ throws IOException {
+ if (inStatus == IpSecManager.Status.OK && inFd == null) {
+ throw new IllegalArgumentException("Valid status implies FD must be non-null");
+ }
+ status = inStatus;
+ resourceId = inResourceId;
+ port = inPort;
+ fileDescriptor = (status == IpSecManager.Status.OK) ? ParcelFileDescriptor.dup(inFd) : null;
+ }
+
+ private IpSecUdpEncapResponse(Parcel in) {
+ status = in.readInt();
+ resourceId = in.readInt();
+ port = in.readInt();
+ fileDescriptor = in.readParcelable(ParcelFileDescriptor.class.getClassLoader());
+ }
+
+ @android.annotation.NonNull
+ public static final Parcelable.Creator<IpSecUdpEncapResponse> CREATOR =
+ new Parcelable.Creator<IpSecUdpEncapResponse>() {
+ public IpSecUdpEncapResponse createFromParcel(Parcel in) {
+ return new IpSecUdpEncapResponse(in);
+ }
+
+ public IpSecUdpEncapResponse[] newArray(int size) {
+ return new IpSecUdpEncapResponse[size];
+ }
+ };
+}
diff --git a/framework-t/src/android/net/NetworkIdentity.java b/framework-t/src/android/net/NetworkIdentity.java
new file mode 100644
index 0000000..da5f88d
--- /dev/null
+++ b/framework-t/src/android/net/NetworkIdentity.java
@@ -0,0 +1,594 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.net.NetworkTemplate.NETWORK_TYPE_ALL;
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.app.usage.NetworkStatsManager;
+import android.content.Context;
+import android.net.wifi.WifiInfo;
+import android.service.NetworkIdentityProto;
+import android.telephony.TelephonyManager;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.net.module.util.CollectionUtils;
+import com.android.net.module.util.NetworkCapabilitiesUtils;
+import com.android.net.module.util.NetworkIdentityUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Objects;
+
+/**
+ * Network definition that includes strong identity. Analogous to combining
+ * {@link NetworkCapabilities} and an IMSI.
+ *
+ * @hide
+ */
+@SystemApi(client = MODULE_LIBRARIES)
+public class NetworkIdentity {
+ private static final String TAG = "NetworkIdentity";
+
+ /** @hide */
+ // TODO: Remove this after migrating all callers to use
+ // {@link NetworkTemplate#NETWORK_TYPE_ALL} instead.
+ public static final int SUBTYPE_COMBINED = -1;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "OEM_MANAGED_" }, flag = true, value = {
+ NetworkTemplate.OEM_MANAGED_NO,
+ NetworkTemplate.OEM_MANAGED_PAID,
+ NetworkTemplate.OEM_MANAGED_PRIVATE
+ })
+ public @interface OemManaged{}
+
+ /**
+ * Network has no {@code NetworkCapabilities#NET_CAPABILITY_OEM_*}.
+ * @hide
+ */
+ public static final int OEM_NONE = 0x0;
+ /**
+ * Network has {@link NetworkCapabilities#NET_CAPABILITY_OEM_PAID}.
+ * @hide
+ */
+ public static final int OEM_PAID = 1 << 0;
+ /**
+ * Network has {@link NetworkCapabilities#NET_CAPABILITY_OEM_PRIVATE}.
+ * @hide
+ */
+ public static final int OEM_PRIVATE = 1 << 1;
+
+ private static final long SUPPORTED_OEM_MANAGED_TYPES = OEM_PAID | OEM_PRIVATE;
+
+ final int mType;
+ final int mRatType;
+ final int mSubId;
+ final String mSubscriberId;
+ final String mWifiNetworkKey;
+ final boolean mRoaming;
+ final boolean mMetered;
+ final boolean mDefaultNetwork;
+ final int mOemManaged;
+
+ /** @hide */
+ public NetworkIdentity(
+ int type, int ratType, @Nullable String subscriberId, @Nullable String wifiNetworkKey,
+ boolean roaming, boolean metered, boolean defaultNetwork, int oemManaged, int subId) {
+ mType = type;
+ mRatType = ratType;
+ mSubscriberId = subscriberId;
+ mWifiNetworkKey = wifiNetworkKey;
+ mRoaming = roaming;
+ mMetered = metered;
+ mDefaultNetwork = defaultNetwork;
+ mOemManaged = oemManaged;
+ mSubId = subId;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mType, mRatType, mSubscriberId, mWifiNetworkKey, mRoaming, mMetered,
+ mDefaultNetwork, mOemManaged, mSubId);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (obj instanceof NetworkIdentity) {
+ final NetworkIdentity ident = (NetworkIdentity) obj;
+ return mType == ident.mType && mRatType == ident.mRatType && mRoaming == ident.mRoaming
+ && Objects.equals(mSubscriberId, ident.mSubscriberId)
+ && Objects.equals(mWifiNetworkKey, ident.mWifiNetworkKey)
+ && mMetered == ident.mMetered
+ && mDefaultNetwork == ident.mDefaultNetwork
+ && mOemManaged == ident.mOemManaged
+ && mSubId == ident.mSubId;
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder("{");
+ builder.append("type=").append(mType);
+ builder.append(", ratType=");
+ if (mRatType == NETWORK_TYPE_ALL) {
+ builder.append("COMBINED");
+ } else {
+ builder.append(mRatType);
+ }
+ if (mSubscriberId != null) {
+ builder.append(", subscriberId=")
+ .append(NetworkIdentityUtils.scrubSubscriberId(mSubscriberId));
+ }
+ if (mWifiNetworkKey != null) {
+ builder.append(", wifiNetworkKey=").append(mWifiNetworkKey);
+ }
+ if (mRoaming) {
+ builder.append(", ROAMING");
+ }
+ builder.append(", metered=").append(mMetered);
+ builder.append(", defaultNetwork=").append(mDefaultNetwork);
+ builder.append(", oemManaged=").append(getOemManagedNames(mOemManaged));
+ builder.append(", subId=").append(mSubId);
+ return builder.append("}").toString();
+ }
+
+ /**
+ * Get the human readable representation of a bitfield representing the OEM managed state of a
+ * network.
+ */
+ static String getOemManagedNames(int oemManaged) {
+ if (oemManaged == OEM_NONE) {
+ return "OEM_NONE";
+ }
+ final int[] bitPositions = NetworkCapabilitiesUtils.unpackBits(oemManaged);
+ final ArrayList<String> oemManagedNames = new ArrayList<String>();
+ for (int position : bitPositions) {
+ oemManagedNames.add(nameOfOemManaged(1 << position));
+ }
+ return String.join(",", oemManagedNames);
+ }
+
+ private static String nameOfOemManaged(int oemManagedBit) {
+ switch (oemManagedBit) {
+ case OEM_PAID:
+ return "OEM_PAID";
+ case OEM_PRIVATE:
+ return "OEM_PRIVATE";
+ default:
+ return "Invalid(" + oemManagedBit + ")";
+ }
+ }
+
+ /** @hide */
+ public void dumpDebug(ProtoOutputStream proto, long tag) {
+ final long start = proto.start(tag);
+
+ proto.write(NetworkIdentityProto.TYPE, mType);
+
+ // TODO: dump mRatType as well.
+
+ proto.write(NetworkIdentityProto.ROAMING, mRoaming);
+ proto.write(NetworkIdentityProto.METERED, mMetered);
+ proto.write(NetworkIdentityProto.DEFAULT_NETWORK, mDefaultNetwork);
+ proto.write(NetworkIdentityProto.OEM_MANAGED_NETWORK, mOemManaged);
+
+ proto.end(start);
+ }
+
+ /** Get the network type of this instance. */
+ public int getType() {
+ return mType;
+ }
+
+ /** Get the Radio Access Technology(RAT) type of this instance. */
+ public int getRatType() {
+ return mRatType;
+ }
+
+ /** Get the Subscriber Id of this instance. */
+ @Nullable
+ public String getSubscriberId() {
+ return mSubscriberId;
+ }
+
+ /** Get the Wifi Network Key of this instance. See {@link WifiInfo#getNetworkKey()}. */
+ @Nullable
+ public String getWifiNetworkKey() {
+ return mWifiNetworkKey;
+ }
+
+ /** @hide */
+ // TODO: Remove this function after all callers are removed.
+ public boolean getRoaming() {
+ return mRoaming;
+ }
+
+ /** Return whether this network is roaming. */
+ public boolean isRoaming() {
+ return mRoaming;
+ }
+
+ /** @hide */
+ // TODO: Remove this function after all callers are removed.
+ public boolean getMetered() {
+ return mMetered;
+ }
+
+ /** Return whether this network is metered. */
+ public boolean isMetered() {
+ return mMetered;
+ }
+
+ /** @hide */
+ // TODO: Remove this function after all callers are removed.
+ public boolean getDefaultNetwork() {
+ return mDefaultNetwork;
+ }
+
+ /** Return whether this network is the default network. */
+ public boolean isDefaultNetwork() {
+ return mDefaultNetwork;
+ }
+
+ /** Get the OEM managed type of this instance. */
+ public int getOemManaged() {
+ return mOemManaged;
+ }
+
+ /** Get the SubId of this instance. */
+ public int getSubId() {
+ return mSubId;
+ }
+
+ /**
+ * Assemble a {@link NetworkIdentity} from the passed arguments.
+ *
+ * This methods builds an identity based on the capabilities of the network in the
+ * snapshot and other passed arguments. The identity is used as a key to record data usage.
+ *
+ * @param snapshot the snapshot of network state. See {@link NetworkStateSnapshot}.
+ * @param defaultNetwork whether the network is a default network.
+ * @param ratType the Radio Access Technology(RAT) type of the network. Or
+ * {@link TelephonyManager#NETWORK_TYPE_UNKNOWN} if not applicable.
+ * See {@code TelephonyManager.NETWORK_TYPE_*}.
+ * @hide
+ * @deprecated See {@link NetworkIdentity.Builder}.
+ */
+ // TODO: Remove this after all callers are migrated to use new Api.
+ @Deprecated
+ @NonNull
+ public static NetworkIdentity buildNetworkIdentity(Context context,
+ @NonNull NetworkStateSnapshot snapshot, boolean defaultNetwork, int ratType) {
+ final NetworkIdentity.Builder builder = new NetworkIdentity.Builder()
+ .setNetworkStateSnapshot(snapshot).setDefaultNetwork(defaultNetwork)
+ .setSubId(snapshot.getSubId());
+ if (snapshot.getLegacyType() == TYPE_MOBILE && ratType != NETWORK_TYPE_ALL) {
+ builder.setRatType(ratType);
+ }
+ return builder.build();
+ }
+
+ /**
+ * Builds a bitfield of {@code NetworkIdentity.OEM_*} based on {@link NetworkCapabilities}.
+ * @hide
+ */
+ public static int getOemBitfield(@NonNull NetworkCapabilities nc) {
+ int oemManaged = OEM_NONE;
+
+ if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PAID)) {
+ oemManaged |= OEM_PAID;
+ }
+ if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE)) {
+ oemManaged |= OEM_PRIVATE;
+ }
+
+ return oemManaged;
+ }
+
+ /** @hide */
+ public static int compare(@NonNull NetworkIdentity left, @NonNull NetworkIdentity right) {
+ Objects.requireNonNull(right);
+ int res = Integer.compare(left.mType, right.mType);
+ if (res == 0) {
+ res = Integer.compare(left.mRatType, right.mRatType);
+ }
+ if (res == 0 && left.mSubscriberId != null && right.mSubscriberId != null) {
+ res = left.mSubscriberId.compareTo(right.mSubscriberId);
+ }
+ if (res == 0 && left.mWifiNetworkKey != null && right.mWifiNetworkKey != null) {
+ res = left.mWifiNetworkKey.compareTo(right.mWifiNetworkKey);
+ }
+ if (res == 0) {
+ res = Boolean.compare(left.mRoaming, right.mRoaming);
+ }
+ if (res == 0) {
+ res = Boolean.compare(left.mMetered, right.mMetered);
+ }
+ if (res == 0) {
+ res = Boolean.compare(left.mDefaultNetwork, right.mDefaultNetwork);
+ }
+ if (res == 0) {
+ res = Integer.compare(left.mOemManaged, right.mOemManaged);
+ }
+ if (res == 0) {
+ res = Integer.compare(left.mSubId, right.mSubId);
+ }
+ return res;
+ }
+
+ /**
+ * Builder class for {@link NetworkIdentity}.
+ */
+ public static final class Builder {
+ // Need to be synchronized with ConnectivityManager.
+ // TODO: Use {@link ConnectivityManager#MAX_NETWORK_TYPE} when this file is in the module.
+ private static final int MAX_NETWORK_TYPE = 18; // TYPE_TEST
+ private static final int MIN_NETWORK_TYPE = TYPE_MOBILE;
+
+ private int mType;
+ private int mRatType;
+ private String mSubscriberId;
+ private String mWifiNetworkKey;
+ private boolean mRoaming;
+ private boolean mMetered;
+ private boolean mDefaultNetwork;
+ private int mOemManaged;
+ private int mSubId;
+
+ /**
+ * Creates a new Builder.
+ */
+ public Builder() {
+ // Initialize with default values. Will be overwritten by setters.
+ mType = ConnectivityManager.TYPE_NONE;
+ mRatType = NetworkTemplate.NETWORK_TYPE_ALL;
+ mSubscriberId = null;
+ mWifiNetworkKey = null;
+ mRoaming = false;
+ mMetered = false;
+ mDefaultNetwork = false;
+ mOemManaged = NetworkTemplate.OEM_MANAGED_NO;
+ mSubId = INVALID_SUBSCRIPTION_ID;
+ }
+
+ /**
+ * Add an {@link NetworkStateSnapshot} into the {@link NetworkIdentity} instance.
+ * This is a useful shorthand that will read from the snapshot and set the
+ * following fields, if they are set in the snapshot :
+ * - type
+ * - subscriberId
+ * - roaming
+ * - metered
+ * - oemManaged
+ * - wifiNetworkKey
+ *
+ * @param snapshot The target {@link NetworkStateSnapshot} object.
+ * @return The builder object.
+ */
+ @SuppressLint("MissingGetterMatchingBuilder")
+ @NonNull
+ public Builder setNetworkStateSnapshot(@NonNull NetworkStateSnapshot snapshot) {
+ setType(snapshot.getLegacyType());
+
+ setSubscriberId(snapshot.getSubscriberId());
+ setRoaming(!snapshot.getNetworkCapabilities().hasCapability(
+ NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING));
+ setMetered(!(snapshot.getNetworkCapabilities().hasCapability(
+ NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
+ || snapshot.getNetworkCapabilities().hasCapability(
+ NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED)));
+
+ setOemManaged(getOemBitfield(snapshot.getNetworkCapabilities()));
+
+ if (mType == TYPE_WIFI) {
+ final TransportInfo transportInfo = snapshot.getNetworkCapabilities()
+ .getTransportInfo();
+ if (transportInfo instanceof WifiInfo) {
+ final WifiInfo info = (WifiInfo) transportInfo;
+ setWifiNetworkKey(info.getNetworkKey());
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Set the network type of the network.
+ *
+ * @param type the network type. See {@link ConnectivityManager#TYPE_*}.
+ *
+ * @return this builder.
+ */
+ @NonNull
+ public Builder setType(int type) {
+ // Include TYPE_NONE for compatibility, type field might not be filled by some
+ // networks such as test networks.
+ if ((type < MIN_NETWORK_TYPE || MAX_NETWORK_TYPE < type)
+ && type != ConnectivityManager.TYPE_NONE) {
+ throw new IllegalArgumentException("Invalid network type: " + type);
+ }
+ mType = type;
+ return this;
+ }
+
+ /**
+ * Set the Radio Access Technology(RAT) type of the network.
+ *
+ * No RAT type is specified by default. Call clearRatType to reset.
+ *
+ * @param ratType the Radio Access Technology(RAT) type if applicable. See
+ * {@code TelephonyManager.NETWORK_TYPE_*}.
+ *
+ * @return this builder.
+ */
+ @NonNull
+ public Builder setRatType(int ratType) {
+ if (!CollectionUtils.contains(TelephonyManager.getAllNetworkTypes(), ratType)
+ && ratType != TelephonyManager.NETWORK_TYPE_UNKNOWN
+ && ratType != NetworkStatsManager.NETWORK_TYPE_5G_NSA) {
+ throw new IllegalArgumentException("Invalid ratType " + ratType);
+ }
+ mRatType = ratType;
+ return this;
+ }
+
+ /**
+ * Clear the Radio Access Technology(RAT) type of the network.
+ *
+ * @return this builder.
+ */
+ @NonNull
+ public Builder clearRatType() {
+ mRatType = NetworkTemplate.NETWORK_TYPE_ALL;
+ return this;
+ }
+
+ /**
+ * Set the Subscriber Id.
+ *
+ * @param subscriberId the Subscriber Id of the network. Or null if not applicable.
+ * @return this builder.
+ */
+ @NonNull
+ public Builder setSubscriberId(@Nullable String subscriberId) {
+ mSubscriberId = subscriberId;
+ return this;
+ }
+
+ /**
+ * Set the Wifi Network Key.
+ *
+ * @param wifiNetworkKey Wifi Network Key of the network,
+ * see {@link WifiInfo#getNetworkKey()}.
+ * Or null if not applicable.
+ * @return this builder.
+ */
+ @NonNull
+ public Builder setWifiNetworkKey(@Nullable String wifiNetworkKey) {
+ mWifiNetworkKey = wifiNetworkKey;
+ return this;
+ }
+
+ /**
+ * Set whether this network is roaming.
+ *
+ * This field is false by default. Call with false to reset.
+ *
+ * @param roaming the roaming status of the network.
+ * @return this builder.
+ */
+ @NonNull
+ public Builder setRoaming(boolean roaming) {
+ mRoaming = roaming;
+ return this;
+ }
+
+ /**
+ * Set whether this network is metered.
+ *
+ * This field is false by default. Call with false to reset.
+ *
+ * @param metered the meteredness of the network.
+ * @return this builder.
+ */
+ @NonNull
+ public Builder setMetered(boolean metered) {
+ mMetered = metered;
+ return this;
+ }
+
+ /**
+ * Set whether this network is the default network.
+ *
+ * This field is false by default. Call with false to reset.
+ *
+ * @param defaultNetwork the default network status of the network.
+ * @return this builder.
+ */
+ @NonNull
+ public Builder setDefaultNetwork(boolean defaultNetwork) {
+ mDefaultNetwork = defaultNetwork;
+ return this;
+ }
+
+ /**
+ * Set the OEM managed type.
+ *
+ * @param oemManaged Type of OEM managed network or unmanaged networks.
+ * See {@code NetworkTemplate#OEM_MANAGED_*}.
+ * @return this builder.
+ */
+ @NonNull
+ public Builder setOemManaged(@OemManaged int oemManaged) {
+ // Assert input does not contain illegal oemManage bits.
+ if ((~SUPPORTED_OEM_MANAGED_TYPES & oemManaged) != 0) {
+ throw new IllegalArgumentException("Invalid value for OemManaged : " + oemManaged);
+ }
+ mOemManaged = oemManaged;
+ return this;
+ }
+
+ /**
+ * Set the Subscription Id.
+ *
+ * @param subId the Subscription Id of the network. Or INVALID_SUBSCRIPTION_ID if not
+ * applicable.
+ * @return this builder.
+ */
+ @NonNull
+ public Builder setSubId(int subId) {
+ mSubId = subId;
+ return this;
+ }
+
+ private void ensureValidParameters() {
+ // Assert non-mobile network cannot have a ratType.
+ if (mType != TYPE_MOBILE && mRatType != NetworkTemplate.NETWORK_TYPE_ALL) {
+ throw new IllegalArgumentException(
+ "Invalid ratType " + mRatType + " for type " + mType);
+ }
+
+ // Assert non-wifi network cannot have a wifi network key.
+ if (mType != TYPE_WIFI && mWifiNetworkKey != null) {
+ throw new IllegalArgumentException("Invalid wifi network key for type " + mType);
+ }
+ }
+
+ /**
+ * Builds the instance of the {@link NetworkIdentity}.
+ *
+ * @return the built instance of {@link NetworkIdentity}.
+ */
+ @NonNull
+ public NetworkIdentity build() {
+ ensureValidParameters();
+ return new NetworkIdentity(mType, mRatType, mSubscriberId, mWifiNetworkKey,
+ mRoaming, mMetered, mDefaultNetwork, mOemManaged, mSubId);
+ }
+ }
+}
diff --git a/framework-t/src/android/net/NetworkIdentitySet.java b/framework-t/src/android/net/NetworkIdentitySet.java
new file mode 100644
index 0000000..d88408e
--- /dev/null
+++ b/framework-t/src/android/net/NetworkIdentitySet.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
+import android.annotation.NonNull;
+import android.service.NetworkIdentitySetProto;
+import android.util.proto.ProtoOutputStream;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Identity of a {@code iface}, defined by the set of {@link NetworkIdentity}
+ * active on that interface.
+ *
+ * @hide
+ */
+public class NetworkIdentitySet extends HashSet<NetworkIdentity> {
+ private static final int VERSION_INIT = 1;
+ private static final int VERSION_ADD_ROAMING = 2;
+ private static final int VERSION_ADD_NETWORK_ID = 3;
+ private static final int VERSION_ADD_METERED = 4;
+ private static final int VERSION_ADD_DEFAULT_NETWORK = 5;
+ private static final int VERSION_ADD_OEM_MANAGED_NETWORK = 6;
+ private static final int VERSION_ADD_SUB_ID = 7;
+
+ /**
+ * Construct a {@link NetworkIdentitySet} object.
+ */
+ public NetworkIdentitySet() {
+ super();
+ }
+
+ /** @hide */
+ public NetworkIdentitySet(@NonNull Set<NetworkIdentity> ident) {
+ super(ident);
+ }
+
+ /** @hide */
+ public NetworkIdentitySet(DataInput in) throws IOException {
+ final int version = in.readInt();
+ final int size = in.readInt();
+ for (int i = 0; i < size; i++) {
+ if (version <= VERSION_INIT) {
+ final int ignored = in.readInt();
+ }
+ final int type = in.readInt();
+ final int ratType = in.readInt();
+ final String subscriberId = readOptionalString(in);
+ final String networkId;
+ if (version >= VERSION_ADD_NETWORK_ID) {
+ networkId = readOptionalString(in);
+ } else {
+ networkId = null;
+ }
+ final boolean roaming;
+ if (version >= VERSION_ADD_ROAMING) {
+ roaming = in.readBoolean();
+ } else {
+ roaming = false;
+ }
+
+ final boolean metered;
+ if (version >= VERSION_ADD_METERED) {
+ metered = in.readBoolean();
+ } else {
+ // If this is the old data and the type is mobile, treat it as metered. (Note that
+ // if this is a mobile network, TYPE_MOBILE is the only possible type that could be
+ // used.)
+ metered = (type == TYPE_MOBILE);
+ }
+
+ final boolean defaultNetwork;
+ if (version >= VERSION_ADD_DEFAULT_NETWORK) {
+ defaultNetwork = in.readBoolean();
+ } else {
+ defaultNetwork = true;
+ }
+
+ final int oemNetCapabilities;
+ if (version >= VERSION_ADD_OEM_MANAGED_NETWORK) {
+ oemNetCapabilities = in.readInt();
+ } else {
+ oemNetCapabilities = NetworkIdentity.OEM_NONE;
+ }
+
+ final int subId;
+ if (version >= VERSION_ADD_SUB_ID) {
+ subId = in.readInt();
+ } else {
+ subId = INVALID_SUBSCRIPTION_ID;
+ }
+
+ add(new NetworkIdentity(type, ratType, subscriberId, networkId, roaming, metered,
+ defaultNetwork, oemNetCapabilities, subId));
+ }
+ }
+
+ /**
+ * Method to serialize this object into a {@code DataOutput}.
+ * @hide
+ */
+ public void writeToStream(DataOutput out) throws IOException {
+ out.writeInt(VERSION_ADD_SUB_ID);
+ out.writeInt(size());
+ for (NetworkIdentity ident : this) {
+ out.writeInt(ident.getType());
+ out.writeInt(ident.getRatType());
+ writeOptionalString(out, ident.getSubscriberId());
+ writeOptionalString(out, ident.getWifiNetworkKey());
+ out.writeBoolean(ident.isRoaming());
+ out.writeBoolean(ident.isMetered());
+ out.writeBoolean(ident.isDefaultNetwork());
+ out.writeInt(ident.getOemManaged());
+ out.writeInt(ident.getSubId());
+ }
+ }
+
+ /**
+ * @return whether any {@link NetworkIdentity} in this set is considered metered.
+ * @hide
+ */
+ public boolean isAnyMemberMetered() {
+ if (isEmpty()) {
+ return false;
+ }
+ for (NetworkIdentity ident : this) {
+ if (ident.isMetered()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @return whether any {@link NetworkIdentity} in this set is considered roaming.
+ * @hide
+ */
+ public boolean isAnyMemberRoaming() {
+ if (isEmpty()) {
+ return false;
+ }
+ for (NetworkIdentity ident : this) {
+ if (ident.isRoaming()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @return whether any {@link NetworkIdentity} in this set is considered on the default
+ * network.
+ * @hide
+ */
+ public boolean areAllMembersOnDefaultNetwork() {
+ if (isEmpty()) {
+ return true;
+ }
+ for (NetworkIdentity ident : this) {
+ if (!ident.isDefaultNetwork()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static void writeOptionalString(DataOutput out, String value) throws IOException {
+ if (value != null) {
+ out.writeByte(1);
+ out.writeUTF(value);
+ } else {
+ out.writeByte(0);
+ }
+ }
+
+ private static String readOptionalString(DataInput in) throws IOException {
+ if (in.readByte() != 0) {
+ return in.readUTF();
+ } else {
+ return null;
+ }
+ }
+
+ public static int compare(@NonNull NetworkIdentitySet left, @NonNull NetworkIdentitySet right) {
+ Objects.requireNonNull(left);
+ Objects.requireNonNull(right);
+ if (left.isEmpty() && right.isEmpty()) return 0;
+ if (left.isEmpty()) return -1;
+ if (right.isEmpty()) return 1;
+
+ final NetworkIdentity leftIdent = left.iterator().next();
+ final NetworkIdentity rightIdent = right.iterator().next();
+ return NetworkIdentity.compare(leftIdent, rightIdent);
+ }
+
+ /**
+ * Method to dump this object into proto debug file.
+ * @hide
+ */
+ public void dumpDebug(ProtoOutputStream proto, long tag) {
+ final long start = proto.start(tag);
+
+ for (NetworkIdentity ident : this) {
+ ident.dumpDebug(proto, NetworkIdentitySetProto.IDENTITIES);
+ }
+
+ proto.end(start);
+ }
+}
diff --git a/framework-t/src/android/net/NetworkStateSnapshot.java b/framework-t/src/android/net/NetworkStateSnapshot.java
new file mode 100644
index 0000000..d3f785a
--- /dev/null
+++ b/framework-t/src/android/net/NetworkStateSnapshot.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.net.module.util.NetworkIdentityUtils;
+
+import java.util.Objects;
+
+/**
+ * Snapshot of network state.
+ *
+ * @hide
+ */
+@SystemApi(client = MODULE_LIBRARIES)
+public final class NetworkStateSnapshot implements Parcelable {
+ /** The network associated with this snapshot. */
+ @NonNull
+ private final Network mNetwork;
+
+ /** The {@link NetworkCapabilities} of the network associated with this snapshot. */
+ @NonNull
+ private final NetworkCapabilities mNetworkCapabilities;
+
+ /** The {@link LinkProperties} of the network associated with this snapshot. */
+ @NonNull
+ private final LinkProperties mLinkProperties;
+
+ /**
+ * The Subscriber Id of the network associated with this snapshot. See
+ * {@link android.telephony.TelephonyManager#getSubscriberId()}.
+ */
+ @Nullable
+ private final String mSubscriberId;
+
+ /**
+ * The legacy type of the network associated with this snapshot. See
+ * {@code ConnectivityManager#TYPE_*}.
+ */
+ private final int mLegacyType;
+
+ public NetworkStateSnapshot(@NonNull Network network,
+ @NonNull NetworkCapabilities networkCapabilities,
+ @NonNull LinkProperties linkProperties,
+ @Nullable String subscriberId, int legacyType) {
+ mNetwork = Objects.requireNonNull(network);
+ mNetworkCapabilities = Objects.requireNonNull(networkCapabilities);
+ mLinkProperties = Objects.requireNonNull(linkProperties);
+ mSubscriberId = subscriberId;
+ mLegacyType = legacyType;
+ }
+
+ /** @hide */
+ public NetworkStateSnapshot(@NonNull Parcel in) {
+ mNetwork = in.readParcelable(null);
+ mNetworkCapabilities = in.readParcelable(null);
+ mLinkProperties = in.readParcelable(null);
+ mSubscriberId = in.readString();
+ mLegacyType = in.readInt();
+ }
+
+ /** Get the network associated with this snapshot */
+ @NonNull
+ public Network getNetwork() {
+ return mNetwork;
+ }
+
+ /** Get {@link NetworkCapabilities} of the network associated with this snapshot. */
+ @NonNull
+ public NetworkCapabilities getNetworkCapabilities() {
+ return mNetworkCapabilities;
+ }
+
+ /** Get the {@link LinkProperties} of the network associated with this snapshot. */
+ @NonNull
+ public LinkProperties getLinkProperties() {
+ return mLinkProperties;
+ }
+
+ /**
+ * Get the Subscriber Id of the network associated with this snapshot.
+ * @deprecated Please use #getSubId, which doesn't return personally identifiable
+ * information.
+ */
+ @Deprecated
+ @Nullable
+ public String getSubscriberId() {
+ return mSubscriberId;
+ }
+
+ /** Get the subId of the network associated with this snapshot. */
+ public int getSubId() {
+ if (mNetworkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
+ final NetworkSpecifier spec = mNetworkCapabilities.getNetworkSpecifier();
+ if (spec instanceof TelephonyNetworkSpecifier) {
+ return ((TelephonyNetworkSpecifier) spec).getSubscriptionId();
+ }
+ }
+ return INVALID_SUBSCRIPTION_ID;
+ }
+
+
+ /**
+ * Get the legacy type of the network associated with this snapshot.
+ * @return the legacy network type. See {@code ConnectivityManager#TYPE_*}.
+ */
+ public int getLegacyType() {
+ return mLegacyType;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeParcelable(mNetwork, flags);
+ out.writeParcelable(mNetworkCapabilities, flags);
+ out.writeParcelable(mLinkProperties, flags);
+ out.writeString(mSubscriberId);
+ out.writeInt(mLegacyType);
+ }
+
+ @NonNull
+ public static final Creator<NetworkStateSnapshot> CREATOR =
+ new Creator<NetworkStateSnapshot>() {
+ @NonNull
+ @Override
+ public NetworkStateSnapshot createFromParcel(@NonNull Parcel in) {
+ return new NetworkStateSnapshot(in);
+ }
+
+ @NonNull
+ @Override
+ public NetworkStateSnapshot[] newArray(int size) {
+ return new NetworkStateSnapshot[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof NetworkStateSnapshot)) return false;
+ NetworkStateSnapshot that = (NetworkStateSnapshot) o;
+ return mLegacyType == that.mLegacyType
+ && Objects.equals(mNetwork, that.mNetwork)
+ && Objects.equals(mNetworkCapabilities, that.mNetworkCapabilities)
+ && Objects.equals(mLinkProperties, that.mLinkProperties)
+ && Objects.equals(mSubscriberId, that.mSubscriberId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mNetwork,
+ mNetworkCapabilities, mLinkProperties, mSubscriberId, mLegacyType);
+ }
+
+ @Override
+ public String toString() {
+ return "NetworkStateSnapshot{"
+ + "network=" + mNetwork
+ + ", networkCapabilities=" + mNetworkCapabilities
+ + ", linkProperties=" + mLinkProperties
+ + ", subscriberId='" + NetworkIdentityUtils.scrubSubscriberId(mSubscriberId) + '\''
+ + ", legacyType=" + mLegacyType
+ + '}';
+ }
+}
diff --git a/framework-t/src/android/net/NetworkStats.java b/framework-t/src/android/net/NetworkStats.java
new file mode 100644
index 0000000..51ff5ec
--- /dev/null
+++ b/framework-t/src/android/net/NetworkStats.java
@@ -0,0 +1,1836 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static com.android.net.module.util.NetworkStatsUtils.multiplySafeByRational;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.Process;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.SparseBooleanArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.CollectionUtils;
+
+import libcore.util.EmptyArray;
+
+import java.io.CharArrayWriter;
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Predicate;
+
+/**
+ * Collection of active network statistics. Can contain summary details across
+ * all interfaces, or details with per-UID granularity. Internally stores data
+ * as a large table, closely matching {@code /proc/} data format. This structure
+ * optimizes for rapid in-memory comparison, but consider using
+ * {@link NetworkStatsHistory} when persisting.
+ *
+ * @hide
+ */
+// @NotThreadSafe
+@SystemApi
+public final class NetworkStats implements Parcelable, Iterable<NetworkStats.Entry> {
+ private static final String TAG = "NetworkStats";
+
+ /**
+ * {@link #iface} value when interface details unavailable.
+ * @hide
+ */
+ @Nullable public static final String IFACE_ALL = null;
+
+ /**
+ * Virtual network interface for video telephony. This is for VT data usage counting
+ * purpose.
+ */
+ public static final String IFACE_VT = "vt_data0";
+
+ /** {@link #uid} value when UID details unavailable. */
+ public static final int UID_ALL = -1;
+ /** Special UID value for data usage by tethering. */
+ public static final int UID_TETHERING = -5;
+
+ /**
+ * {@link #tag} value matching any tag.
+ * @hide
+ */
+ // TODO: Rename TAG_ALL to TAG_ANY.
+ public static final int TAG_ALL = -1;
+ /** {@link #set} value for all sets combined, not including debug sets. */
+ public static final int SET_ALL = -1;
+ /** {@link #set} value where background data is accounted. */
+ public static final int SET_DEFAULT = 0;
+ /** {@link #set} value where foreground data is accounted. */
+ public static final int SET_FOREGROUND = 1;
+ /**
+ * All {@link #set} value greater than SET_DEBUG_START are debug {@link #set} values.
+ * @hide
+ */
+ public static final int SET_DEBUG_START = 1000;
+ /**
+ * Debug {@link #set} value when the VPN stats are moved in.
+ * @hide
+ */
+ public static final int SET_DBG_VPN_IN = 1001;
+ /**
+ * Debug {@link #set} value when the VPN stats are moved out of a vpn UID.
+ * @hide
+ */
+ public static final int SET_DBG_VPN_OUT = 1002;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "SET_" }, value = {
+ SET_ALL,
+ SET_DEFAULT,
+ SET_FOREGROUND,
+ })
+ public @interface State {
+ }
+
+ /**
+ * Include all interfaces when filtering
+ * @hide
+ */
+ public @Nullable static final String[] INTERFACES_ALL = null;
+
+ /** {@link #tag} value for total data across all tags. */
+ public static final int TAG_NONE = 0;
+
+ /** {@link #metered} value to account for all metered states. */
+ public static final int METERED_ALL = -1;
+ /** {@link #metered} value where native, unmetered data is accounted. */
+ public static final int METERED_NO = 0;
+ /** {@link #metered} value where metered data is accounted. */
+ public static final int METERED_YES = 1;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "METERED_" }, value = {
+ METERED_ALL,
+ METERED_NO,
+ METERED_YES
+ })
+ public @interface Meteredness {
+ }
+
+
+ /** {@link #roaming} value to account for all roaming states. */
+ public static final int ROAMING_ALL = -1;
+ /** {@link #roaming} value where native, non-roaming data is accounted. */
+ public static final int ROAMING_NO = 0;
+ /** {@link #roaming} value where roaming data is accounted. */
+ public static final int ROAMING_YES = 1;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "ROAMING_" }, value = {
+ ROAMING_ALL,
+ ROAMING_NO,
+ ROAMING_YES
+ })
+ public @interface Roaming {
+ }
+
+ /** {@link #onDefaultNetwork} value to account for all default network states. */
+ public static final int DEFAULT_NETWORK_ALL = -1;
+ /** {@link #onDefaultNetwork} value to account for usage while not the default network. */
+ public static final int DEFAULT_NETWORK_NO = 0;
+ /** {@link #onDefaultNetwork} value to account for usage while the default network. */
+ public static final int DEFAULT_NETWORK_YES = 1;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "DEFAULT_NETWORK_" }, value = {
+ DEFAULT_NETWORK_ALL,
+ DEFAULT_NETWORK_NO,
+ DEFAULT_NETWORK_YES
+ })
+ public @interface DefaultNetwork {
+ }
+
+ /**
+ * Denotes a request for stats at the interface level.
+ * @hide
+ */
+ public static final int STATS_PER_IFACE = 0;
+ /**
+ * Denotes a request for stats at the interface and UID level.
+ * @hide
+ */
+ public static final int STATS_PER_UID = 1;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "STATS_PER_" }, value = {
+ STATS_PER_IFACE,
+ STATS_PER_UID
+ })
+ public @interface StatsType {
+ }
+
+ private static final String CLATD_INTERFACE_PREFIX = "v4-";
+ // Delta between IPv4 header (20b) and IPv6 header (40b).
+ // Used for correct stats accounting on clatd interfaces.
+ private static final int IPV4V6_HEADER_DELTA = 20;
+
+ // TODO: move fields to "mVariable" notation
+
+ /**
+ * {@link SystemClock#elapsedRealtime()} timestamp in milliseconds when this data was
+ * generated.
+ * It's a timestamps delta when {@link #subtract()},
+ * {@code INetworkStatsSession#getSummaryForAllUid()} methods are used.
+ */
+ private long elapsedRealtime;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ private int size;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ private int capacity;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ private String[] iface;
+ @UnsupportedAppUsage
+ private int[] uid;
+ @UnsupportedAppUsage
+ private int[] set;
+ @UnsupportedAppUsage
+ private int[] tag;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ private int[] metered;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ private int[] roaming;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ private int[] defaultNetwork;
+ @UnsupportedAppUsage
+ private long[] rxBytes;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ private long[] rxPackets;
+ @UnsupportedAppUsage
+ private long[] txBytes;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ private long[] txPackets;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ private long[] operations;
+
+ /**
+ * Basic element of network statistics. Contains the number of packets and number of bytes
+ * transferred on both directions in a given set of conditions. See
+ * {@link Entry#Entry(String, int, int, int, int, int, int, long, long, long, long, long)}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static class Entry {
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public String iface;
+ /** @hide */
+ @UnsupportedAppUsage
+ public int uid;
+ /** @hide */
+ @UnsupportedAppUsage
+ public int set;
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public int tag;
+ /**
+ * Note that this is only populated w/ the default value when read from /proc or written
+ * to disk. We merge in the correct value when reporting this value to clients of
+ * getSummary().
+ * @hide
+ */
+ public int metered;
+ /**
+ * Note that this is only populated w/ the default value when read from /proc or written
+ * to disk. We merge in the correct value when reporting this value to clients of
+ * getSummary().
+ * @hide
+ */
+ public int roaming;
+ /**
+ * Note that this is only populated w/ the default value when read from /proc or written
+ * to disk. We merge in the correct value when reporting this value to clients of
+ * getSummary().
+ * @hide
+ */
+ public int defaultNetwork;
+ /** @hide */
+ @UnsupportedAppUsage
+ public long rxBytes;
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public long rxPackets;
+ /** @hide */
+ @UnsupportedAppUsage
+ public long txBytes;
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public long txPackets;
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public long operations;
+
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public Entry() {
+ this(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
+ }
+
+ /** @hide */
+ public Entry(long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) {
+ this(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets,
+ operations);
+ }
+
+ /** @hide */
+ public Entry(String iface, int uid, int set, int tag, long rxBytes, long rxPackets,
+ long txBytes, long txPackets, long operations) {
+ this(iface, uid, set, tag, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO,
+ rxBytes, rxPackets, txBytes, txPackets, operations);
+ }
+
+ /**
+ * Construct a {@link Entry} object by giving statistics of packet and byte transferred on
+ * both direction, and associated with a set of given conditions.
+ *
+ * @param iface interface name of this {@link Entry}. Or null if not specified.
+ * @param uid uid of this {@link Entry}. {@link #UID_TETHERING} if this {@link Entry} is
+ * for tethering. Or {@link #UID_ALL} if this {@link NetworkStats} is only
+ * counting iface stats.
+ * @param set usage state of this {@link Entry}.
+ * @param tag tag of this {@link Entry}.
+ * @param metered metered state of this {@link Entry}.
+ * @param roaming roaming state of this {@link Entry}.
+ * @param defaultNetwork default network status of this {@link Entry}.
+ * @param rxBytes Number of bytes received for this {@link Entry}. Statistics should
+ * represent the contents of IP packets, including IP headers.
+ * @param rxPackets Number of packets received for this {@link Entry}. Statistics should
+ * represent the contents of IP packets, including IP headers.
+ * @param txBytes Number of bytes transmitted for this {@link Entry}. Statistics should
+ * represent the contents of IP packets, including IP headers.
+ * @param txPackets Number of bytes transmitted for this {@link Entry}. Statistics should
+ * represent the contents of IP packets, including IP headers.
+ * @param operations count of network operations performed for this {@link Entry}. This can
+ * be used to derive bytes-per-operation.
+ */
+ public Entry(@Nullable String iface, int uid, @State int set, int tag,
+ @Meteredness int metered, @Roaming int roaming, @DefaultNetwork int defaultNetwork,
+ long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) {
+ this.iface = iface;
+ this.uid = uid;
+ this.set = set;
+ this.tag = tag;
+ this.metered = metered;
+ this.roaming = roaming;
+ this.defaultNetwork = defaultNetwork;
+ this.rxBytes = rxBytes;
+ this.rxPackets = rxPackets;
+ this.txBytes = txBytes;
+ this.txPackets = txPackets;
+ this.operations = operations;
+ }
+
+ /** @hide */
+ public boolean isNegative() {
+ return rxBytes < 0 || rxPackets < 0 || txBytes < 0 || txPackets < 0 || operations < 0;
+ }
+
+ /** @hide */
+ public boolean isEmpty() {
+ return rxBytes == 0 && rxPackets == 0 && txBytes == 0 && txPackets == 0
+ && operations == 0;
+ }
+
+ /** @hide */
+ public void add(Entry another) {
+ this.rxBytes += another.rxBytes;
+ this.rxPackets += another.rxPackets;
+ this.txBytes += another.txBytes;
+ this.txPackets += another.txPackets;
+ this.operations += another.operations;
+ }
+
+ /**
+ * @return interface name of this entry.
+ * @hide
+ */
+ @Nullable public String getIface() {
+ return iface;
+ }
+
+ /**
+ * @return the uid of this entry.
+ */
+ public int getUid() {
+ return uid;
+ }
+
+ /**
+ * @return the set state of this entry.
+ */
+ @State public int getSet() {
+ return set;
+ }
+
+ /**
+ * @return the tag value of this entry.
+ */
+ public int getTag() {
+ return tag;
+ }
+
+ /**
+ * @return the metered state.
+ */
+ @Meteredness
+ public int getMetered() {
+ return metered;
+ }
+
+ /**
+ * @return the roaming state.
+ */
+ @Roaming
+ public int getRoaming() {
+ return roaming;
+ }
+
+ /**
+ * @return the default network state.
+ */
+ @DefaultNetwork
+ public int getDefaultNetwork() {
+ return defaultNetwork;
+ }
+
+ /**
+ * @return the number of received bytes.
+ */
+ public long getRxBytes() {
+ return rxBytes;
+ }
+
+ /**
+ * @return the number of received packets.
+ */
+ public long getRxPackets() {
+ return rxPackets;
+ }
+
+ /**
+ * @return the number of transmitted bytes.
+ */
+ public long getTxBytes() {
+ return txBytes;
+ }
+
+ /**
+ * @return the number of transmitted packets.
+ */
+ public long getTxPackets() {
+ return txPackets;
+ }
+
+ /**
+ * @return the count of network operations performed for this entry.
+ */
+ public long getOperations() {
+ return operations;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("iface=").append(iface);
+ builder.append(" uid=").append(uid);
+ builder.append(" set=").append(setToString(set));
+ builder.append(" tag=").append(tagToString(tag));
+ builder.append(" metered=").append(meteredToString(metered));
+ builder.append(" roaming=").append(roamingToString(roaming));
+ builder.append(" defaultNetwork=").append(defaultNetworkToString(defaultNetwork));
+ builder.append(" rxBytes=").append(rxBytes);
+ builder.append(" rxPackets=").append(rxPackets);
+ builder.append(" txBytes=").append(txBytes);
+ builder.append(" txPackets=").append(txPackets);
+ builder.append(" operations=").append(operations);
+ return builder.toString();
+ }
+
+ /** @hide */
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (o instanceof Entry) {
+ final Entry e = (Entry) o;
+ return uid == e.uid && set == e.set && tag == e.tag && metered == e.metered
+ && roaming == e.roaming && defaultNetwork == e.defaultNetwork
+ && rxBytes == e.rxBytes && rxPackets == e.rxPackets
+ && txBytes == e.txBytes && txPackets == e.txPackets
+ && operations == e.operations && TextUtils.equals(iface, e.iface);
+ }
+ return false;
+ }
+
+ /** @hide */
+ @Override
+ public int hashCode() {
+ return Objects.hash(uid, set, tag, metered, roaming, defaultNetwork, iface);
+ }
+ }
+
+ public NetworkStats(long elapsedRealtime, int initialSize) {
+ this.elapsedRealtime = elapsedRealtime;
+ this.size = 0;
+ if (initialSize > 0) {
+ this.capacity = initialSize;
+ this.iface = new String[initialSize];
+ this.uid = new int[initialSize];
+ this.set = new int[initialSize];
+ this.tag = new int[initialSize];
+ this.metered = new int[initialSize];
+ this.roaming = new int[initialSize];
+ this.defaultNetwork = new int[initialSize];
+ this.rxBytes = new long[initialSize];
+ this.rxPackets = new long[initialSize];
+ this.txBytes = new long[initialSize];
+ this.txPackets = new long[initialSize];
+ this.operations = new long[initialSize];
+ } else {
+ // Special case for use by NetworkStatsFactory to start out *really* empty.
+ clear();
+ }
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public NetworkStats(Parcel parcel) {
+ elapsedRealtime = parcel.readLong();
+ size = parcel.readInt();
+ capacity = parcel.readInt();
+ iface = parcel.createStringArray();
+ uid = parcel.createIntArray();
+ set = parcel.createIntArray();
+ tag = parcel.createIntArray();
+ metered = parcel.createIntArray();
+ roaming = parcel.createIntArray();
+ defaultNetwork = parcel.createIntArray();
+ rxBytes = parcel.createLongArray();
+ rxPackets = parcel.createLongArray();
+ txBytes = parcel.createLongArray();
+ txPackets = parcel.createLongArray();
+ operations = parcel.createLongArray();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeLong(elapsedRealtime);
+ dest.writeInt(size);
+ dest.writeInt(capacity);
+ dest.writeStringArray(iface);
+ dest.writeIntArray(uid);
+ dest.writeIntArray(set);
+ dest.writeIntArray(tag);
+ dest.writeIntArray(metered);
+ dest.writeIntArray(roaming);
+ dest.writeIntArray(defaultNetwork);
+ dest.writeLongArray(rxBytes);
+ dest.writeLongArray(rxPackets);
+ dest.writeLongArray(txBytes);
+ dest.writeLongArray(txPackets);
+ dest.writeLongArray(operations);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public NetworkStats clone() {
+ final NetworkStats clone = new NetworkStats(elapsedRealtime, size);
+ NetworkStats.Entry entry = null;
+ for (int i = 0; i < size; i++) {
+ entry = getValues(i, entry);
+ clone.insertEntry(entry);
+ }
+ return clone;
+ }
+
+ /**
+ * Clear all data stored in this object.
+ * @hide
+ */
+ public void clear() {
+ this.capacity = 0;
+ this.iface = EmptyArray.STRING;
+ this.uid = EmptyArray.INT;
+ this.set = EmptyArray.INT;
+ this.tag = EmptyArray.INT;
+ this.metered = EmptyArray.INT;
+ this.roaming = EmptyArray.INT;
+ this.defaultNetwork = EmptyArray.INT;
+ this.rxBytes = EmptyArray.LONG;
+ this.rxPackets = EmptyArray.LONG;
+ this.txBytes = EmptyArray.LONG;
+ this.txPackets = EmptyArray.LONG;
+ this.operations = EmptyArray.LONG;
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public NetworkStats insertEntry(
+ String iface, long rxBytes, long rxPackets, long txBytes, long txPackets) {
+ return insertEntry(
+ iface, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets, 0L);
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public NetworkStats insertEntry(String iface, int uid, int set, int tag, long rxBytes,
+ long rxPackets, long txBytes, long txPackets, long operations) {
+ return insertEntry(new Entry(
+ iface, uid, set, tag, rxBytes, rxPackets, txBytes, txPackets, operations));
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public NetworkStats insertEntry(String iface, int uid, int set, int tag, int metered,
+ int roaming, int defaultNetwork, long rxBytes, long rxPackets, long txBytes,
+ long txPackets, long operations) {
+ return insertEntry(new Entry(
+ iface, uid, set, tag, metered, roaming, defaultNetwork, rxBytes, rxPackets,
+ txBytes, txPackets, operations));
+ }
+
+ /**
+ * Add new stats entry, copying from given {@link Entry}. The {@link Entry}
+ * object can be recycled across multiple calls.
+ * @hide
+ */
+ public NetworkStats insertEntry(Entry entry) {
+ if (size >= capacity) {
+ final int newLength = Math.max(size, 10) * 3 / 2;
+ iface = Arrays.copyOf(iface, newLength);
+ uid = Arrays.copyOf(uid, newLength);
+ set = Arrays.copyOf(set, newLength);
+ tag = Arrays.copyOf(tag, newLength);
+ metered = Arrays.copyOf(metered, newLength);
+ roaming = Arrays.copyOf(roaming, newLength);
+ defaultNetwork = Arrays.copyOf(defaultNetwork, newLength);
+ rxBytes = Arrays.copyOf(rxBytes, newLength);
+ rxPackets = Arrays.copyOf(rxPackets, newLength);
+ txBytes = Arrays.copyOf(txBytes, newLength);
+ txPackets = Arrays.copyOf(txPackets, newLength);
+ operations = Arrays.copyOf(operations, newLength);
+ capacity = newLength;
+ }
+
+ setValues(size, entry);
+ size++;
+
+ return this;
+ }
+
+ private void setValues(int i, Entry entry) {
+ iface[i] = entry.iface;
+ uid[i] = entry.uid;
+ set[i] = entry.set;
+ tag[i] = entry.tag;
+ metered[i] = entry.metered;
+ roaming[i] = entry.roaming;
+ defaultNetwork[i] = entry.defaultNetwork;
+ rxBytes[i] = entry.rxBytes;
+ rxPackets[i] = entry.rxPackets;
+ txBytes[i] = entry.txBytes;
+ txPackets[i] = entry.txPackets;
+ operations[i] = entry.operations;
+ }
+
+ /**
+ * Iterate over Entry objects.
+ *
+ * Return an iterator of this object that will iterate through all contained Entry objects.
+ *
+ * This iterator does not support concurrent modification and makes no guarantee of fail-fast
+ * behavior. If any method that can mutate the contents of this object is called while
+ * iteration is in progress, either inside the loop or in another thread, then behavior is
+ * undefined.
+ * The remove() method is not implemented and will throw UnsupportedOperationException.
+ * @hide
+ */
+ @SystemApi
+ @NonNull public Iterator<Entry> iterator() {
+ return new Iterator<Entry>() {
+ int mIndex = 0;
+
+ @Override
+ public boolean hasNext() {
+ return mIndex < size;
+ }
+
+ @Override
+ public Entry next() {
+ return getValues(mIndex++, null);
+ }
+ };
+ }
+
+ /**
+ * Return specific stats entry.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public Entry getValues(int i, @Nullable Entry recycle) {
+ final Entry entry = recycle != null ? recycle : new Entry();
+ entry.iface = iface[i];
+ entry.uid = uid[i];
+ entry.set = set[i];
+ entry.tag = tag[i];
+ entry.metered = metered[i];
+ entry.roaming = roaming[i];
+ entry.defaultNetwork = defaultNetwork[i];
+ entry.rxBytes = rxBytes[i];
+ entry.rxPackets = rxPackets[i];
+ entry.txBytes = txBytes[i];
+ entry.txPackets = txPackets[i];
+ entry.operations = operations[i];
+ return entry;
+ }
+
+ /**
+ * If @{code dest} is not equal to @{code src}, copy entry from index @{code src} to index
+ * @{code dest}.
+ */
+ private void maybeCopyEntry(int dest, int src) {
+ if (dest == src) return;
+ iface[dest] = iface[src];
+ uid[dest] = uid[src];
+ set[dest] = set[src];
+ tag[dest] = tag[src];
+ metered[dest] = metered[src];
+ roaming[dest] = roaming[src];
+ defaultNetwork[dest] = defaultNetwork[src];
+ rxBytes[dest] = rxBytes[src];
+ rxPackets[dest] = rxPackets[src];
+ txBytes[dest] = txBytes[src];
+ txPackets[dest] = txPackets[src];
+ operations[dest] = operations[src];
+ }
+
+ /** @hide */
+ public long getElapsedRealtime() {
+ return elapsedRealtime;
+ }
+
+ /** @hide */
+ public void setElapsedRealtime(long time) {
+ elapsedRealtime = time;
+ }
+
+ /**
+ * Return age of this {@link NetworkStats} object with respect to
+ * {@link SystemClock#elapsedRealtime()}.
+ * @hide
+ */
+ public long getElapsedRealtimeAge() {
+ return SystemClock.elapsedRealtime() - elapsedRealtime;
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public int size() {
+ return size;
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public int internalSize() {
+ return capacity;
+ }
+
+ /** @hide */
+ @Deprecated
+ public NetworkStats combineValues(String iface, int uid, int tag, long rxBytes, long rxPackets,
+ long txBytes, long txPackets, long operations) {
+ return combineValues(
+ iface, uid, SET_DEFAULT, tag, rxBytes, rxPackets, txBytes,
+ txPackets, operations);
+ }
+
+ /** @hide */
+ public NetworkStats combineValues(String iface, int uid, int set, int tag,
+ long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) {
+ return combineValues(new Entry(
+ iface, uid, set, tag, rxBytes, rxPackets, txBytes, txPackets, operations));
+ }
+
+ /**
+ * Combine given values with an existing row, or create a new row if
+ * {@link #findIndex(String, int, int, int, int, int, int)} is unable to find match. Can
+ * also be used to subtract values from existing rows. This method mutates the referencing
+ * {@link NetworkStats} object.
+ *
+ * @param entry the {@link Entry} to combine.
+ * @return a reference to this mutated {@link NetworkStats} object.
+ * @hide
+ */
+ public @NonNull NetworkStats combineValues(@NonNull Entry entry) {
+ final int i = findIndex(entry.iface, entry.uid, entry.set, entry.tag, entry.metered,
+ entry.roaming, entry.defaultNetwork);
+ if (i == -1) {
+ // only create new entry when positive contribution
+ insertEntry(entry);
+ } else {
+ rxBytes[i] += entry.rxBytes;
+ rxPackets[i] += entry.rxPackets;
+ txBytes[i] += entry.txBytes;
+ txPackets[i] += entry.txPackets;
+ operations[i] += entry.operations;
+ }
+ return this;
+ }
+
+ /**
+ * Add given values with an existing row, or create a new row if
+ * {@link #findIndex(String, int, int, int, int, int, int)} is unable to find match. Can
+ * also be used to subtract values from existing rows.
+ *
+ * @param entry the {@link Entry} to add.
+ * @return a new constructed {@link NetworkStats} object that contains the result.
+ */
+ public @NonNull NetworkStats addEntry(@NonNull Entry entry) {
+ return this.clone().combineValues(entry);
+ }
+
+ /**
+ * Add the given {@link NetworkStats} objects.
+ *
+ * @return the sum of two objects.
+ */
+ public @NonNull NetworkStats add(@NonNull NetworkStats another) {
+ final NetworkStats ret = this.clone();
+ ret.combineAllValues(another);
+ return ret;
+ }
+
+ /**
+ * Combine all values from another {@link NetworkStats} into this object.
+ * @hide
+ */
+ public void combineAllValues(@NonNull NetworkStats another) {
+ NetworkStats.Entry entry = null;
+ for (int i = 0; i < another.size; i++) {
+ entry = another.getValues(i, entry);
+ combineValues(entry);
+ }
+ }
+
+ /**
+ * Find first stats index that matches the requested parameters.
+ * @hide
+ */
+ public int findIndex(String iface, int uid, int set, int tag, int metered, int roaming,
+ int defaultNetwork) {
+ for (int i = 0; i < size; i++) {
+ if (uid == this.uid[i] && set == this.set[i] && tag == this.tag[i]
+ && metered == this.metered[i] && roaming == this.roaming[i]
+ && defaultNetwork == this.defaultNetwork[i]
+ && Objects.equals(iface, this.iface[i])) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Find first stats index that matches the requested parameters, starting
+ * search around the hinted index as an optimization.
+ * @hide
+ */
+ @VisibleForTesting
+ public int findIndexHinted(String iface, int uid, int set, int tag, int metered, int roaming,
+ int defaultNetwork, int hintIndex) {
+ for (int offset = 0; offset < size; offset++) {
+ final int halfOffset = offset / 2;
+
+ // search outwards from hint index, alternating forward and backward
+ final int i;
+ if (offset % 2 == 0) {
+ i = (hintIndex + halfOffset) % size;
+ } else {
+ i = (size + hintIndex - halfOffset - 1) % size;
+ }
+
+ if (uid == this.uid[i] && set == this.set[i] && tag == this.tag[i]
+ && metered == this.metered[i] && roaming == this.roaming[i]
+ && defaultNetwork == this.defaultNetwork[i]
+ && Objects.equals(iface, this.iface[i])) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Splice in {@link #operations} from the given {@link NetworkStats} based
+ * on matching {@link #uid} and {@link #tag} rows. Ignores {@link #iface},
+ * since operation counts are at data layer.
+ * @hide
+ */
+ public void spliceOperationsFrom(NetworkStats stats) {
+ for (int i = 0; i < size; i++) {
+ final int j = stats.findIndex(iface[i], uid[i], set[i], tag[i], metered[i], roaming[i],
+ defaultNetwork[i]);
+ if (j == -1) {
+ operations[i] = 0;
+ } else {
+ operations[i] = stats.operations[j];
+ }
+ }
+ }
+
+ /**
+ * Return list of unique interfaces known by this data structure.
+ * @hide
+ */
+ public String[] getUniqueIfaces() {
+ final HashSet<String> ifaces = new HashSet<String>();
+ for (String iface : this.iface) {
+ if (iface != IFACE_ALL) {
+ ifaces.add(iface);
+ }
+ }
+ return ifaces.toArray(new String[ifaces.size()]);
+ }
+
+ /**
+ * Return list of unique UIDs known by this data structure.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public int[] getUniqueUids() {
+ final SparseBooleanArray uids = new SparseBooleanArray();
+ for (int uid : this.uid) {
+ uids.put(uid, true);
+ }
+
+ final int size = uids.size();
+ final int[] result = new int[size];
+ for (int i = 0; i < size; i++) {
+ result[i] = uids.keyAt(i);
+ }
+ return result;
+ }
+
+ /**
+ * Return total bytes represented by this snapshot object, usually used when
+ * checking if a {@link #subtract(NetworkStats)} delta passes a threshold.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public long getTotalBytes() {
+ final Entry entry = getTotal(null);
+ return entry.rxBytes + entry.txBytes;
+ }
+
+ /**
+ * Return total of all fields represented by this snapshot object.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public Entry getTotal(Entry recycle) {
+ return getTotal(recycle, null, UID_ALL, false);
+ }
+
+ /**
+ * Return total of all fields represented by this snapshot object matching
+ * the requested {@link #uid}.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public Entry getTotal(Entry recycle, int limitUid) {
+ return getTotal(recycle, null, limitUid, false);
+ }
+
+ /**
+ * Return total of all fields represented by this snapshot object matching
+ * the requested {@link #iface}.
+ * @hide
+ */
+ public Entry getTotal(Entry recycle, HashSet<String> limitIface) {
+ return getTotal(recycle, limitIface, UID_ALL, false);
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public Entry getTotalIncludingTags(Entry recycle) {
+ return getTotal(recycle, null, UID_ALL, true);
+ }
+
+ /**
+ * Return total of all fields represented by this snapshot object matching
+ * the requested {@link #iface} and {@link #uid}.
+ *
+ * @param limitIface Set of {@link #iface} to include in total; or {@code
+ * null} to include all ifaces.
+ */
+ private Entry getTotal(
+ Entry recycle, HashSet<String> limitIface, int limitUid, boolean includeTags) {
+ final Entry entry = recycle != null ? recycle : new Entry();
+
+ entry.iface = IFACE_ALL;
+ entry.uid = limitUid;
+ entry.set = SET_ALL;
+ entry.tag = TAG_NONE;
+ entry.metered = METERED_ALL;
+ entry.roaming = ROAMING_ALL;
+ entry.defaultNetwork = DEFAULT_NETWORK_ALL;
+ entry.rxBytes = 0;
+ entry.rxPackets = 0;
+ entry.txBytes = 0;
+ entry.txPackets = 0;
+ entry.operations = 0;
+
+ for (int i = 0; i < size; i++) {
+ final boolean matchesUid = (limitUid == UID_ALL) || (limitUid == uid[i]);
+ final boolean matchesIface = (limitIface == null) || (limitIface.contains(iface[i]));
+
+ if (matchesUid && matchesIface) {
+ // skip specific tags, since already counted in TAG_NONE
+ if (tag[i] != TAG_NONE && !includeTags) continue;
+
+ entry.rxBytes += rxBytes[i];
+ entry.rxPackets += rxPackets[i];
+ entry.txBytes += txBytes[i];
+ entry.txPackets += txPackets[i];
+ entry.operations += operations[i];
+ }
+ }
+ return entry;
+ }
+
+ /**
+ * Fast path for battery stats.
+ * @hide
+ */
+ public long getTotalPackets() {
+ long total = 0;
+ for (int i = size-1; i >= 0; i--) {
+ total += rxPackets[i] + txPackets[i];
+ }
+ return total;
+ }
+
+ /**
+ * Subtract the given {@link NetworkStats}, effectively leaving the delta
+ * between two snapshots in time. Assumes that statistics rows collect over
+ * time, and that none of them have disappeared. This method does not mutate
+ * the referencing object.
+ *
+ * @return the delta between two objects.
+ */
+ public @NonNull NetworkStats subtract(@NonNull NetworkStats right) {
+ return subtract(this, right, null, null);
+ }
+
+ /**
+ * Subtract the two given {@link NetworkStats} objects, returning the delta
+ * between two snapshots in time. Assumes that statistics rows collect over
+ * time, and that none of them have disappeared.
+ * <p>
+ * If counters have rolled backwards, they are clamped to {@code 0} and
+ * reported to the given {@link NonMonotonicObserver}.
+ * @hide
+ */
+ public static <C> NetworkStats subtract(NetworkStats left, NetworkStats right,
+ NonMonotonicObserver<C> observer, C cookie) {
+ return subtract(left, right, observer, cookie, null);
+ }
+
+ /**
+ * Subtract the two given {@link NetworkStats} objects, returning the delta
+ * between two snapshots in time. Assumes that statistics rows collect over
+ * time, and that none of them have disappeared.
+ * <p>
+ * If counters have rolled backwards, they are clamped to {@code 0} and
+ * reported to the given {@link NonMonotonicObserver}.
+ * <p>
+ * If <var>recycle</var> is supplied, this NetworkStats object will be
+ * reused (and returned) as the result if it is large enough to contain
+ * the data.
+ * @hide
+ */
+ public static <C> NetworkStats subtract(NetworkStats left, NetworkStats right,
+ NonMonotonicObserver<C> observer, C cookie, NetworkStats recycle) {
+ long deltaRealtime = left.elapsedRealtime - right.elapsedRealtime;
+ if (deltaRealtime < 0) {
+ if (observer != null) {
+ observer.foundNonMonotonic(left, -1, right, -1, cookie);
+ }
+ deltaRealtime = 0;
+ }
+
+ // result will have our rows, and elapsed time between snapshots
+ final Entry entry = new Entry();
+ final NetworkStats result;
+ if (recycle != null && recycle.capacity >= left.size) {
+ result = recycle;
+ result.size = 0;
+ result.elapsedRealtime = deltaRealtime;
+ } else {
+ result = new NetworkStats(deltaRealtime, left.size);
+ }
+ for (int i = 0; i < left.size; i++) {
+ entry.iface = left.iface[i];
+ entry.uid = left.uid[i];
+ entry.set = left.set[i];
+ entry.tag = left.tag[i];
+ entry.metered = left.metered[i];
+ entry.roaming = left.roaming[i];
+ entry.defaultNetwork = left.defaultNetwork[i];
+ entry.rxBytes = left.rxBytes[i];
+ entry.rxPackets = left.rxPackets[i];
+ entry.txBytes = left.txBytes[i];
+ entry.txPackets = left.txPackets[i];
+ entry.operations = left.operations[i];
+
+ // find remote row that matches, and subtract
+ final int j = right.findIndexHinted(entry.iface, entry.uid, entry.set, entry.tag,
+ entry.metered, entry.roaming, entry.defaultNetwork, i);
+ if (j != -1) {
+ // Found matching row, subtract remote value.
+ entry.rxBytes -= right.rxBytes[j];
+ entry.rxPackets -= right.rxPackets[j];
+ entry.txBytes -= right.txBytes[j];
+ entry.txPackets -= right.txPackets[j];
+ entry.operations -= right.operations[j];
+ }
+
+ if (entry.isNegative()) {
+ if (observer != null) {
+ observer.foundNonMonotonic(left, i, right, j, cookie);
+ }
+ entry.rxBytes = Math.max(entry.rxBytes, 0);
+ entry.rxPackets = Math.max(entry.rxPackets, 0);
+ entry.txBytes = Math.max(entry.txBytes, 0);
+ entry.txPackets = Math.max(entry.txPackets, 0);
+ entry.operations = Math.max(entry.operations, 0);
+ }
+
+ result.insertEntry(entry);
+ }
+
+ return result;
+ }
+
+ /**
+ * Calculate and apply adjustments to captured statistics for 464xlat traffic.
+ *
+ * <p>This mutates stacked traffic stats, to account for IPv4/IPv6 header size difference.
+ *
+ * <p>UID stats, which are only accounted on the stacked interface, need to be increased
+ * by 20 bytes/packet to account for translation overhead.
+ *
+ * <p>The potential additional overhead of 8 bytes/packet for ip fragments is ignored.
+ *
+ * <p>Interface stats need to sum traffic on both stacked and base interface because:
+ * - eBPF offloaded packets appear only on the stacked interface
+ * - Non-offloaded ingress packets appear only on the stacked interface
+ * (due to iptables raw PREROUTING drop rules)
+ * - Non-offloaded egress packets appear only on the stacked interface
+ * (due to ignoring traffic from clat daemon by uid match)
+ * (and of course the 20 bytes/packet overhead needs to be applied to stacked interface stats)
+ *
+ * <p>This method will behave fine if {@code stackedIfaces} is an non-synchronized but add-only
+ * {@code ConcurrentHashMap}
+ * @param baseTraffic Traffic on the base interfaces. Will be mutated.
+ * @param stackedTraffic Stats with traffic stacked on top of our ifaces. Will also be mutated.
+ * @param stackedIfaces Mapping ipv6if -> ipv4if interface where traffic is counted on both.
+ * @hide
+ */
+ public static void apply464xlatAdjustments(NetworkStats baseTraffic,
+ NetworkStats stackedTraffic, Map<String, String> stackedIfaces) {
+ // For recycling
+ Entry entry = null;
+ for (int i = 0; i < stackedTraffic.size; i++) {
+ entry = stackedTraffic.getValues(i, entry);
+ if (entry == null) continue;
+ if (entry.iface == null) continue;
+ if (!entry.iface.startsWith(CLATD_INTERFACE_PREFIX)) continue;
+
+ // For 464xlat traffic, per uid stats only counts the bytes of the native IPv4 packet
+ // sent on the stacked interface with prefix "v4-" and drops the IPv6 header size after
+ // unwrapping. To account correctly for on-the-wire traffic, add the 20 additional bytes
+ // difference for all packets (http://b/12249687, http:/b/33681750).
+ //
+ // Note: this doesn't account for LRO/GRO/GSO/TSO (ie. >mtu) traffic correctly, nor
+ // does it correctly account for the 8 extra bytes in the IPv6 fragmentation header.
+ //
+ // While the ebpf code path does try to simulate proper post segmentation packet
+ // counts, we have nothing of the sort of xt_qtaguid stats.
+ entry.rxBytes += entry.rxPackets * IPV4V6_HEADER_DELTA;
+ entry.txBytes += entry.txPackets * IPV4V6_HEADER_DELTA;
+ stackedTraffic.setValues(i, entry);
+ }
+ }
+
+ /**
+ * Calculate and apply adjustments to captured statistics for 464xlat traffic counted twice.
+ *
+ * <p>This mutates the object this method is called on. Equivalent to calling
+ * {@link #apply464xlatAdjustments(NetworkStats, NetworkStats, Map)} with {@code this} as
+ * base and stacked traffic.
+ * @param stackedIfaces Mapping ipv6if -> ipv4if interface where traffic is counted on both.
+ * @hide
+ */
+ public void apply464xlatAdjustments(Map<String, String> stackedIfaces) {
+ apply464xlatAdjustments(this, this, stackedIfaces);
+ }
+
+ /**
+ * Return total statistics grouped by {@link #iface}; doesn't mutate the
+ * original structure.
+ * @hide
+ */
+ public NetworkStats groupedByIface() {
+ final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
+
+ final Entry entry = new Entry();
+ entry.uid = UID_ALL;
+ entry.set = SET_ALL;
+ entry.tag = TAG_NONE;
+ entry.metered = METERED_ALL;
+ entry.roaming = ROAMING_ALL;
+ entry.defaultNetwork = DEFAULT_NETWORK_ALL;
+ entry.operations = 0L;
+
+ for (int i = 0; i < size; i++) {
+ // skip specific tags, since already counted in TAG_NONE
+ if (tag[i] != TAG_NONE) continue;
+
+ entry.iface = iface[i];
+ entry.rxBytes = rxBytes[i];
+ entry.rxPackets = rxPackets[i];
+ entry.txBytes = txBytes[i];
+ entry.txPackets = txPackets[i];
+ stats.combineValues(entry);
+ }
+
+ return stats;
+ }
+
+ /**
+ * Return total statistics grouped by {@link #uid}; doesn't mutate the
+ * original structure.
+ * @hide
+ */
+ public NetworkStats groupedByUid() {
+ final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
+
+ final Entry entry = new Entry();
+ entry.iface = IFACE_ALL;
+ entry.set = SET_ALL;
+ entry.tag = TAG_NONE;
+ entry.metered = METERED_ALL;
+ entry.roaming = ROAMING_ALL;
+ entry.defaultNetwork = DEFAULT_NETWORK_ALL;
+
+ for (int i = 0; i < size; i++) {
+ // skip specific tags, since already counted in TAG_NONE
+ if (tag[i] != TAG_NONE) continue;
+
+ entry.uid = uid[i];
+ entry.rxBytes = rxBytes[i];
+ entry.rxPackets = rxPackets[i];
+ entry.txBytes = txBytes[i];
+ entry.txPackets = txPackets[i];
+ entry.operations = operations[i];
+ stats.combineValues(entry);
+ }
+
+ return stats;
+ }
+
+ /**
+ * Remove all rows that match one of specified UIDs.
+ * This mutates the original structure in place.
+ * @hide
+ */
+ public void removeUids(int[] uids) {
+ filter(e -> !CollectionUtils.contains(uids, e.uid));
+ }
+
+ /**
+ * Remove all rows that match one of specified UIDs.
+ * @return the result object.
+ * @hide
+ */
+ @NonNull
+ public NetworkStats removeEmptyEntries() {
+ final NetworkStats ret = this.clone();
+ ret.filter(e -> e.rxBytes != 0 || e.rxPackets != 0 || e.txBytes != 0 || e.txPackets != 0
+ || e.operations != 0);
+ return ret;
+ }
+
+ /**
+ * Only keep entries that match all specified filters.
+ *
+ * <p>This mutates the original structure in place. After this method is called,
+ * size is the number of matching entries, and capacity is the previous capacity.
+ * @param limitUid UID to filter for, or {@link #UID_ALL}.
+ * @param limitIfaces Interfaces to filter for, or {@link #INTERFACES_ALL}.
+ * @param limitTag Tag to filter for, or {@link #TAG_ALL}.
+ * @hide
+ */
+ public void filter(int limitUid, String[] limitIfaces, int limitTag) {
+ if (limitUid == UID_ALL && limitTag == TAG_ALL && limitIfaces == INTERFACES_ALL) {
+ return;
+ }
+ filter(e -> (limitUid == UID_ALL || limitUid == e.uid)
+ && (limitTag == TAG_ALL || limitTag == e.tag)
+ && (limitIfaces == INTERFACES_ALL
+ || CollectionUtils.contains(limitIfaces, e.iface)));
+ }
+
+ /**
+ * Only keep entries with {@link #set} value less than {@link #SET_DEBUG_START}.
+ *
+ * <p>This mutates the original structure in place.
+ * @hide
+ */
+ public void filterDebugEntries() {
+ filter(e -> e.set < SET_DEBUG_START);
+ }
+
+ private void filter(Predicate<Entry> predicate) {
+ Entry entry = new Entry();
+ int nextOutputEntry = 0;
+ for (int i = 0; i < size; i++) {
+ entry = getValues(i, entry);
+ if (predicate.test(entry)) {
+ if (nextOutputEntry != i) {
+ setValues(nextOutputEntry, entry);
+ }
+ nextOutputEntry++;
+ }
+ }
+ size = nextOutputEntry;
+ }
+
+ /** @hide */
+ public void dump(String prefix, PrintWriter pw) {
+ pw.print(prefix);
+ pw.print("NetworkStats: elapsedRealtime="); pw.println(elapsedRealtime);
+ for (int i = 0; i < size; i++) {
+ pw.print(prefix);
+ pw.print(" ["); pw.print(i); pw.print("]");
+ pw.print(" iface="); pw.print(iface[i]);
+ pw.print(" uid="); pw.print(uid[i]);
+ pw.print(" set="); pw.print(setToString(set[i]));
+ pw.print(" tag="); pw.print(tagToString(tag[i]));
+ pw.print(" metered="); pw.print(meteredToString(metered[i]));
+ pw.print(" roaming="); pw.print(roamingToString(roaming[i]));
+ pw.print(" defaultNetwork="); pw.print(defaultNetworkToString(defaultNetwork[i]));
+ pw.print(" rxBytes="); pw.print(rxBytes[i]);
+ pw.print(" rxPackets="); pw.print(rxPackets[i]);
+ pw.print(" txBytes="); pw.print(txBytes[i]);
+ pw.print(" txPackets="); pw.print(txPackets[i]);
+ pw.print(" operations="); pw.println(operations[i]);
+ }
+ }
+
+ /**
+ * Return text description of {@link #set} value.
+ * @hide
+ */
+ public static String setToString(int set) {
+ switch (set) {
+ case SET_ALL:
+ return "ALL";
+ case SET_DEFAULT:
+ return "DEFAULT";
+ case SET_FOREGROUND:
+ return "FOREGROUND";
+ case SET_DBG_VPN_IN:
+ return "DBG_VPN_IN";
+ case SET_DBG_VPN_OUT:
+ return "DBG_VPN_OUT";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
+ /**
+ * Return text description of {@link #set} value.
+ * @hide
+ */
+ public static String setToCheckinString(int set) {
+ switch (set) {
+ case SET_ALL:
+ return "all";
+ case SET_DEFAULT:
+ return "def";
+ case SET_FOREGROUND:
+ return "fg";
+ case SET_DBG_VPN_IN:
+ return "vpnin";
+ case SET_DBG_VPN_OUT:
+ return "vpnout";
+ default:
+ return "unk";
+ }
+ }
+
+ /**
+ * @return true if the querySet matches the dataSet.
+ * @hide
+ */
+ public static boolean setMatches(int querySet, int dataSet) {
+ if (querySet == dataSet) {
+ return true;
+ }
+ // SET_ALL matches all non-debugging sets.
+ return querySet == SET_ALL && dataSet < SET_DEBUG_START;
+ }
+
+ /**
+ * Return text description of {@link #tag} value.
+ * @hide
+ */
+ public static String tagToString(int tag) {
+ return "0x" + Integer.toHexString(tag);
+ }
+
+ /**
+ * Return text description of {@link #metered} value.
+ * @hide
+ */
+ public static String meteredToString(int metered) {
+ switch (metered) {
+ case METERED_ALL:
+ return "ALL";
+ case METERED_NO:
+ return "NO";
+ case METERED_YES:
+ return "YES";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
+ /**
+ * Return text description of {@link #roaming} value.
+ * @hide
+ */
+ public static String roamingToString(int roaming) {
+ switch (roaming) {
+ case ROAMING_ALL:
+ return "ALL";
+ case ROAMING_NO:
+ return "NO";
+ case ROAMING_YES:
+ return "YES";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
+ /**
+ * Return text description of {@link #defaultNetwork} value.
+ * @hide
+ */
+ public static String defaultNetworkToString(int defaultNetwork) {
+ switch (defaultNetwork) {
+ case DEFAULT_NETWORK_ALL:
+ return "ALL";
+ case DEFAULT_NETWORK_NO:
+ return "NO";
+ case DEFAULT_NETWORK_YES:
+ return "YES";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
+ /** @hide */
+ @Override
+ public String toString() {
+ final CharArrayWriter writer = new CharArrayWriter();
+ dump("", new PrintWriter(writer));
+ return writer.toString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final @NonNull Creator<NetworkStats> CREATOR = new Creator<NetworkStats>() {
+ @Override
+ public NetworkStats createFromParcel(Parcel in) {
+ return new NetworkStats(in);
+ }
+
+ @Override
+ public NetworkStats[] newArray(int size) {
+ return new NetworkStats[size];
+ }
+ };
+
+ /** @hide */
+ public interface NonMonotonicObserver<C> {
+ public void foundNonMonotonic(
+ NetworkStats left, int leftIndex, NetworkStats right, int rightIndex, C cookie);
+ public void foundNonMonotonic(
+ NetworkStats stats, int statsIndex, C cookie);
+ }
+
+ /**
+ * VPN accounting. Move some VPN's underlying traffic to other UIDs that use tun0 iface.
+ *
+ * <p>This method should only be called on delta NetworkStats. Do not call this method on a
+ * snapshot {@link NetworkStats} object because the tunUid and/or the underlyingIface may change
+ * over time.
+ *
+ * <p>This method performs adjustments for one active VPN package and one VPN iface at a time.
+ *
+ * @param tunUid uid of the VPN application
+ * @param tunIface iface of the vpn tunnel
+ * @param underlyingIfaces underlying network ifaces used by the VPN application
+ * @hide
+ */
+ public void migrateTun(int tunUid, @NonNull String tunIface,
+ @NonNull List<String> underlyingIfaces) {
+ // Combined usage by all apps using VPN.
+ final Entry tunIfaceTotal = new Entry();
+ // Usage by VPN, grouped by its {@code underlyingIfaces}.
+ final Entry[] perInterfaceTotal = new Entry[underlyingIfaces.size()];
+ // Usage by VPN, summed across all its {@code underlyingIfaces}.
+ final Entry underlyingIfacesTotal = new Entry();
+
+ for (int i = 0; i < perInterfaceTotal.length; i++) {
+ perInterfaceTotal[i] = new Entry();
+ }
+
+ tunAdjustmentInit(tunUid, tunIface, underlyingIfaces, tunIfaceTotal, perInterfaceTotal,
+ underlyingIfacesTotal);
+
+ // If tunIface < underlyingIfacesTotal, it leaves the overhead traffic in the VPN app.
+ // If tunIface > underlyingIfacesTotal, the VPN app doesn't get credit for data compression.
+ // Negative stats should be avoided.
+ final Entry[] moved =
+ addTrafficToApplications(tunUid, tunIface, underlyingIfaces, tunIfaceTotal,
+ perInterfaceTotal, underlyingIfacesTotal);
+ deductTrafficFromVpnApp(tunUid, underlyingIfaces, moved);
+ }
+
+ /**
+ * Initializes the data used by the migrateTun() method.
+ *
+ * <p>This is the first pass iteration which does the following work:
+ *
+ * <ul>
+ * <li>Adds up all the traffic through the tunUid's underlyingIfaces (both foreground and
+ * background).
+ * <li>Adds up all the traffic through tun0 excluding traffic from the vpn app itself.
+ * </ul>
+ *
+ * @param tunUid uid of the VPN application
+ * @param tunIface iface of the vpn tunnel
+ * @param underlyingIfaces underlying network ifaces used by the VPN application
+ * @param tunIfaceTotal output parameter; combined data usage by all apps using VPN
+ * @param perInterfaceTotal output parameter; data usage by VPN app, grouped by its {@code
+ * underlyingIfaces}
+ * @param underlyingIfacesTotal output parameter; data usage by VPN, summed across all of its
+ * {@code underlyingIfaces}
+ */
+ private void tunAdjustmentInit(int tunUid, @NonNull String tunIface,
+ @NonNull List<String> underlyingIfaces, @NonNull Entry tunIfaceTotal,
+ @NonNull Entry[] perInterfaceTotal, @NonNull Entry underlyingIfacesTotal) {
+ final Entry recycle = new Entry();
+ for (int i = 0; i < size; i++) {
+ getValues(i, recycle);
+ if (recycle.uid == UID_ALL) {
+ throw new IllegalStateException(
+ "Cannot adjust VPN accounting on an iface aggregated NetworkStats.");
+ }
+ if (recycle.set == SET_DBG_VPN_IN || recycle.set == SET_DBG_VPN_OUT) {
+ throw new IllegalStateException(
+ "Cannot adjust VPN accounting on a NetworkStats containing SET_DBG_VPN_*");
+ }
+ if (recycle.tag != TAG_NONE) {
+ // TODO(b/123666283): Take all tags for tunUid into account.
+ continue;
+ }
+
+ if (tunUid == Process.SYSTEM_UID) {
+ // Kernel-based VPN or VCN, traffic sent by apps on the VPN/VCN network
+ //
+ // Since the data is not UID-accounted on underlying networks, just use VPN/VCN
+ // network usage as ground truth. Encrypted traffic on the underlying networks will
+ // never be processed here because encrypted traffic on the underlying interfaces
+ // is not present in UID stats, and this method is only called on UID stats.
+ if (tunIface.equals(recycle.iface)) {
+ tunIfaceTotal.add(recycle);
+ underlyingIfacesTotal.add(recycle);
+
+ // In steady state, there should always be one network, but edge cases may
+ // result in the network being null (network lost), and thus no underlying
+ // ifaces is possible.
+ if (perInterfaceTotal.length > 0) {
+ // While platform VPNs and VCNs have exactly one underlying network, that
+ // network may have multiple interfaces (eg for 464xlat). This layer does
+ // not have the required information to identify which of the interfaces
+ // were used. Select "any" of the interfaces. Since overhead is already
+ // lost, this number is an approximation anyways.
+ perInterfaceTotal[0].add(recycle);
+ }
+ }
+ } else if (recycle.uid == tunUid) {
+ // VpnService VPN, traffic sent by the VPN app over underlying networks
+ for (int j = 0; j < underlyingIfaces.size(); j++) {
+ if (Objects.equals(underlyingIfaces.get(j), recycle.iface)) {
+ perInterfaceTotal[j].add(recycle);
+ underlyingIfacesTotal.add(recycle);
+ break;
+ }
+ }
+ } else if (tunIface.equals(recycle.iface)) {
+ // VpnService VPN; traffic sent by apps on the VPN network
+ tunIfaceTotal.add(recycle);
+ }
+ }
+ }
+
+ /**
+ * Distributes traffic across apps that are using given {@code tunIface}, and returns the total
+ * traffic that should be moved off of {@code tunUid} grouped by {@code underlyingIfaces}.
+ *
+ * @param tunUid uid of the VPN application
+ * @param tunIface iface of the vpn tunnel
+ * @param underlyingIfaces underlying network ifaces used by the VPN application
+ * @param tunIfaceTotal combined data usage across all apps using {@code tunIface}
+ * @param perInterfaceTotal data usage by VPN app, grouped by its {@code underlyingIfaces}
+ * @param underlyingIfacesTotal data usage by VPN, summed across all of its {@code
+ * underlyingIfaces}
+ */
+ private Entry[] addTrafficToApplications(int tunUid, @NonNull String tunIface,
+ @NonNull List<String> underlyingIfaces, @NonNull Entry tunIfaceTotal,
+ @NonNull Entry[] perInterfaceTotal, @NonNull Entry underlyingIfacesTotal) {
+ // Traffic that should be moved off of each underlying interface for tunUid (see
+ // deductTrafficFromVpnApp below).
+ final Entry[] moved = new Entry[underlyingIfaces.size()];
+ for (int i = 0; i < underlyingIfaces.size(); i++) {
+ moved[i] = new Entry();
+ }
+
+ final Entry tmpEntry = new Entry();
+ final int origSize = size;
+ for (int i = 0; i < origSize; i++) {
+ if (!Objects.equals(iface[i], tunIface)) {
+ // Consider only entries that go onto the VPN interface.
+ continue;
+ }
+
+ if (uid[i] == tunUid && tunUid != Process.SYSTEM_UID) {
+ // Exclude VPN app from the redistribution, as it can choose to create packet
+ // streams by writing to itself.
+ //
+ // However, for platform VPNs, do not exclude the system's usage of the VPN network,
+ // since it is never local-only, and never double counted
+ continue;
+ }
+ tmpEntry.uid = uid[i];
+ tmpEntry.tag = tag[i];
+ tmpEntry.metered = metered[i];
+ tmpEntry.roaming = roaming[i];
+ tmpEntry.defaultNetwork = defaultNetwork[i];
+
+ // In a first pass, compute this entry's total share of data across all
+ // underlyingIfaces. This is computed on the basis of the share of this entry's usage
+ // over tunIface.
+ // TODO: Consider refactoring first pass into a separate helper method.
+ long totalRxBytes = 0;
+ if (tunIfaceTotal.rxBytes > 0) {
+ // Note - The multiplication below should not overflow since NetworkStatsService
+ // processes this every time device has transmitted/received amount equivalent to
+ // global threshold alert (~ 2MB) across all interfaces.
+ final long rxBytesAcrossUnderlyingIfaces =
+ multiplySafeByRational(underlyingIfacesTotal.rxBytes,
+ rxBytes[i], tunIfaceTotal.rxBytes);
+ // app must not be blamed for more than it consumed on tunIface
+ totalRxBytes = Math.min(rxBytes[i], rxBytesAcrossUnderlyingIfaces);
+ }
+ long totalRxPackets = 0;
+ if (tunIfaceTotal.rxPackets > 0) {
+ final long rxPacketsAcrossUnderlyingIfaces =
+ multiplySafeByRational(underlyingIfacesTotal.rxPackets,
+ rxPackets[i], tunIfaceTotal.rxPackets);
+ totalRxPackets = Math.min(rxPackets[i], rxPacketsAcrossUnderlyingIfaces);
+ }
+ long totalTxBytes = 0;
+ if (tunIfaceTotal.txBytes > 0) {
+ final long txBytesAcrossUnderlyingIfaces =
+ multiplySafeByRational(underlyingIfacesTotal.txBytes,
+ txBytes[i], tunIfaceTotal.txBytes);
+ totalTxBytes = Math.min(txBytes[i], txBytesAcrossUnderlyingIfaces);
+ }
+ long totalTxPackets = 0;
+ if (tunIfaceTotal.txPackets > 0) {
+ final long txPacketsAcrossUnderlyingIfaces =
+ multiplySafeByRational(underlyingIfacesTotal.txPackets,
+ txPackets[i], tunIfaceTotal.txPackets);
+ totalTxPackets = Math.min(txPackets[i], txPacketsAcrossUnderlyingIfaces);
+ }
+ long totalOperations = 0;
+ if (tunIfaceTotal.operations > 0) {
+ final long operationsAcrossUnderlyingIfaces =
+ multiplySafeByRational(underlyingIfacesTotal.operations,
+ operations[i], tunIfaceTotal.operations);
+ totalOperations = Math.min(operations[i], operationsAcrossUnderlyingIfaces);
+ }
+ // In a second pass, distribute these values across interfaces in the proportion that
+ // each interface represents of the total traffic of the underlying interfaces.
+ for (int j = 0; j < underlyingIfaces.size(); j++) {
+ tmpEntry.iface = underlyingIfaces.get(j);
+ tmpEntry.rxBytes = 0;
+ // Reset 'set' to correct value since it gets updated when adding debug info below.
+ tmpEntry.set = set[i];
+ if (underlyingIfacesTotal.rxBytes > 0) {
+ tmpEntry.rxBytes =
+ multiplySafeByRational(totalRxBytes,
+ perInterfaceTotal[j].rxBytes,
+ underlyingIfacesTotal.rxBytes);
+ }
+ tmpEntry.rxPackets = 0;
+ if (underlyingIfacesTotal.rxPackets > 0) {
+ tmpEntry.rxPackets =
+ multiplySafeByRational(totalRxPackets,
+ perInterfaceTotal[j].rxPackets,
+ underlyingIfacesTotal.rxPackets);
+ }
+ tmpEntry.txBytes = 0;
+ if (underlyingIfacesTotal.txBytes > 0) {
+ tmpEntry.txBytes =
+ multiplySafeByRational(totalTxBytes,
+ perInterfaceTotal[j].txBytes,
+ underlyingIfacesTotal.txBytes);
+ }
+ tmpEntry.txPackets = 0;
+ if (underlyingIfacesTotal.txPackets > 0) {
+ tmpEntry.txPackets =
+ multiplySafeByRational(totalTxPackets,
+ perInterfaceTotal[j].txPackets,
+ underlyingIfacesTotal.txPackets);
+ }
+ tmpEntry.operations = 0;
+ if (underlyingIfacesTotal.operations > 0) {
+ tmpEntry.operations =
+ multiplySafeByRational(totalOperations,
+ perInterfaceTotal[j].operations,
+ underlyingIfacesTotal.operations);
+ }
+ // tmpEntry now contains the migrated data of the i-th entry for the j-th underlying
+ // interface. Add that data usage to this object.
+ combineValues(tmpEntry);
+ if (tag[i] == TAG_NONE) {
+ // Add the migrated data to moved so it is deducted from the VPN app later.
+ moved[j].add(tmpEntry);
+ // Add debug info
+ tmpEntry.set = SET_DBG_VPN_IN;
+ combineValues(tmpEntry);
+ }
+ }
+ }
+ return moved;
+ }
+
+ private void deductTrafficFromVpnApp(
+ int tunUid,
+ @NonNull List<String> underlyingIfaces,
+ @NonNull Entry[] moved) {
+ if (tunUid == Process.SYSTEM_UID) {
+ // No traffic recorded on a per-UID basis for in-kernel VPN/VCNs over underlying
+ // networks; thus no traffic to deduct.
+ return;
+ }
+
+ for (int i = 0; i < underlyingIfaces.size(); i++) {
+ moved[i].uid = tunUid;
+ // Add debug info
+ moved[i].set = SET_DBG_VPN_OUT;
+ moved[i].tag = TAG_NONE;
+ moved[i].iface = underlyingIfaces.get(i);
+ moved[i].metered = METERED_ALL;
+ moved[i].roaming = ROAMING_ALL;
+ moved[i].defaultNetwork = DEFAULT_NETWORK_ALL;
+ combineValues(moved[i]);
+
+ // Caveat: if the vpn software uses tag, the total tagged traffic may be greater than
+ // the TAG_NONE traffic.
+ //
+ // Relies on the fact that the underlying traffic only has state ROAMING_NO and
+ // METERED_NO, which should be the case as it comes directly from the /proc file.
+ // We only blend in the roaming data after applying these adjustments, by checking the
+ // NetworkIdentity of the underlying iface.
+ final int idxVpnBackground = findIndex(underlyingIfaces.get(i), tunUid, SET_DEFAULT,
+ TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO);
+ if (idxVpnBackground != -1) {
+ // Note - tunSubtract also updates moved[i]; whatever traffic that's left is removed
+ // from foreground usage.
+ tunSubtract(idxVpnBackground, this, moved[i]);
+ }
+
+ final int idxVpnForeground = findIndex(underlyingIfaces.get(i), tunUid, SET_FOREGROUND,
+ TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO);
+ if (idxVpnForeground != -1) {
+ tunSubtract(idxVpnForeground, this, moved[i]);
+ }
+ }
+ }
+
+ private static void tunSubtract(int i, @NonNull NetworkStats left, @NonNull Entry right) {
+ long rxBytes = Math.min(left.rxBytes[i], right.rxBytes);
+ left.rxBytes[i] -= rxBytes;
+ right.rxBytes -= rxBytes;
+
+ long rxPackets = Math.min(left.rxPackets[i], right.rxPackets);
+ left.rxPackets[i] -= rxPackets;
+ right.rxPackets -= rxPackets;
+
+ long txBytes = Math.min(left.txBytes[i], right.txBytes);
+ left.txBytes[i] -= txBytes;
+ right.txBytes -= txBytes;
+
+ long txPackets = Math.min(left.txPackets[i], right.txPackets);
+ left.txPackets[i] -= txPackets;
+ right.txPackets -= txPackets;
+ }
+}
diff --git a/framework-t/src/android/net/NetworkStatsAccess.java b/framework-t/src/android/net/NetworkStatsAccess.java
new file mode 100644
index 0000000..b64fbdb
--- /dev/null
+++ b/framework-t/src/android/net/NetworkStatsAccess.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.net.NetworkStats.UID_ALL;
+import static android.net.TrafficStats.UID_REMOVED;
+import static android.net.TrafficStats.UID_TETHERING;
+
+import android.Manifest;
+import android.annotation.IntDef;
+import android.app.AppOpsManager;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.Process;
+import android.os.UserHandle;
+import android.telephony.TelephonyManager;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Utility methods for controlling access to network stats APIs.
+ *
+ * @hide
+ */
+public final class NetworkStatsAccess {
+ private NetworkStatsAccess() {}
+
+ /**
+ * Represents an access level for the network usage history and statistics APIs.
+ *
+ * <p>Access levels are in increasing order; that is, it is reasonable to check access by
+ * verifying that the caller's access level is at least the minimum required level.
+ */
+ @IntDef({
+ Level.DEFAULT,
+ Level.USER,
+ Level.DEVICESUMMARY,
+ Level.DEVICE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Level {
+ /**
+ * Default, unprivileged access level.
+ *
+ * <p>Can only access usage for one's own UID.
+ *
+ * <p>Every app will have at least this access level.
+ */
+ int DEFAULT = 0;
+
+ /**
+ * Access level for apps which can access usage for any app running in the same user.
+ *
+ * <p>Granted to:
+ * <ul>
+ * <li>Profile owners.
+ * </ul>
+ */
+ int USER = 1;
+
+ /**
+ * Access level for apps which can access usage summary of device. Device summary includes
+ * usage by apps running in any profiles/users, however this access level does not
+ * allow querying usage of individual apps running in other profiles/users.
+ *
+ * <p>Granted to:
+ * <ul>
+ * <li>Apps with the PACKAGE_USAGE_STATS permission granted. Note that this is an AppOps bit
+ * so it is not necessarily sufficient to declare this in the manifest.
+ * <li>Apps with the (signature/privileged) READ_NETWORK_USAGE_HISTORY permission.
+ * </ul>
+ */
+ int DEVICESUMMARY = 2;
+
+ /**
+ * Access level for apps which can access usage for any app on the device, including apps
+ * running on other users/profiles.
+ *
+ * <p>Granted to:
+ * <ul>
+ * <li>Device owners.
+ * <li>Carrier-privileged applications.
+ * <li>The system UID.
+ * </ul>
+ */
+ int DEVICE = 3;
+ }
+
+ /** Returns the {@link NetworkStatsAccess.Level} for the given caller. */
+ public static @NetworkStatsAccess.Level int checkAccessLevel(
+ Context context, int callingPid, int callingUid, String callingPackage) {
+ final DevicePolicyManager mDpm = context.getSystemService(DevicePolicyManager.class);
+ final TelephonyManager tm = (TelephonyManager)
+ context.getSystemService(Context.TELEPHONY_SERVICE);
+ boolean hasCarrierPrivileges;
+ final long token = Binder.clearCallingIdentity();
+ try {
+ hasCarrierPrivileges = tm != null
+ && tm.checkCarrierPrivilegesForPackageAnyPhone(callingPackage)
+ == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+
+ final boolean isDeviceOwner = mDpm != null && mDpm.isDeviceOwnerApp(callingPackage);
+ final int appId = UserHandle.getAppId(callingUid);
+
+ final boolean isNetworkStack = context.checkPermission(
+ android.Manifest.permission.NETWORK_STACK, callingPid, callingUid)
+ == PERMISSION_GRANTED;
+
+ if (hasCarrierPrivileges || isDeviceOwner
+ || appId == Process.SYSTEM_UID || isNetworkStack) {
+ // Carrier-privileged apps and device owners, and the system (including the
+ // network stack) can access data usage for all apps on the device.
+ return NetworkStatsAccess.Level.DEVICE;
+ }
+
+ boolean hasAppOpsPermission = hasAppOpsPermission(context, callingUid, callingPackage);
+ if (hasAppOpsPermission || context.checkCallingOrSelfPermission(
+ READ_NETWORK_USAGE_HISTORY) == PackageManager.PERMISSION_GRANTED) {
+ return NetworkStatsAccess.Level.DEVICESUMMARY;
+ }
+
+ //TODO(b/169395065) Figure out if this flow makes sense in Device Owner mode.
+ boolean isProfileOwner = mDpm != null && (mDpm.isProfileOwnerApp(callingPackage)
+ || mDpm.isDeviceOwnerApp(callingPackage));
+ if (isProfileOwner) {
+ // Apps with the AppOps permission, profile owners, and apps with the privileged
+ // permission can access data usage for all apps in this user/profile.
+ return NetworkStatsAccess.Level.USER;
+ }
+
+ // Everyone else gets default access (only to their own UID).
+ return NetworkStatsAccess.Level.DEFAULT;
+ }
+
+ /**
+ * Returns whether the given caller should be able to access the given UID when the caller has
+ * the given {@link NetworkStatsAccess.Level}.
+ */
+ public static boolean isAccessibleToUser(int uid, int callerUid,
+ @NetworkStatsAccess.Level int accessLevel) {
+ final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
+ final int callerUserId = UserHandle.getUserHandleForUid(callerUid).getIdentifier();
+ switch (accessLevel) {
+ case NetworkStatsAccess.Level.DEVICE:
+ // Device-level access - can access usage for any uid.
+ return true;
+ case NetworkStatsAccess.Level.DEVICESUMMARY:
+ // Can access usage for any app running in the same user, along
+ // with some special uids (system, removed, or tethering) and
+ // anonymized uids
+ return uid == android.os.Process.SYSTEM_UID || uid == UID_REMOVED
+ || uid == UID_TETHERING || uid == UID_ALL
+ || userId == callerUserId;
+ case NetworkStatsAccess.Level.USER:
+ // User-level access - can access usage for any app running in the same user, along
+ // with some special uids (system, removed, or tethering).
+ return uid == android.os.Process.SYSTEM_UID || uid == UID_REMOVED
+ || uid == UID_TETHERING
+ || userId == callerUserId;
+ case NetworkStatsAccess.Level.DEFAULT:
+ default:
+ // Default access level - can only access one's own usage.
+ return uid == callerUid;
+ }
+ }
+
+ private static boolean hasAppOpsPermission(
+ Context context, int callingUid, String callingPackage) {
+ if (callingPackage != null) {
+ AppOpsManager appOps = (AppOpsManager) context.getSystemService(
+ Context.APP_OPS_SERVICE);
+
+ final int mode = appOps.noteOp(AppOpsManager.OPSTR_GET_USAGE_STATS,
+ callingUid, callingPackage, null /* attributionTag */, null /* message */);
+ if (mode == AppOpsManager.MODE_DEFAULT) {
+ // The default behavior here is to check if PackageManager has given the app
+ // permission.
+ final int permissionCheck = context.checkCallingPermission(
+ Manifest.permission.PACKAGE_USAGE_STATS);
+ return permissionCheck == PackageManager.PERMISSION_GRANTED;
+ }
+ return (mode == AppOpsManager.MODE_ALLOWED);
+ }
+ return false;
+ }
+}
diff --git a/framework-t/src/android/net/NetworkStatsCollection.java b/framework-t/src/android/net/NetworkStatsCollection.java
new file mode 100644
index 0000000..6a1d2dd
--- /dev/null
+++ b/framework-t/src/android/net/NetworkStatsCollection.java
@@ -0,0 +1,991 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
+import static android.net.NetworkStats.DEFAULT_NETWORK_YES;
+import static android.net.NetworkStats.IFACE_ALL;
+import static android.net.NetworkStats.METERED_NO;
+import static android.net.NetworkStats.METERED_YES;
+import static android.net.NetworkStats.ROAMING_NO;
+import static android.net.NetworkStats.ROAMING_YES;
+import static android.net.NetworkStats.SET_ALL;
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkStats.UID_ALL;
+import static android.net.TrafficStats.UID_REMOVED;
+import static android.text.format.DateUtils.WEEK_IN_MILLIS;
+
+import static com.android.net.module.util.NetworkStatsUtils.multiplySafeByRational;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.net.NetworkStats.State;
+import android.net.NetworkStatsHistory.Entry;
+import android.os.Binder;
+import android.service.NetworkStatsCollectionKeyProto;
+import android.service.NetworkStatsCollectionProto;
+import android.service.NetworkStatsCollectionStatsProto;
+import android.telephony.SubscriptionPlan;
+import android.text.format.DateUtils;
+import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+import android.util.Range;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FileRotator;
+import com.android.net.module.util.CollectionUtils;
+import com.android.net.module.util.NetworkStatsUtils;
+
+import libcore.io.IoUtils;
+
+import java.io.BufferedInputStream;
+import java.io.DataInput;
+import java.io.DataInputStream;
+import java.io.DataOutput;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.net.ProtocolException;
+import java.time.ZonedDateTime;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Collection of {@link NetworkStatsHistory}, stored based on combined key of
+ * {@link NetworkIdentitySet}, UID, set, and tag. Knows how to persist itself.
+ *
+ * @hide
+ */
+@SystemApi(client = MODULE_LIBRARIES)
+public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.Writer {
+ private static final String TAG = NetworkStatsCollection.class.getSimpleName();
+ /** File header magic number: "ANET" */
+ private static final int FILE_MAGIC = 0x414E4554;
+
+ private static final int VERSION_NETWORK_INIT = 1;
+
+ private static final int VERSION_UID_INIT = 1;
+ private static final int VERSION_UID_WITH_IDENT = 2;
+ private static final int VERSION_UID_WITH_TAG = 3;
+ private static final int VERSION_UID_WITH_SET = 4;
+
+ private static final int VERSION_UNIFIED_INIT = 16;
+
+ private ArrayMap<Key, NetworkStatsHistory> mStats = new ArrayMap<>();
+
+ private final long mBucketDurationMillis;
+
+ private long mStartMillis;
+ private long mEndMillis;
+ private long mTotalBytes;
+ private boolean mDirty;
+
+ /**
+ * Construct a {@link NetworkStatsCollection} object.
+ *
+ * @param bucketDuration duration of the buckets in this object, in milliseconds.
+ * @hide
+ */
+ public NetworkStatsCollection(long bucketDurationMillis) {
+ mBucketDurationMillis = bucketDurationMillis;
+ reset();
+ }
+
+ /** @hide */
+ public void clear() {
+ reset();
+ }
+
+ /** @hide */
+ public void reset() {
+ mStats.clear();
+ mStartMillis = Long.MAX_VALUE;
+ mEndMillis = Long.MIN_VALUE;
+ mTotalBytes = 0;
+ mDirty = false;
+ }
+
+ /** @hide */
+ public long getStartMillis() {
+ return mStartMillis;
+ }
+
+ /**
+ * Return first atomic bucket in this collection, which is more conservative
+ * than {@link #mStartMillis}.
+ * @hide
+ */
+ public long getFirstAtomicBucketMillis() {
+ if (mStartMillis == Long.MAX_VALUE) {
+ return Long.MAX_VALUE;
+ } else {
+ return mStartMillis + mBucketDurationMillis;
+ }
+ }
+
+ /** @hide */
+ public long getEndMillis() {
+ return mEndMillis;
+ }
+
+ /** @hide */
+ public long getTotalBytes() {
+ return mTotalBytes;
+ }
+
+ /** @hide */
+ public boolean isDirty() {
+ return mDirty;
+ }
+
+ /** @hide */
+ public void clearDirty() {
+ mDirty = false;
+ }
+
+ /** @hide */
+ public boolean isEmpty() {
+ return mStartMillis == Long.MAX_VALUE && mEndMillis == Long.MIN_VALUE;
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public long roundUp(long time) {
+ if (time == Long.MIN_VALUE || time == Long.MAX_VALUE
+ || time == SubscriptionPlan.TIME_UNKNOWN) {
+ return time;
+ } else {
+ final long mod = time % mBucketDurationMillis;
+ if (mod > 0) {
+ time -= mod;
+ time += mBucketDurationMillis;
+ }
+ return time;
+ }
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public long roundDown(long time) {
+ if (time == Long.MIN_VALUE || time == Long.MAX_VALUE
+ || time == SubscriptionPlan.TIME_UNKNOWN) {
+ return time;
+ } else {
+ final long mod = time % mBucketDurationMillis;
+ if (mod > 0) {
+ time -= mod;
+ }
+ return time;
+ }
+ }
+
+ /** @hide */
+ public int[] getRelevantUids(@NetworkStatsAccess.Level int accessLevel) {
+ return getRelevantUids(accessLevel, Binder.getCallingUid());
+ }
+
+ /** @hide */
+ public int[] getRelevantUids(@NetworkStatsAccess.Level int accessLevel,
+ final int callerUid) {
+ final ArrayList<Integer> uids = new ArrayList<>();
+ for (int i = 0; i < mStats.size(); i++) {
+ final Key key = mStats.keyAt(i);
+ if (NetworkStatsAccess.isAccessibleToUser(key.uid, callerUid, accessLevel)) {
+ int j = Collections.binarySearch(uids, new Integer(key.uid));
+
+ if (j < 0) {
+ j = ~j;
+ uids.add(j, key.uid);
+ }
+ }
+ }
+ return CollectionUtils.toIntArray(uids);
+ }
+
+ /**
+ * Combine all {@link NetworkStatsHistory} in this collection which match
+ * the requested parameters.
+ * @hide
+ */
+ public NetworkStatsHistory getHistory(NetworkTemplate template, SubscriptionPlan augmentPlan,
+ int uid, int set, int tag, int fields, long start, long end,
+ @NetworkStatsAccess.Level int accessLevel, int callerUid) {
+ if (!NetworkStatsAccess.isAccessibleToUser(uid, callerUid, accessLevel)) {
+ throw new SecurityException("Network stats history of uid " + uid
+ + " is forbidden for caller " + callerUid);
+ }
+
+ // 180 days of history should be enough for anyone; if we end up needing
+ // more, we'll dynamically grow the history object.
+ final int bucketEstimate = (int) NetworkStatsUtils.constrain(
+ ((end - start) / mBucketDurationMillis), 0,
+ (180 * DateUtils.DAY_IN_MILLIS) / mBucketDurationMillis);
+ final NetworkStatsHistory combined = new NetworkStatsHistory(
+ mBucketDurationMillis, bucketEstimate, fields);
+
+ // shortcut when we know stats will be empty
+ if (start == end) return combined;
+
+ // Figure out the window of time that we should be augmenting (if any)
+ long augmentStart = SubscriptionPlan.TIME_UNKNOWN;
+ long augmentEnd = (augmentPlan != null) ? augmentPlan.getDataUsageTime()
+ : SubscriptionPlan.TIME_UNKNOWN;
+ // And if augmenting, we might need to collect more data to adjust with
+ long collectStart = start;
+ long collectEnd = end;
+
+ if (augmentEnd != SubscriptionPlan.TIME_UNKNOWN) {
+ final Iterator<Range<ZonedDateTime>> it = augmentPlan.cycleIterator();
+ while (it.hasNext()) {
+ final Range<ZonedDateTime> cycle = it.next();
+ final long cycleStart = cycle.getLower().toInstant().toEpochMilli();
+ final long cycleEnd = cycle.getUpper().toInstant().toEpochMilli();
+ if (cycleStart <= augmentEnd && augmentEnd < cycleEnd) {
+ augmentStart = cycleStart;
+ collectStart = Long.min(collectStart, augmentStart);
+ collectEnd = Long.max(collectEnd, augmentEnd);
+ break;
+ }
+ }
+ }
+
+ if (augmentStart != SubscriptionPlan.TIME_UNKNOWN) {
+ // Shrink augmentation window so we don't risk undercounting.
+ augmentStart = roundUp(augmentStart);
+ augmentEnd = roundDown(augmentEnd);
+ // Grow collection window so we get all the stats needed.
+ collectStart = roundDown(collectStart);
+ collectEnd = roundUp(collectEnd);
+ }
+
+ for (int i = 0; i < mStats.size(); i++) {
+ final Key key = mStats.keyAt(i);
+ if (key.uid == uid && NetworkStats.setMatches(set, key.set) && key.tag == tag
+ && templateMatches(template, key.ident)) {
+ final NetworkStatsHistory value = mStats.valueAt(i);
+ combined.recordHistory(value, collectStart, collectEnd);
+ }
+ }
+
+ if (augmentStart != SubscriptionPlan.TIME_UNKNOWN) {
+ final NetworkStatsHistory.Entry entry = combined.getValues(
+ augmentStart, augmentEnd, null);
+
+ // If we don't have any recorded data for this time period, give
+ // ourselves something to scale with.
+ if (entry.rxBytes == 0 || entry.txBytes == 0) {
+ combined.recordData(augmentStart, augmentEnd,
+ new NetworkStats.Entry(1, 0, 1, 0, 0));
+ combined.getValues(augmentStart, augmentEnd, entry);
+ }
+
+ final long rawBytes = (entry.rxBytes + entry.txBytes) == 0 ? 1 :
+ (entry.rxBytes + entry.txBytes);
+ final long rawRxBytes = entry.rxBytes == 0 ? 1 : entry.rxBytes;
+ final long rawTxBytes = entry.txBytes == 0 ? 1 : entry.txBytes;
+ final long targetBytes = augmentPlan.getDataUsageBytes();
+
+ final long targetRxBytes = multiplySafeByRational(targetBytes, rawRxBytes, rawBytes);
+ final long targetTxBytes = multiplySafeByRational(targetBytes, rawTxBytes, rawBytes);
+
+
+ // Scale all matching buckets to reach anchor target
+ final long beforeTotal = combined.getTotalBytes();
+ for (int i = 0; i < combined.size(); i++) {
+ combined.getValues(i, entry);
+ if (entry.bucketStart >= augmentStart
+ && entry.bucketStart + entry.bucketDuration <= augmentEnd) {
+ entry.rxBytes = multiplySafeByRational(
+ targetRxBytes, entry.rxBytes, rawRxBytes);
+ entry.txBytes = multiplySafeByRational(
+ targetTxBytes, entry.txBytes, rawTxBytes);
+ // We purposefully clear out packet counters to indicate
+ // that this data has been augmented.
+ entry.rxPackets = 0;
+ entry.txPackets = 0;
+ combined.setValues(i, entry);
+ }
+ }
+
+ final long deltaTotal = combined.getTotalBytes() - beforeTotal;
+ if (deltaTotal != 0) {
+ Log.d(TAG, "Augmented network usage by " + deltaTotal + " bytes");
+ }
+
+ // Finally we can slice data as originally requested
+ final NetworkStatsHistory sliced = new NetworkStatsHistory(
+ mBucketDurationMillis, bucketEstimate, fields);
+ sliced.recordHistory(combined, start, end);
+ return sliced;
+ } else {
+ return combined;
+ }
+ }
+
+ /**
+ * Summarize all {@link NetworkStatsHistory} in this collection which match
+ * the requested parameters across the requested range.
+ *
+ * @param template - a predicate for filtering netstats.
+ * @param start - start of the range, timestamp in milliseconds since the epoch.
+ * @param end - end of the range, timestamp in milliseconds since the epoch.
+ * @param accessLevel - caller access level.
+ * @param callerUid - caller UID.
+ * @hide
+ */
+ public NetworkStats getSummary(NetworkTemplate template, long start, long end,
+ @NetworkStatsAccess.Level int accessLevel, int callerUid) {
+ final long now = System.currentTimeMillis();
+
+ final NetworkStats stats = new NetworkStats(end - start, 24);
+
+ // shortcut when we know stats will be empty
+ if (start == end) return stats;
+
+ final NetworkStats.Entry entry = new NetworkStats.Entry();
+ NetworkStatsHistory.Entry historyEntry = null;
+
+ for (int i = 0; i < mStats.size(); i++) {
+ final Key key = mStats.keyAt(i);
+ if (templateMatches(template, key.ident)
+ && NetworkStatsAccess.isAccessibleToUser(key.uid, callerUid, accessLevel)
+ && key.set < NetworkStats.SET_DEBUG_START) {
+ final NetworkStatsHistory value = mStats.valueAt(i);
+ historyEntry = value.getValues(start, end, now, historyEntry);
+
+ entry.iface = IFACE_ALL;
+ entry.uid = key.uid;
+ entry.set = key.set;
+ entry.tag = key.tag;
+ entry.defaultNetwork = key.ident.areAllMembersOnDefaultNetwork()
+ ? DEFAULT_NETWORK_YES : DEFAULT_NETWORK_NO;
+ entry.metered = key.ident.isAnyMemberMetered() ? METERED_YES : METERED_NO;
+ entry.roaming = key.ident.isAnyMemberRoaming() ? ROAMING_YES : ROAMING_NO;
+ entry.rxBytes = historyEntry.rxBytes;
+ entry.rxPackets = historyEntry.rxPackets;
+ entry.txBytes = historyEntry.txBytes;
+ entry.txPackets = historyEntry.txPackets;
+ entry.operations = historyEntry.operations;
+
+ if (!entry.isEmpty()) {
+ stats.combineValues(entry);
+ }
+ }
+ }
+
+ return stats;
+ }
+
+ /**
+ * Record given {@link android.net.NetworkStats.Entry} into this collection.
+ * @hide
+ */
+ public void recordData(NetworkIdentitySet ident, int uid, int set, int tag, long start,
+ long end, NetworkStats.Entry entry) {
+ final NetworkStatsHistory history = findOrCreateHistory(ident, uid, set, tag);
+ history.recordData(start, end, entry);
+ noteRecordedHistory(history.getStart(), history.getEnd(), entry.rxBytes + entry.txBytes);
+ }
+
+ /**
+ * Record given {@link NetworkStatsHistory} into this collection.
+ *
+ * @hide
+ */
+ public void recordHistory(@NonNull Key key, @NonNull NetworkStatsHistory history) {
+ Objects.requireNonNull(key);
+ Objects.requireNonNull(history);
+ if (history.size() == 0) return;
+ noteRecordedHistory(history.getStart(), history.getEnd(), history.getTotalBytes());
+
+ NetworkStatsHistory target = mStats.get(key);
+ if (target == null) {
+ target = new NetworkStatsHistory(history.getBucketDuration());
+ mStats.put(key, target);
+ }
+ target.recordEntireHistory(history);
+ }
+
+ /**
+ * Record all {@link NetworkStatsHistory} contained in the given collection
+ * into this collection.
+ *
+ * @hide
+ */
+ public void recordCollection(@NonNull NetworkStatsCollection another) {
+ Objects.requireNonNull(another);
+ for (int i = 0; i < another.mStats.size(); i++) {
+ final Key key = another.mStats.keyAt(i);
+ final NetworkStatsHistory value = another.mStats.valueAt(i);
+ recordHistory(key, value);
+ }
+ }
+
+ private NetworkStatsHistory findOrCreateHistory(
+ NetworkIdentitySet ident, int uid, int set, int tag) {
+ final Key key = new Key(ident, uid, set, tag);
+ final NetworkStatsHistory existing = mStats.get(key);
+
+ // update when no existing, or when bucket duration changed
+ NetworkStatsHistory updated = null;
+ if (existing == null) {
+ updated = new NetworkStatsHistory(mBucketDurationMillis, 10);
+ } else if (existing.getBucketDuration() != mBucketDurationMillis) {
+ updated = new NetworkStatsHistory(existing, mBucketDurationMillis);
+ }
+
+ if (updated != null) {
+ mStats.put(key, updated);
+ return updated;
+ } else {
+ return existing;
+ }
+ }
+
+ /** @hide */
+ @Override
+ public void read(InputStream in) throws IOException {
+ read((DataInput) new DataInputStream(in));
+ }
+
+ private void read(DataInput in) throws IOException {
+ // verify file magic header intact
+ final int magic = in.readInt();
+ if (magic != FILE_MAGIC) {
+ throw new ProtocolException("unexpected magic: " + magic);
+ }
+
+ final int version = in.readInt();
+ switch (version) {
+ case VERSION_UNIFIED_INIT: {
+ // uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory))
+ final int identSize = in.readInt();
+ for (int i = 0; i < identSize; i++) {
+ final NetworkIdentitySet ident = new NetworkIdentitySet(in);
+
+ final int size = in.readInt();
+ for (int j = 0; j < size; j++) {
+ final int uid = in.readInt();
+ final int set = in.readInt();
+ final int tag = in.readInt();
+
+ final Key key = new Key(ident, uid, set, tag);
+ final NetworkStatsHistory history = new NetworkStatsHistory(in);
+ recordHistory(key, history);
+ }
+ }
+ break;
+ }
+ default: {
+ throw new ProtocolException("unexpected version: " + version);
+ }
+ }
+ }
+
+ /** @hide */
+ @Override
+ public void write(OutputStream out) throws IOException {
+ write((DataOutput) new DataOutputStream(out));
+ out.flush();
+ }
+
+ private void write(DataOutput out) throws IOException {
+ // cluster key lists grouped by ident
+ final HashMap<NetworkIdentitySet, ArrayList<Key>> keysByIdent = new HashMap<>();
+ for (Key key : mStats.keySet()) {
+ ArrayList<Key> keys = keysByIdent.get(key.ident);
+ if (keys == null) {
+ keys = new ArrayList<>();
+ keysByIdent.put(key.ident, keys);
+ }
+ keys.add(key);
+ }
+
+ out.writeInt(FILE_MAGIC);
+ out.writeInt(VERSION_UNIFIED_INIT);
+
+ out.writeInt(keysByIdent.size());
+ for (NetworkIdentitySet ident : keysByIdent.keySet()) {
+ final ArrayList<Key> keys = keysByIdent.get(ident);
+ ident.writeToStream(out);
+
+ out.writeInt(keys.size());
+ for (Key key : keys) {
+ final NetworkStatsHistory history = mStats.get(key);
+ out.writeInt(key.uid);
+ out.writeInt(key.set);
+ out.writeInt(key.tag);
+ history.writeToStream(out);
+ }
+ }
+ }
+
+ /**
+ * Read legacy network summary statistics file format into the collection,
+ * See {@code NetworkStatsService#maybeUpgradeLegacyStatsLocked}.
+ *
+ * @deprecated
+ * @hide
+ */
+ @Deprecated
+ public void readLegacyNetwork(File file) throws IOException {
+ final AtomicFile inputFile = new AtomicFile(file);
+
+ DataInputStream in = null;
+ try {
+ in = new DataInputStream(new BufferedInputStream(inputFile.openRead()));
+
+ // verify file magic header intact
+ final int magic = in.readInt();
+ if (magic != FILE_MAGIC) {
+ throw new ProtocolException("unexpected magic: " + magic);
+ }
+
+ final int version = in.readInt();
+ switch (version) {
+ case VERSION_NETWORK_INIT: {
+ // network := size *(NetworkIdentitySet NetworkStatsHistory)
+ final int size = in.readInt();
+ for (int i = 0; i < size; i++) {
+ final NetworkIdentitySet ident = new NetworkIdentitySet(in);
+ final NetworkStatsHistory history = new NetworkStatsHistory(in);
+
+ final Key key = new Key(ident, UID_ALL, SET_ALL, TAG_NONE);
+ recordHistory(key, history);
+ }
+ break;
+ }
+ default: {
+ throw new ProtocolException("unexpected version: " + version);
+ }
+ }
+ } catch (FileNotFoundException e) {
+ // missing stats is okay, probably first boot
+ } finally {
+ IoUtils.closeQuietly(in);
+ }
+ }
+
+ /**
+ * Read legacy Uid statistics file format into the collection,
+ * See {@code NetworkStatsService#maybeUpgradeLegacyStatsLocked}.
+ *
+ * @deprecated
+ * @hide
+ */
+ @Deprecated
+ public void readLegacyUid(File file, boolean onlyTags) throws IOException {
+ final AtomicFile inputFile = new AtomicFile(file);
+
+ DataInputStream in = null;
+ try {
+ in = new DataInputStream(new BufferedInputStream(inputFile.openRead()));
+
+ // verify file magic header intact
+ final int magic = in.readInt();
+ if (magic != FILE_MAGIC) {
+ throw new ProtocolException("unexpected magic: " + magic);
+ }
+
+ final int version = in.readInt();
+ switch (version) {
+ case VERSION_UID_INIT: {
+ // uid := size *(UID NetworkStatsHistory)
+
+ // drop this data version, since we don't have a good
+ // mapping into NetworkIdentitySet.
+ break;
+ }
+ case VERSION_UID_WITH_IDENT: {
+ // uid := size *(NetworkIdentitySet size *(UID NetworkStatsHistory))
+
+ // drop this data version, since this version only existed
+ // for a short time.
+ break;
+ }
+ case VERSION_UID_WITH_TAG:
+ case VERSION_UID_WITH_SET: {
+ // uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory))
+ final int identSize = in.readInt();
+ for (int i = 0; i < identSize; i++) {
+ final NetworkIdentitySet ident = new NetworkIdentitySet(in);
+
+ final int size = in.readInt();
+ for (int j = 0; j < size; j++) {
+ final int uid = in.readInt();
+ final int set = (version >= VERSION_UID_WITH_SET) ? in.readInt()
+ : SET_DEFAULT;
+ final int tag = in.readInt();
+
+ final Key key = new Key(ident, uid, set, tag);
+ final NetworkStatsHistory history = new NetworkStatsHistory(in);
+
+ if ((tag == TAG_NONE) != onlyTags) {
+ recordHistory(key, history);
+ }
+ }
+ }
+ break;
+ }
+ default: {
+ throw new ProtocolException("unexpected version: " + version);
+ }
+ }
+ } catch (FileNotFoundException e) {
+ // missing stats is okay, probably first boot
+ } finally {
+ IoUtils.closeQuietly(in);
+ }
+ }
+
+ /**
+ * Remove any {@link NetworkStatsHistory} attributed to the requested UID,
+ * moving any {@link NetworkStats#TAG_NONE} series to
+ * {@link TrafficStats#UID_REMOVED}.
+ * @hide
+ */
+ public void removeUids(int[] uids) {
+ final ArrayList<Key> knownKeys = new ArrayList<>();
+ knownKeys.addAll(mStats.keySet());
+
+ // migrate all UID stats into special "removed" bucket
+ for (Key key : knownKeys) {
+ if (CollectionUtils.contains(uids, key.uid)) {
+ // only migrate combined TAG_NONE history
+ if (key.tag == TAG_NONE) {
+ final NetworkStatsHistory uidHistory = mStats.get(key);
+ final NetworkStatsHistory removedHistory = findOrCreateHistory(
+ key.ident, UID_REMOVED, SET_DEFAULT, TAG_NONE);
+ removedHistory.recordEntireHistory(uidHistory);
+ }
+ mStats.remove(key);
+ mDirty = true;
+ }
+ }
+ }
+
+ /**
+ * Remove histories which contains or is before the cutoff timestamp.
+ * @hide
+ */
+ public void removeHistoryBefore(long cutoffMillis) {
+ final ArrayList<Key> knownKeys = new ArrayList<>();
+ knownKeys.addAll(mStats.keySet());
+
+ for (Key key : knownKeys) {
+ final NetworkStatsHistory history = mStats.get(key);
+ if (history.getStart() > cutoffMillis) continue;
+
+ history.removeBucketsStartingBefore(cutoffMillis);
+ if (history.size() == 0) {
+ mStats.remove(key);
+ }
+ mDirty = true;
+ }
+ }
+
+ private void noteRecordedHistory(long startMillis, long endMillis, long totalBytes) {
+ if (startMillis < mStartMillis) mStartMillis = startMillis;
+ if (endMillis > mEndMillis) mEndMillis = endMillis;
+ mTotalBytes += totalBytes;
+ mDirty = true;
+ }
+
+ private int estimateBuckets() {
+ return (int) (Math.min(mEndMillis - mStartMillis, WEEK_IN_MILLIS * 5)
+ / mBucketDurationMillis);
+ }
+
+ private ArrayList<Key> getSortedKeys() {
+ final ArrayList<Key> keys = new ArrayList<>();
+ keys.addAll(mStats.keySet());
+ Collections.sort(keys, (left, right) -> Key.compare(left, right));
+ return keys;
+ }
+
+ /** @hide */
+ public void dump(IndentingPrintWriter pw) {
+ for (Key key : getSortedKeys()) {
+ pw.print("ident="); pw.print(key.ident.toString());
+ pw.print(" uid="); pw.print(key.uid);
+ pw.print(" set="); pw.print(NetworkStats.setToString(key.set));
+ pw.print(" tag="); pw.println(NetworkStats.tagToString(key.tag));
+
+ final NetworkStatsHistory history = mStats.get(key);
+ pw.increaseIndent();
+ history.dump(pw, true);
+ pw.decreaseIndent();
+ }
+ }
+
+ /** @hide */
+ public void dumpDebug(ProtoOutputStream proto, long tag) {
+ final long start = proto.start(tag);
+
+ for (Key key : getSortedKeys()) {
+ final long startStats = proto.start(NetworkStatsCollectionProto.STATS);
+
+ // Key
+ final long startKey = proto.start(NetworkStatsCollectionStatsProto.KEY);
+ key.ident.dumpDebug(proto, NetworkStatsCollectionKeyProto.IDENTITY);
+ proto.write(NetworkStatsCollectionKeyProto.UID, key.uid);
+ proto.write(NetworkStatsCollectionKeyProto.SET, key.set);
+ proto.write(NetworkStatsCollectionKeyProto.TAG, key.tag);
+ proto.end(startKey);
+
+ // Value
+ final NetworkStatsHistory history = mStats.get(key);
+ history.dumpDebug(proto, NetworkStatsCollectionStatsProto.HISTORY);
+ proto.end(startStats);
+ }
+
+ proto.end(start);
+ }
+
+ /** @hide */
+ public void dumpCheckin(PrintWriter pw, long start, long end) {
+ dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateMobileWildcard(), "cell");
+ dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateWifiWildcard(), "wifi");
+ dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateEthernet(), "eth");
+ dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateBluetooth(), "bt");
+ }
+
+ /**
+ * Dump all contained stats that match requested parameters, but group
+ * together all matching {@link NetworkTemplate} under a single prefix.
+ */
+ private void dumpCheckin(PrintWriter pw, long start, long end, NetworkTemplate groupTemplate,
+ String groupPrefix) {
+ final ArrayMap<Key, NetworkStatsHistory> grouped = new ArrayMap<>();
+
+ // Walk through all history, grouping by matching network templates
+ for (int i = 0; i < mStats.size(); i++) {
+ final Key key = mStats.keyAt(i);
+ final NetworkStatsHistory value = mStats.valueAt(i);
+
+ if (!templateMatches(groupTemplate, key.ident)) continue;
+ if (key.set >= NetworkStats.SET_DEBUG_START) continue;
+
+ final Key groupKey = new Key(new NetworkIdentitySet(), key.uid, key.set, key.tag);
+ NetworkStatsHistory groupHistory = grouped.get(groupKey);
+ if (groupHistory == null) {
+ groupHistory = new NetworkStatsHistory(value.getBucketDuration());
+ grouped.put(groupKey, groupHistory);
+ }
+ groupHistory.recordHistory(value, start, end);
+ }
+
+ for (int i = 0; i < grouped.size(); i++) {
+ final Key key = grouped.keyAt(i);
+ final NetworkStatsHistory value = grouped.valueAt(i);
+
+ if (value.size() == 0) continue;
+
+ pw.print("c,");
+ pw.print(groupPrefix); pw.print(',');
+ pw.print(key.uid); pw.print(',');
+ pw.print(NetworkStats.setToCheckinString(key.set)); pw.print(',');
+ pw.print(key.tag);
+ pw.println();
+
+ value.dumpCheckin(pw);
+ }
+ }
+
+ /**
+ * Test if given {@link NetworkTemplate} matches any {@link NetworkIdentity}
+ * in the given {@link NetworkIdentitySet}.
+ */
+ private static boolean templateMatches(NetworkTemplate template, NetworkIdentitySet identSet) {
+ for (NetworkIdentity ident : identSet) {
+ if (template.matches(ident)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Get the all historical stats of the collection {@link NetworkStatsCollection}.
+ *
+ * @return All {@link NetworkStatsHistory} in this collection.
+ */
+ @NonNull
+ public Map<Key, NetworkStatsHistory> getEntries() {
+ return new ArrayMap(mStats);
+ }
+
+ /**
+ * Builder class for {@link NetworkStatsCollection}.
+ */
+ public static final class Builder {
+ private final long mBucketDurationMillis;
+ private final ArrayMap<Key, NetworkStatsHistory> mEntries = new ArrayMap<>();
+
+ /**
+ * Creates a new Builder with given bucket duration.
+ *
+ * @param bucketDuration Duration of the buckets of the object, in milliseconds.
+ */
+ public Builder(long bucketDurationMillis) {
+ mBucketDurationMillis = bucketDurationMillis;
+ }
+
+ /**
+ * Add association of the history with the specified key in this map.
+ *
+ * @param key The object used to identify a network, see {@link Key}.
+ * If history already exists for this key, then the passed-in history is appended
+ * to the previously-passed in history. The caller must ensure that the history
+ * passed-in timestamps are greater than all previously-passed-in timestamps.
+ * @param history {@link NetworkStatsHistory} instance associated to the given {@link Key}.
+ * @return The builder object.
+ */
+ @NonNull
+ public NetworkStatsCollection.Builder addEntry(@NonNull Key key,
+ @NonNull NetworkStatsHistory history) {
+ Objects.requireNonNull(key);
+ Objects.requireNonNull(history);
+ final List<Entry> historyEntries = history.getEntries();
+ final NetworkStatsHistory existing = mEntries.get(key);
+
+ final int size = historyEntries.size() + ((existing != null) ? existing.size() : 0);
+ final NetworkStatsHistory.Builder historyBuilder =
+ new NetworkStatsHistory.Builder(mBucketDurationMillis, size);
+
+ // TODO: this simply appends the entries to any entries that were already present in
+ // the builder, which requires the caller to pass in entries in order. We might be
+ // able to do better with something like recordHistory.
+ if (existing != null) {
+ for (Entry entry : existing.getEntries()) {
+ historyBuilder.addEntry(entry);
+ }
+ }
+
+ for (Entry entry : historyEntries) {
+ historyBuilder.addEntry(entry);
+ }
+
+ mEntries.put(key, historyBuilder.build());
+ return this;
+ }
+
+ /**
+ * Builds the instance of the {@link NetworkStatsCollection}.
+ *
+ * @return the built instance of {@link NetworkStatsCollection}.
+ */
+ @NonNull
+ public NetworkStatsCollection build() {
+ final NetworkStatsCollection collection =
+ new NetworkStatsCollection(mBucketDurationMillis);
+ for (int i = 0; i < mEntries.size(); i++) {
+ collection.recordHistory(mEntries.keyAt(i), mEntries.valueAt(i));
+ }
+ return collection;
+ }
+ }
+
+ /**
+ * the identifier that associate with the {@link NetworkStatsHistory} object to identify
+ * a certain record in the {@link NetworkStatsCollection} object.
+ */
+ public static final class Key {
+ /** @hide */
+ public final NetworkIdentitySet ident;
+ /** @hide */
+ public final int uid;
+ /** @hide */
+ public final int set;
+ /** @hide */
+ public final int tag;
+
+ private final int mHashCode;
+
+ /**
+ * Construct a {@link Key} object.
+ *
+ * @param ident a Set of {@link NetworkIdentity} that associated with the record.
+ * @param uid Uid of the record.
+ * @param set Set of the record, see {@code NetworkStats#SET_*}.
+ * @param tag Tag of the record, see {@link TrafficStats#setThreadStatsTag(int)}.
+ */
+ public Key(@NonNull Set<NetworkIdentity> ident, int uid, @State int set, int tag) {
+ this(new NetworkIdentitySet(Objects.requireNonNull(ident)), uid, set, tag);
+ }
+
+ /** @hide */
+ public Key(@NonNull NetworkIdentitySet ident, int uid, int set, int tag) {
+ this.ident = Objects.requireNonNull(ident);
+ this.uid = uid;
+ this.set = set;
+ this.tag = tag;
+ mHashCode = Objects.hash(ident, uid, set, tag);
+ }
+
+ @Override
+ public int hashCode() {
+ return mHashCode;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (obj instanceof Key) {
+ final Key key = (Key) obj;
+ return uid == key.uid && set == key.set && tag == key.tag
+ && Objects.equals(ident, key.ident);
+ }
+ return false;
+ }
+
+ /** @hide */
+ public static int compare(@NonNull Key left, @NonNull Key right) {
+ Objects.requireNonNull(left);
+ Objects.requireNonNull(right);
+ int res = 0;
+ if (left.ident != null && right.ident != null) {
+ res = NetworkIdentitySet.compare(left.ident, right.ident);
+ }
+ if (res == 0) {
+ res = Integer.compare(left.uid, right.uid);
+ }
+ if (res == 0) {
+ res = Integer.compare(left.set, right.set);
+ }
+ if (res == 0) {
+ res = Integer.compare(left.tag, right.tag);
+ }
+ return res;
+ }
+ }
+}
diff --git a/framework-t/src/android/net/NetworkStatsHistory.aidl b/framework-t/src/android/net/NetworkStatsHistory.aidl
new file mode 100644
index 0000000..8b9069f
--- /dev/null
+++ b/framework-t/src/android/net/NetworkStatsHistory.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2011, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+parcelable NetworkStatsHistory;
diff --git a/framework-t/src/android/net/NetworkStatsHistory.java b/framework-t/src/android/net/NetworkStatsHistory.java
new file mode 100644
index 0000000..0ff9d96
--- /dev/null
+++ b/framework-t/src/android/net/NetworkStatsHistory.java
@@ -0,0 +1,1214 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+import static android.net.NetworkStats.IFACE_ALL;
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkStats.UID_ALL;
+import static android.net.NetworkStatsHistory.DataStreamUtils.readFullLongArray;
+import static android.net.NetworkStatsHistory.DataStreamUtils.readVarLongArray;
+import static android.net.NetworkStatsHistory.DataStreamUtils.writeVarLongArray;
+import static android.net.NetworkStatsHistory.Entry.UNKNOWN;
+import static android.net.NetworkStatsHistory.ParcelUtils.readLongArray;
+import static android.net.NetworkStatsHistory.ParcelUtils.writeLongArray;
+import static android.text.format.DateUtils.SECOND_IN_MILLIS;
+
+import static com.android.net.module.util.NetworkStatsUtils.multiplySafeByRational;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.service.NetworkStatsHistoryBucketProto;
+import android.service.NetworkStatsHistoryProto;
+import android.util.IndentingPrintWriter;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.net.module.util.CollectionUtils;
+import com.android.net.module.util.NetworkStatsUtils;
+
+import libcore.util.EmptyArray;
+
+import java.io.CharArrayWriter;
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.ProtocolException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Collection of historical network statistics, recorded into equally-sized
+ * "buckets" in time. Internally it stores data in {@code long} series for more
+ * efficient persistence.
+ * <p>
+ * Each bucket is defined by a {@link #bucketStart} timestamp, and lasts for
+ * {@link #bucketDuration}. Internally assumes that {@link #bucketStart} is
+ * sorted at all times.
+ *
+ * @hide
+ */
+@SystemApi(client = MODULE_LIBRARIES)
+public final class NetworkStatsHistory implements Parcelable {
+ private static final int VERSION_INIT = 1;
+ private static final int VERSION_ADD_PACKETS = 2;
+ private static final int VERSION_ADD_ACTIVE = 3;
+
+ /** @hide */
+ public static final int FIELD_ACTIVE_TIME = 0x01;
+ /** @hide */
+ public static final int FIELD_RX_BYTES = 0x02;
+ /** @hide */
+ public static final int FIELD_RX_PACKETS = 0x04;
+ /** @hide */
+ public static final int FIELD_TX_BYTES = 0x08;
+ /** @hide */
+ public static final int FIELD_TX_PACKETS = 0x10;
+ /** @hide */
+ public static final int FIELD_OPERATIONS = 0x20;
+ /** @hide */
+ public static final int FIELD_ALL = 0xFFFFFFFF;
+
+ private long bucketDuration;
+ private int bucketCount;
+ private long[] bucketStart;
+ private long[] activeTime;
+ private long[] rxBytes;
+ private long[] rxPackets;
+ private long[] txBytes;
+ private long[] txPackets;
+ private long[] operations;
+ private long totalBytes;
+
+ /** @hide */
+ public NetworkStatsHistory(long bucketDuration, long[] bucketStart, long[] activeTime,
+ long[] rxBytes, long[] rxPackets, long[] txBytes, long[] txPackets,
+ long[] operations, int bucketCount, long totalBytes) {
+ this.bucketDuration = bucketDuration;
+ this.bucketStart = bucketStart;
+ this.activeTime = activeTime;
+ this.rxBytes = rxBytes;
+ this.rxPackets = rxPackets;
+ this.txBytes = txBytes;
+ this.txPackets = txPackets;
+ this.operations = operations;
+ this.bucketCount = bucketCount;
+ this.totalBytes = totalBytes;
+ }
+
+ /**
+ * An instance to represent a single record in a {@link NetworkStatsHistory} object.
+ */
+ public static final class Entry {
+ /** @hide */
+ public static final long UNKNOWN = -1;
+
+ /** @hide */
+ // TODO: Migrate all callers to get duration from the history object and remove this field.
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public long bucketDuration;
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public long bucketStart;
+ /** @hide */
+ public long activeTime;
+ /** @hide */
+ @UnsupportedAppUsage
+ public long rxBytes;
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public long rxPackets;
+ /** @hide */
+ @UnsupportedAppUsage
+ public long txBytes;
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public long txPackets;
+ /** @hide */
+ public long operations;
+ /** @hide */
+ Entry() {}
+
+ /**
+ * Construct a {@link Entry} instance to represent a single record in a
+ * {@link NetworkStatsHistory} object.
+ *
+ * @param bucketStart Start of period for this {@link Entry}, in milliseconds since the
+ * Unix epoch, see {@link java.lang.System#currentTimeMillis}.
+ * @param activeTime Active time for this {@link Entry}, in milliseconds.
+ * @param rxBytes Number of bytes received for this {@link Entry}. Statistics should
+ * represent the contents of IP packets, including IP headers.
+ * @param rxPackets Number of packets received for this {@link Entry}. Statistics should
+ * represent the contents of IP packets, including IP headers.
+ * @param txBytes Number of bytes transmitted for this {@link Entry}. Statistics should
+ * represent the contents of IP packets, including IP headers.
+ * @param txPackets Number of bytes transmitted for this {@link Entry}. Statistics should
+ * represent the contents of IP packets, including IP headers.
+ * @param operations count of network operations performed for this {@link Entry}. This can
+ * be used to derive bytes-per-operation.
+ */
+ public Entry(long bucketStart, long activeTime, long rxBytes,
+ long rxPackets, long txBytes, long txPackets, long operations) {
+ this.bucketStart = bucketStart;
+ this.activeTime = activeTime;
+ this.rxBytes = rxBytes;
+ this.rxPackets = rxPackets;
+ this.txBytes = txBytes;
+ this.txPackets = txPackets;
+ this.operations = operations;
+ }
+
+ /**
+ * Get start timestamp of the bucket's time interval, in milliseconds since the Unix epoch.
+ */
+ public long getBucketStart() {
+ return bucketStart;
+ }
+
+ /**
+ * Get active time of the bucket's time interval, in milliseconds.
+ */
+ public long getActiveTime() {
+ return activeTime;
+ }
+
+ /** Get number of bytes received for this {@link Entry}. */
+ public long getRxBytes() {
+ return rxBytes;
+ }
+
+ /** Get number of packets received for this {@link Entry}. */
+ public long getRxPackets() {
+ return rxPackets;
+ }
+
+ /** Get number of bytes transmitted for this {@link Entry}. */
+ public long getTxBytes() {
+ return txBytes;
+ }
+
+ /** Get number of packets transmitted for this {@link Entry}. */
+ public long getTxPackets() {
+ return txPackets;
+ }
+
+ /** Get count of network operations performed for this {@link Entry}. */
+ public long getOperations() {
+ return operations;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o.getClass() != getClass()) return false;
+ Entry entry = (Entry) o;
+ return bucketStart == entry.bucketStart
+ && activeTime == entry.activeTime && rxBytes == entry.rxBytes
+ && rxPackets == entry.rxPackets && txBytes == entry.txBytes
+ && txPackets == entry.txPackets && operations == entry.operations;
+ }
+
+ @Override
+ public int hashCode() {
+ return (int) (bucketStart * 2
+ + activeTime * 3
+ + rxBytes * 5
+ + rxPackets * 7
+ + txBytes * 11
+ + txPackets * 13
+ + operations * 17);
+ }
+
+ @Override
+ public String toString() {
+ return "Entry{"
+ + "bucketStart=" + bucketStart
+ + ", activeTime=" + activeTime
+ + ", rxBytes=" + rxBytes
+ + ", rxPackets=" + rxPackets
+ + ", txBytes=" + txBytes
+ + ", txPackets=" + txPackets
+ + ", operations=" + operations
+ + "}";
+ }
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public NetworkStatsHistory(long bucketDuration) {
+ this(bucketDuration, 10, FIELD_ALL);
+ }
+
+ /** @hide */
+ public NetworkStatsHistory(long bucketDuration, int initialSize) {
+ this(bucketDuration, initialSize, FIELD_ALL);
+ }
+
+ /** @hide */
+ public NetworkStatsHistory(long bucketDuration, int initialSize, int fields) {
+ this.bucketDuration = bucketDuration;
+ bucketStart = new long[initialSize];
+ if ((fields & FIELD_ACTIVE_TIME) != 0) activeTime = new long[initialSize];
+ if ((fields & FIELD_RX_BYTES) != 0) rxBytes = new long[initialSize];
+ if ((fields & FIELD_RX_PACKETS) != 0) rxPackets = new long[initialSize];
+ if ((fields & FIELD_TX_BYTES) != 0) txBytes = new long[initialSize];
+ if ((fields & FIELD_TX_PACKETS) != 0) txPackets = new long[initialSize];
+ if ((fields & FIELD_OPERATIONS) != 0) operations = new long[initialSize];
+ bucketCount = 0;
+ totalBytes = 0;
+ }
+
+ /** @hide */
+ public NetworkStatsHistory(NetworkStatsHistory existing, long bucketDuration) {
+ this(bucketDuration, existing.estimateResizeBuckets(bucketDuration));
+ recordEntireHistory(existing);
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public NetworkStatsHistory(Parcel in) {
+ bucketDuration = in.readLong();
+ bucketStart = readLongArray(in);
+ activeTime = readLongArray(in);
+ rxBytes = readLongArray(in);
+ rxPackets = readLongArray(in);
+ txBytes = readLongArray(in);
+ txPackets = readLongArray(in);
+ operations = readLongArray(in);
+ bucketCount = bucketStart.length;
+ totalBytes = in.readLong();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeLong(bucketDuration);
+ writeLongArray(out, bucketStart, bucketCount);
+ writeLongArray(out, activeTime, bucketCount);
+ writeLongArray(out, rxBytes, bucketCount);
+ writeLongArray(out, rxPackets, bucketCount);
+ writeLongArray(out, txBytes, bucketCount);
+ writeLongArray(out, txPackets, bucketCount);
+ writeLongArray(out, operations, bucketCount);
+ out.writeLong(totalBytes);
+ }
+
+ /** @hide */
+ public NetworkStatsHistory(DataInput in) throws IOException {
+ final int version = in.readInt();
+ switch (version) {
+ case VERSION_INIT: {
+ bucketDuration = in.readLong();
+ bucketStart = readFullLongArray(in);
+ rxBytes = readFullLongArray(in);
+ rxPackets = new long[bucketStart.length];
+ txBytes = readFullLongArray(in);
+ txPackets = new long[bucketStart.length];
+ operations = new long[bucketStart.length];
+ bucketCount = bucketStart.length;
+ totalBytes = CollectionUtils.total(rxBytes) + CollectionUtils.total(txBytes);
+ break;
+ }
+ case VERSION_ADD_PACKETS:
+ case VERSION_ADD_ACTIVE: {
+ bucketDuration = in.readLong();
+ bucketStart = readVarLongArray(in);
+ activeTime = (version >= VERSION_ADD_ACTIVE) ? readVarLongArray(in)
+ : new long[bucketStart.length];
+ rxBytes = readVarLongArray(in);
+ rxPackets = readVarLongArray(in);
+ txBytes = readVarLongArray(in);
+ txPackets = readVarLongArray(in);
+ operations = readVarLongArray(in);
+ bucketCount = bucketStart.length;
+ totalBytes = CollectionUtils.total(rxBytes) + CollectionUtils.total(txBytes);
+ break;
+ }
+ default: {
+ throw new ProtocolException("unexpected version: " + version);
+ }
+ }
+
+ if (bucketStart.length != bucketCount || rxBytes.length != bucketCount
+ || rxPackets.length != bucketCount || txBytes.length != bucketCount
+ || txPackets.length != bucketCount || operations.length != bucketCount) {
+ throw new ProtocolException("Mismatched history lengths");
+ }
+ }
+
+ /** @hide */
+ public void writeToStream(DataOutput out) throws IOException {
+ out.writeInt(VERSION_ADD_ACTIVE);
+ out.writeLong(bucketDuration);
+ writeVarLongArray(out, bucketStart, bucketCount);
+ writeVarLongArray(out, activeTime, bucketCount);
+ writeVarLongArray(out, rxBytes, bucketCount);
+ writeVarLongArray(out, rxPackets, bucketCount);
+ writeVarLongArray(out, txBytes, bucketCount);
+ writeVarLongArray(out, txPackets, bucketCount);
+ writeVarLongArray(out, operations, bucketCount);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public int size() {
+ return bucketCount;
+ }
+
+ /** @hide */
+ public long getBucketDuration() {
+ return bucketDuration;
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public long getStart() {
+ if (bucketCount > 0) {
+ return bucketStart[0];
+ } else {
+ return Long.MAX_VALUE;
+ }
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public long getEnd() {
+ if (bucketCount > 0) {
+ return bucketStart[bucketCount - 1] + bucketDuration;
+ } else {
+ return Long.MIN_VALUE;
+ }
+ }
+
+ /**
+ * Return total bytes represented by this history.
+ * @hide
+ */
+ public long getTotalBytes() {
+ return totalBytes;
+ }
+
+ /**
+ * Return index of bucket that contains or is immediately before the
+ * requested time.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public int getIndexBefore(long time) {
+ int index = Arrays.binarySearch(bucketStart, 0, bucketCount, time);
+ if (index < 0) {
+ index = (~index) - 1;
+ } else {
+ index -= 1;
+ }
+ return NetworkStatsUtils.constrain(index, 0, bucketCount - 1);
+ }
+
+ /**
+ * Return index of bucket that contains or is immediately after the
+ * requested time.
+ * @hide
+ */
+ public int getIndexAfter(long time) {
+ int index = Arrays.binarySearch(bucketStart, 0, bucketCount, time);
+ if (index < 0) {
+ index = ~index;
+ } else {
+ index += 1;
+ }
+ return NetworkStatsUtils.constrain(index, 0, bucketCount - 1);
+ }
+
+ /**
+ * Return specific stats entry.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public Entry getValues(int i, Entry recycle) {
+ final Entry entry = recycle != null ? recycle : new Entry();
+ entry.bucketStart = bucketStart[i];
+ entry.bucketDuration = bucketDuration;
+ entry.activeTime = getLong(activeTime, i, UNKNOWN);
+ entry.rxBytes = getLong(rxBytes, i, UNKNOWN);
+ entry.rxPackets = getLong(rxPackets, i, UNKNOWN);
+ entry.txBytes = getLong(txBytes, i, UNKNOWN);
+ entry.txPackets = getLong(txPackets, i, UNKNOWN);
+ entry.operations = getLong(operations, i, UNKNOWN);
+ return entry;
+ }
+
+ /**
+ * Get List of {@link Entry} of the {@link NetworkStatsHistory} instance.
+ *
+ * @return
+ */
+ @NonNull
+ public List<Entry> getEntries() {
+ // TODO: Return a wrapper that uses this list instead, to prevent the returned result
+ // from being changed.
+ final ArrayList<Entry> ret = new ArrayList<>(size());
+ for (int i = 0; i < size(); i++) {
+ ret.add(getValues(i, null /* recycle */));
+ }
+ return ret;
+ }
+
+ /** @hide */
+ public void setValues(int i, Entry entry) {
+ // Unwind old values
+ if (rxBytes != null) totalBytes -= rxBytes[i];
+ if (txBytes != null) totalBytes -= txBytes[i];
+
+ bucketStart[i] = entry.bucketStart;
+ setLong(activeTime, i, entry.activeTime);
+ setLong(rxBytes, i, entry.rxBytes);
+ setLong(rxPackets, i, entry.rxPackets);
+ setLong(txBytes, i, entry.txBytes);
+ setLong(txPackets, i, entry.txPackets);
+ setLong(operations, i, entry.operations);
+
+ // Apply new values
+ if (rxBytes != null) totalBytes += rxBytes[i];
+ if (txBytes != null) totalBytes += txBytes[i];
+ }
+
+ /**
+ * Record that data traffic occurred in the given time range. Will
+ * distribute across internal buckets, creating new buckets as needed.
+ * @hide
+ */
+ @Deprecated
+ public void recordData(long start, long end, long rxBytes, long txBytes) {
+ recordData(start, end, new NetworkStats.Entry(
+ IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, 0L, txBytes, 0L, 0L));
+ }
+
+ /**
+ * Record that data traffic occurred in the given time range. Will
+ * distribute across internal buckets, creating new buckets as needed.
+ * @hide
+ */
+ public void recordData(long start, long end, NetworkStats.Entry entry) {
+ long rxBytes = entry.rxBytes;
+ long rxPackets = entry.rxPackets;
+ long txBytes = entry.txBytes;
+ long txPackets = entry.txPackets;
+ long operations = entry.operations;
+
+ if (entry.isNegative()) {
+ throw new IllegalArgumentException("tried recording negative data");
+ }
+ if (entry.isEmpty()) {
+ return;
+ }
+
+ // create any buckets needed by this range
+ ensureBuckets(start, end);
+ // Return fast if there is still no entry. This would typically happen when the start,
+ // end or duration are not valid values, e.g. start > end, negative duration value, etc.
+ if (bucketCount == 0) return;
+
+ // distribute data usage into buckets
+ long duration = end - start;
+ final int startIndex = getIndexAfter(end);
+ for (int i = startIndex; i >= 0; i--) {
+ final long curStart = bucketStart[i];
+ final long curEnd = curStart + bucketDuration;
+
+ // bucket is older than record; we're finished
+ if (curEnd < start) break;
+ // bucket is newer than record; keep looking
+ if (curStart > end) continue;
+
+ final long overlap = Math.min(curEnd, end) - Math.max(curStart, start);
+ if (overlap <= 0) continue;
+
+ // integer math each time is faster than floating point
+ final long fracRxBytes = multiplySafeByRational(rxBytes, overlap, duration);
+ final long fracRxPackets = multiplySafeByRational(rxPackets, overlap, duration);
+ final long fracTxBytes = multiplySafeByRational(txBytes, overlap, duration);
+ final long fracTxPackets = multiplySafeByRational(txPackets, overlap, duration);
+ final long fracOperations = multiplySafeByRational(operations, overlap, duration);
+
+
+ addLong(activeTime, i, overlap);
+ addLong(this.rxBytes, i, fracRxBytes); rxBytes -= fracRxBytes;
+ addLong(this.rxPackets, i, fracRxPackets); rxPackets -= fracRxPackets;
+ addLong(this.txBytes, i, fracTxBytes); txBytes -= fracTxBytes;
+ addLong(this.txPackets, i, fracTxPackets); txPackets -= fracTxPackets;
+ addLong(this.operations, i, fracOperations); operations -= fracOperations;
+
+ duration -= overlap;
+ }
+
+ totalBytes += entry.rxBytes + entry.txBytes;
+ }
+
+ /**
+ * Record an entire {@link NetworkStatsHistory} into this history. Usually
+ * for combining together stats for external reporting.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public void recordEntireHistory(NetworkStatsHistory input) {
+ recordHistory(input, Long.MIN_VALUE, Long.MAX_VALUE);
+ }
+
+ /**
+ * Record given {@link NetworkStatsHistory} into this history, copying only
+ * buckets that atomically occur in the inclusive time range. Doesn't
+ * interpolate across partial buckets.
+ * @hide
+ */
+ public void recordHistory(NetworkStatsHistory input, long start, long end) {
+ final NetworkStats.Entry entry = new NetworkStats.Entry(
+ IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
+ for (int i = 0; i < input.bucketCount; i++) {
+ final long bucketStart = input.bucketStart[i];
+ final long bucketEnd = bucketStart + input.bucketDuration;
+
+ // skip when bucket is outside requested range
+ if (bucketStart < start || bucketEnd > end) continue;
+
+ entry.rxBytes = getLong(input.rxBytes, i, 0L);
+ entry.rxPackets = getLong(input.rxPackets, i, 0L);
+ entry.txBytes = getLong(input.txBytes, i, 0L);
+ entry.txPackets = getLong(input.txPackets, i, 0L);
+ entry.operations = getLong(input.operations, i, 0L);
+
+ recordData(bucketStart, bucketEnd, entry);
+ }
+ }
+
+ /**
+ * Ensure that buckets exist for given time range, creating as needed.
+ */
+ private void ensureBuckets(long start, long end) {
+ // normalize incoming range to bucket boundaries
+ start -= start % bucketDuration;
+ end += (bucketDuration - (end % bucketDuration)) % bucketDuration;
+
+ for (long now = start; now < end; now += bucketDuration) {
+ // try finding existing bucket
+ final int index = Arrays.binarySearch(bucketStart, 0, bucketCount, now);
+ if (index < 0) {
+ // bucket missing, create and insert
+ insertBucket(~index, now);
+ }
+ }
+ }
+
+ /**
+ * Insert new bucket at requested index and starting time.
+ */
+ private void insertBucket(int index, long start) {
+ // create more buckets when needed
+ if (bucketCount >= bucketStart.length) {
+ final int newLength = Math.max(bucketStart.length, 10) * 3 / 2;
+ bucketStart = Arrays.copyOf(bucketStart, newLength);
+ if (activeTime != null) activeTime = Arrays.copyOf(activeTime, newLength);
+ if (rxBytes != null) rxBytes = Arrays.copyOf(rxBytes, newLength);
+ if (rxPackets != null) rxPackets = Arrays.copyOf(rxPackets, newLength);
+ if (txBytes != null) txBytes = Arrays.copyOf(txBytes, newLength);
+ if (txPackets != null) txPackets = Arrays.copyOf(txPackets, newLength);
+ if (operations != null) operations = Arrays.copyOf(operations, newLength);
+ }
+
+ // create gap when inserting bucket in middle
+ if (index < bucketCount) {
+ final int dstPos = index + 1;
+ final int length = bucketCount - index;
+
+ System.arraycopy(bucketStart, index, bucketStart, dstPos, length);
+ if (activeTime != null) System.arraycopy(activeTime, index, activeTime, dstPos, length);
+ if (rxBytes != null) System.arraycopy(rxBytes, index, rxBytes, dstPos, length);
+ if (rxPackets != null) System.arraycopy(rxPackets, index, rxPackets, dstPos, length);
+ if (txBytes != null) System.arraycopy(txBytes, index, txBytes, dstPos, length);
+ if (txPackets != null) System.arraycopy(txPackets, index, txPackets, dstPos, length);
+ if (operations != null) System.arraycopy(operations, index, operations, dstPos, length);
+ }
+
+ bucketStart[index] = start;
+ setLong(activeTime, index, 0L);
+ setLong(rxBytes, index, 0L);
+ setLong(rxPackets, index, 0L);
+ setLong(txBytes, index, 0L);
+ setLong(txPackets, index, 0L);
+ setLong(operations, index, 0L);
+ bucketCount++;
+ }
+
+ /**
+ * Clear all data stored in this object.
+ * @hide
+ */
+ public void clear() {
+ bucketStart = EmptyArray.LONG;
+ if (activeTime != null) activeTime = EmptyArray.LONG;
+ if (rxBytes != null) rxBytes = EmptyArray.LONG;
+ if (rxPackets != null) rxPackets = EmptyArray.LONG;
+ if (txBytes != null) txBytes = EmptyArray.LONG;
+ if (txPackets != null) txPackets = EmptyArray.LONG;
+ if (operations != null) operations = EmptyArray.LONG;
+ bucketCount = 0;
+ totalBytes = 0;
+ }
+
+ /**
+ * Remove buckets that start older than requested cutoff.
+ *
+ * This method will remove any bucket that contains any data older than the requested
+ * cutoff, even if that same bucket includes some data from after the cutoff.
+ *
+ * @hide
+ */
+ public void removeBucketsStartingBefore(final long cutoff) {
+ // TODO: Consider use getIndexBefore.
+ int i;
+ for (i = 0; i < bucketCount; i++) {
+ final long curStart = bucketStart[i];
+
+ // This bucket starts after or at the cutoff, so it should be kept.
+ if (curStart >= cutoff) break;
+ }
+
+ if (i > 0) {
+ final int length = bucketStart.length;
+ bucketStart = Arrays.copyOfRange(bucketStart, i, length);
+ if (activeTime != null) activeTime = Arrays.copyOfRange(activeTime, i, length);
+ if (rxBytes != null) rxBytes = Arrays.copyOfRange(rxBytes, i, length);
+ if (rxPackets != null) rxPackets = Arrays.copyOfRange(rxPackets, i, length);
+ if (txBytes != null) txBytes = Arrays.copyOfRange(txBytes, i, length);
+ if (txPackets != null) txPackets = Arrays.copyOfRange(txPackets, i, length);
+ if (operations != null) operations = Arrays.copyOfRange(operations, i, length);
+ bucketCount -= i;
+
+ totalBytes = 0;
+ if (rxBytes != null) totalBytes += CollectionUtils.total(rxBytes);
+ if (txBytes != null) totalBytes += CollectionUtils.total(txBytes);
+ }
+ }
+
+ /**
+ * Return interpolated data usage across the requested range. Interpolates
+ * across buckets, so values may be rounded slightly.
+ *
+ * <p>If the active bucket is not completed yet, it returns the proportional value of it
+ * based on its duration and the {@code end} param.
+ *
+ * @param start - start of the range, timestamp in milliseconds since the epoch.
+ * @param end - end of the range, timestamp in milliseconds since the epoch.
+ * @param recycle - entry instance for performance, could be null.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public Entry getValues(long start, long end, Entry recycle) {
+ return getValues(start, end, Long.MAX_VALUE, recycle);
+ }
+
+ /**
+ * Return interpolated data usage across the requested range. Interpolates
+ * across buckets, so values may be rounded slightly.
+ *
+ * @param start - start of the range, timestamp in milliseconds since the epoch.
+ * @param end - end of the range, timestamp in milliseconds since the epoch.
+ * @param now - current timestamp in milliseconds since the epoch (wall clock).
+ * @param recycle - entry instance for performance, could be null.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public Entry getValues(long start, long end, long now, Entry recycle) {
+ final Entry entry = recycle != null ? recycle : new Entry();
+ entry.bucketDuration = end - start;
+ entry.bucketStart = start;
+ entry.activeTime = activeTime != null ? 0 : UNKNOWN;
+ entry.rxBytes = rxBytes != null ? 0 : UNKNOWN;
+ entry.rxPackets = rxPackets != null ? 0 : UNKNOWN;
+ entry.txBytes = txBytes != null ? 0 : UNKNOWN;
+ entry.txPackets = txPackets != null ? 0 : UNKNOWN;
+ entry.operations = operations != null ? 0 : UNKNOWN;
+
+ // Return fast if there is no entry.
+ if (bucketCount == 0) return entry;
+
+ final int startIndex = getIndexAfter(end);
+ for (int i = startIndex; i >= 0; i--) {
+ final long curStart = bucketStart[i];
+ long curEnd = curStart + bucketDuration;
+
+ // bucket is older than request; we're finished
+ if (curEnd <= start) break;
+ // bucket is newer than request; keep looking
+ if (curStart >= end) continue;
+
+ // the active bucket is shorter then a normal completed bucket
+ if (curEnd > now) curEnd = now;
+ // usually this is simply bucketDuration
+ final long bucketSpan = curEnd - curStart;
+ // prevent division by zero
+ if (bucketSpan <= 0) continue;
+
+ final long overlapEnd = curEnd < end ? curEnd : end;
+ final long overlapStart = curStart > start ? curStart : start;
+ final long overlap = overlapEnd - overlapStart;
+ if (overlap <= 0) continue;
+
+ // integer math each time is faster than floating point
+ if (activeTime != null) {
+ entry.activeTime += multiplySafeByRational(activeTime[i], overlap, bucketSpan);
+ }
+ if (rxBytes != null) {
+ entry.rxBytes += multiplySafeByRational(rxBytes[i], overlap, bucketSpan);
+ }
+ if (rxPackets != null) {
+ entry.rxPackets += multiplySafeByRational(rxPackets[i], overlap, bucketSpan);
+ }
+ if (txBytes != null) {
+ entry.txBytes += multiplySafeByRational(txBytes[i], overlap, bucketSpan);
+ }
+ if (txPackets != null) {
+ entry.txPackets += multiplySafeByRational(txPackets[i], overlap, bucketSpan);
+ }
+ if (operations != null) {
+ entry.operations += multiplySafeByRational(operations[i], overlap, bucketSpan);
+ }
+ }
+ return entry;
+ }
+
+ /**
+ * @deprecated only for temporary testing
+ * @hide
+ */
+ @Deprecated
+ public void generateRandom(long start, long end, long bytes) {
+ final Random r = new Random();
+
+ final float fractionRx = r.nextFloat();
+ final long rxBytes = (long) (bytes * fractionRx);
+ final long txBytes = (long) (bytes * (1 - fractionRx));
+
+ final long rxPackets = rxBytes / 1024;
+ final long txPackets = txBytes / 1024;
+ final long operations = rxBytes / 2048;
+
+ generateRandom(start, end, rxBytes, rxPackets, txBytes, txPackets, operations, r);
+ }
+
+ /**
+ * @deprecated only for temporary testing
+ * @hide
+ */
+ @Deprecated
+ public void generateRandom(long start, long end, long rxBytes, long rxPackets, long txBytes,
+ long txPackets, long operations, Random r) {
+ ensureBuckets(start, end);
+
+ final NetworkStats.Entry entry = new NetworkStats.Entry(
+ IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
+ while (rxBytes > 1024 || rxPackets > 128 || txBytes > 1024 || txPackets > 128
+ || operations > 32) {
+ final long curStart = randomLong(r, start, end);
+ final long curEnd = curStart + randomLong(r, 0, (end - curStart) / 2);
+
+ entry.rxBytes = randomLong(r, 0, rxBytes);
+ entry.rxPackets = randomLong(r, 0, rxPackets);
+ entry.txBytes = randomLong(r, 0, txBytes);
+ entry.txPackets = randomLong(r, 0, txPackets);
+ entry.operations = randomLong(r, 0, operations);
+
+ rxBytes -= entry.rxBytes;
+ rxPackets -= entry.rxPackets;
+ txBytes -= entry.txBytes;
+ txPackets -= entry.txPackets;
+ operations -= entry.operations;
+
+ recordData(curStart, curEnd, entry);
+ }
+ }
+
+ /** @hide */
+ public static long randomLong(Random r, long start, long end) {
+ return (long) (start + (r.nextFloat() * (end - start)));
+ }
+
+ /**
+ * Quickly determine if this history intersects with given window.
+ * @hide
+ */
+ public boolean intersects(long start, long end) {
+ final long dataStart = getStart();
+ final long dataEnd = getEnd();
+ if (start >= dataStart && start <= dataEnd) return true;
+ if (end >= dataStart && end <= dataEnd) return true;
+ if (dataStart >= start && dataStart <= end) return true;
+ if (dataEnd >= start && dataEnd <= end) return true;
+ return false;
+ }
+
+ /** @hide */
+ public void dump(IndentingPrintWriter pw, boolean fullHistory) {
+ pw.print("NetworkStatsHistory: bucketDuration=");
+ pw.println(bucketDuration / SECOND_IN_MILLIS);
+ pw.increaseIndent();
+
+ final int start = fullHistory ? 0 : Math.max(0, bucketCount - 32);
+ if (start > 0) {
+ pw.print("(omitting "); pw.print(start); pw.println(" buckets)");
+ }
+
+ for (int i = start; i < bucketCount; i++) {
+ pw.print("st="); pw.print(bucketStart[i] / SECOND_IN_MILLIS);
+ if (rxBytes != null) { pw.print(" rb="); pw.print(rxBytes[i]); }
+ if (rxPackets != null) { pw.print(" rp="); pw.print(rxPackets[i]); }
+ if (txBytes != null) { pw.print(" tb="); pw.print(txBytes[i]); }
+ if (txPackets != null) { pw.print(" tp="); pw.print(txPackets[i]); }
+ if (operations != null) { pw.print(" op="); pw.print(operations[i]); }
+ pw.println();
+ }
+
+ pw.decreaseIndent();
+ }
+
+ /** @hide */
+ public void dumpCheckin(PrintWriter pw) {
+ pw.print("d,");
+ pw.print(bucketDuration / SECOND_IN_MILLIS);
+ pw.println();
+
+ for (int i = 0; i < bucketCount; i++) {
+ pw.print("b,");
+ pw.print(bucketStart[i] / SECOND_IN_MILLIS); pw.print(',');
+ if (rxBytes != null) { pw.print(rxBytes[i]); } else { pw.print("*"); } pw.print(',');
+ if (rxPackets != null) { pw.print(rxPackets[i]); } else { pw.print("*"); } pw.print(',');
+ if (txBytes != null) { pw.print(txBytes[i]); } else { pw.print("*"); } pw.print(',');
+ if (txPackets != null) { pw.print(txPackets[i]); } else { pw.print("*"); } pw.print(',');
+ if (operations != null) { pw.print(operations[i]); } else { pw.print("*"); }
+ pw.println();
+ }
+ }
+
+ /** @hide */
+ public void dumpDebug(ProtoOutputStream proto, long tag) {
+ final long start = proto.start(tag);
+
+ proto.write(NetworkStatsHistoryProto.BUCKET_DURATION_MS, bucketDuration);
+
+ for (int i = 0; i < bucketCount; i++) {
+ final long startBucket = proto.start(NetworkStatsHistoryProto.BUCKETS);
+
+ proto.write(NetworkStatsHistoryBucketProto.BUCKET_START_MS,
+ bucketStart[i]);
+ dumpDebug(proto, NetworkStatsHistoryBucketProto.RX_BYTES, rxBytes, i);
+ dumpDebug(proto, NetworkStatsHistoryBucketProto.RX_PACKETS, rxPackets, i);
+ dumpDebug(proto, NetworkStatsHistoryBucketProto.TX_BYTES, txBytes, i);
+ dumpDebug(proto, NetworkStatsHistoryBucketProto.TX_PACKETS, txPackets, i);
+ dumpDebug(proto, NetworkStatsHistoryBucketProto.OPERATIONS, operations, i);
+
+ proto.end(startBucket);
+ }
+
+ proto.end(start);
+ }
+
+ private static void dumpDebug(ProtoOutputStream proto, long tag, long[] array, int index) {
+ if (array != null) {
+ proto.write(tag, array[index]);
+ }
+ }
+
+ @Override
+ public String toString() {
+ final CharArrayWriter writer = new CharArrayWriter();
+ dump(new IndentingPrintWriter(writer, " "), false);
+ return writer.toString();
+ }
+
+ /**
+ * Same as "equals", but not actually called equals as this would affect public API behavior.
+ * @hide
+ */
+ @Nullable
+ public boolean isSameAs(NetworkStatsHistory other) {
+ return bucketCount == other.bucketCount
+ && Arrays.equals(bucketStart, other.bucketStart)
+ // Don't check activeTime since it can change on import due to the importer using
+ // recordHistory. It's also not exposed by the APIs or present in dumpsys or
+ // toString().
+ && Arrays.equals(rxBytes, other.rxBytes)
+ && Arrays.equals(rxPackets, other.rxPackets)
+ && Arrays.equals(txBytes, other.txBytes)
+ && Arrays.equals(txPackets, other.txPackets)
+ && Arrays.equals(operations, other.operations)
+ && totalBytes == other.totalBytes;
+ }
+
+ @UnsupportedAppUsage
+ public static final @android.annotation.NonNull Creator<NetworkStatsHistory> CREATOR = new Creator<NetworkStatsHistory>() {
+ @Override
+ public NetworkStatsHistory createFromParcel(Parcel in) {
+ return new NetworkStatsHistory(in);
+ }
+
+ @Override
+ public NetworkStatsHistory[] newArray(int size) {
+ return new NetworkStatsHistory[size];
+ }
+ };
+
+ private static long getLong(long[] array, int i, long value) {
+ return array != null ? array[i] : value;
+ }
+
+ private static void setLong(long[] array, int i, long value) {
+ if (array != null) array[i] = value;
+ }
+
+ private static void addLong(long[] array, int i, long value) {
+ if (array != null) array[i] += value;
+ }
+
+ /** @hide */
+ public int estimateResizeBuckets(long newBucketDuration) {
+ return (int) (size() * getBucketDuration() / newBucketDuration);
+ }
+
+ /**
+ * Utility methods for interacting with {@link DataInputStream} and
+ * {@link DataOutputStream}, mostly dealing with writing partial arrays.
+ * @hide
+ */
+ public static class DataStreamUtils {
+ @Deprecated
+ public static long[] readFullLongArray(DataInput in) throws IOException {
+ final int size = in.readInt();
+ if (size < 0) throw new ProtocolException("negative array size");
+ final long[] values = new long[size];
+ for (int i = 0; i < values.length; i++) {
+ values[i] = in.readLong();
+ }
+ return values;
+ }
+
+ /**
+ * Read variable-length {@link Long} using protobuf-style approach.
+ */
+ public static long readVarLong(DataInput in) throws IOException {
+ int shift = 0;
+ long result = 0;
+ while (shift < 64) {
+ byte b = in.readByte();
+ result |= (long) (b & 0x7F) << shift;
+ if ((b & 0x80) == 0)
+ return result;
+ shift += 7;
+ }
+ throw new ProtocolException("malformed long");
+ }
+
+ /**
+ * Write variable-length {@link Long} using protobuf-style approach.
+ */
+ public static void writeVarLong(DataOutput out, long value) throws IOException {
+ while (true) {
+ if ((value & ~0x7FL) == 0) {
+ out.writeByte((int) value);
+ return;
+ } else {
+ out.writeByte(((int) value & 0x7F) | 0x80);
+ value >>>= 7;
+ }
+ }
+ }
+
+ public static long[] readVarLongArray(DataInput in) throws IOException {
+ final int size = in.readInt();
+ if (size == -1) return null;
+ if (size < 0) throw new ProtocolException("negative array size");
+ final long[] values = new long[size];
+ for (int i = 0; i < values.length; i++) {
+ values[i] = readVarLong(in);
+ }
+ return values;
+ }
+
+ public static void writeVarLongArray(DataOutput out, long[] values, int size)
+ throws IOException {
+ if (values == null) {
+ out.writeInt(-1);
+ return;
+ }
+ if (size > values.length) {
+ throw new IllegalArgumentException("size larger than length");
+ }
+ out.writeInt(size);
+ for (int i = 0; i < size; i++) {
+ writeVarLong(out, values[i]);
+ }
+ }
+ }
+
+ /**
+ * Utility methods for interacting with {@link Parcel} structures, mostly
+ * dealing with writing partial arrays.
+ * @hide
+ */
+ public static class ParcelUtils {
+ public static long[] readLongArray(Parcel in) {
+ final int size = in.readInt();
+ if (size == -1) return null;
+ final long[] values = new long[size];
+ for (int i = 0; i < values.length; i++) {
+ values[i] = in.readLong();
+ }
+ return values;
+ }
+
+ public static void writeLongArray(Parcel out, long[] values, int size) {
+ if (values == null) {
+ out.writeInt(-1);
+ return;
+ }
+ if (size > values.length) {
+ throw new IllegalArgumentException("size larger than length");
+ }
+ out.writeInt(size);
+ for (int i = 0; i < size; i++) {
+ out.writeLong(values[i]);
+ }
+ }
+ }
+
+ /**
+ * Builder class for {@link NetworkStatsHistory}.
+ */
+ public static final class Builder {
+ private final long mBucketDuration;
+ private final List<Long> mBucketStart;
+ private final List<Long> mActiveTime;
+ private final List<Long> mRxBytes;
+ private final List<Long> mRxPackets;
+ private final List<Long> mTxBytes;
+ private final List<Long> mTxPackets;
+ private final List<Long> mOperations;
+
+ /**
+ * Creates a new Builder with given bucket duration and initial capacity to construct
+ * {@link NetworkStatsHistory} objects.
+ *
+ * @param bucketDuration Duration of the buckets of the object, in milliseconds.
+ * @param initialCapacity Estimated number of records.
+ */
+ public Builder(long bucketDuration, int initialCapacity) {
+ mBucketDuration = bucketDuration;
+ mBucketStart = new ArrayList<>(initialCapacity);
+ mActiveTime = new ArrayList<>(initialCapacity);
+ mRxBytes = new ArrayList<>(initialCapacity);
+ mRxPackets = new ArrayList<>(initialCapacity);
+ mTxBytes = new ArrayList<>(initialCapacity);
+ mTxPackets = new ArrayList<>(initialCapacity);
+ mOperations = new ArrayList<>(initialCapacity);
+ }
+
+ private void addToElement(List<Long> list, int pos, long value) {
+ list.set(pos, list.get(pos) + value);
+ }
+
+ /**
+ * Add an {@link Entry} into the {@link NetworkStatsHistory} instance.
+ *
+ * @param entry The target {@link Entry} object. The entry timestamp must be greater than
+ * that of any previously-added entry.
+ * @return The builder object.
+ */
+ @NonNull
+ public Builder addEntry(@NonNull Entry entry) {
+ final int lastBucket = mBucketStart.size() - 1;
+ final long lastBucketStart = (lastBucket != -1) ? mBucketStart.get(lastBucket) : 0;
+
+ // If last bucket has the same timestamp, modify it instead of adding another bucket.
+ // This allows callers to pass in the same bucket twice (e.g., to accumulate
+ // data over time), but still requires that entries must be sorted.
+ // The importer will do this in case a rotated file has the same timestamp as
+ // the previous file.
+ if (lastBucket != -1 && entry.bucketStart == lastBucketStart) {
+ addToElement(mActiveTime, lastBucket, entry.activeTime);
+ addToElement(mRxBytes, lastBucket, entry.rxBytes);
+ addToElement(mRxPackets, lastBucket, entry.rxPackets);
+ addToElement(mTxBytes, lastBucket, entry.txBytes);
+ addToElement(mTxPackets, lastBucket, entry.txPackets);
+ addToElement(mOperations, lastBucket, entry.operations);
+ return this;
+ }
+
+ // Inserting in the middle is prohibited for performance reasons.
+ if (entry.bucketStart <= lastBucketStart) {
+ throw new IllegalArgumentException("new bucket start " + entry.bucketStart
+ + " must be greater than last bucket start " + lastBucketStart);
+ }
+
+ // Common case: add entries at the end of the list.
+ mBucketStart.add(entry.bucketStart);
+ mActiveTime.add(entry.activeTime);
+ mRxBytes.add(entry.rxBytes);
+ mRxPackets.add(entry.rxPackets);
+ mTxBytes.add(entry.txBytes);
+ mTxPackets.add(entry.txPackets);
+ mOperations.add(entry.operations);
+ return this;
+ }
+
+ private static long sum(@NonNull List<Long> list) {
+ long sum = 0;
+ for (long entry : list) {
+ sum += entry;
+ }
+ return sum;
+ }
+
+ /**
+ * Builds the instance of the {@link NetworkStatsHistory}.
+ *
+ * @return the built instance of {@link NetworkStatsHistory}.
+ */
+ @NonNull
+ public NetworkStatsHistory build() {
+ return new NetworkStatsHistory(mBucketDuration,
+ CollectionUtils.toLongArray(mBucketStart),
+ CollectionUtils.toLongArray(mActiveTime),
+ CollectionUtils.toLongArray(mRxBytes),
+ CollectionUtils.toLongArray(mRxPackets),
+ CollectionUtils.toLongArray(mTxBytes),
+ CollectionUtils.toLongArray(mTxPackets),
+ CollectionUtils.toLongArray(mOperations),
+ mBucketStart.size(),
+ sum(mRxBytes) + sum(mTxBytes));
+ }
+ }
+}
diff --git a/framework-t/src/android/net/NetworkTemplate.java b/framework-t/src/android/net/NetworkTemplate.java
new file mode 100644
index 0000000..b82a126
--- /dev/null
+++ b/framework-t/src/android/net/NetworkTemplate.java
@@ -0,0 +1,1120 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+import static android.net.ConnectivityManager.TYPE_BLUETOOTH;
+import static android.net.ConnectivityManager.TYPE_ETHERNET;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_PROXY;
+import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.net.ConnectivityManager.TYPE_WIFI_P2P;
+import static android.net.ConnectivityManager.TYPE_WIMAX;
+import static android.net.NetworkIdentity.OEM_NONE;
+import static android.net.NetworkIdentity.OEM_PAID;
+import static android.net.NetworkIdentity.OEM_PRIVATE;
+import static android.net.NetworkStats.DEFAULT_NETWORK_ALL;
+import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
+import static android.net.NetworkStats.DEFAULT_NETWORK_YES;
+import static android.net.NetworkStats.METERED_ALL;
+import static android.net.NetworkStats.METERED_NO;
+import static android.net.NetworkStats.METERED_YES;
+import static android.net.NetworkStats.ROAMING_ALL;
+import static android.net.NetworkStats.ROAMING_NO;
+import static android.net.NetworkStats.ROAMING_YES;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.usage.NetworkStatsManager;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.net.wifi.WifiInfo;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.ArraySet;
+
+import com.android.net.module.util.CollectionUtils;
+import com.android.net.module.util.NetworkIdentityUtils;
+import com.android.net.module.util.NetworkStatsUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+/**
+ * Predicate used to match {@link NetworkIdentity}, usually when collecting
+ * statistics. (It should probably have been named {@code NetworkPredicate}.)
+ *
+ * @hide
+ */
+@SystemApi(client = MODULE_LIBRARIES)
+public final class NetworkTemplate implements Parcelable {
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "MATCH_" }, value = {
+ MATCH_MOBILE,
+ MATCH_WIFI,
+ MATCH_ETHERNET,
+ MATCH_BLUETOOTH,
+ MATCH_PROXY,
+ MATCH_CARRIER,
+ })
+ public @interface TemplateMatchRule{}
+
+ /** Match rule to match cellular networks with given Subscriber Ids. */
+ public static final int MATCH_MOBILE = 1;
+ /** Match rule to match wifi networks. */
+ public static final int MATCH_WIFI = 4;
+ /** Match rule to match ethernet networks. */
+ public static final int MATCH_ETHERNET = 5;
+ /**
+ * Match rule to match all cellular networks.
+ *
+ * @hide
+ */
+ public static final int MATCH_MOBILE_WILDCARD = 6;
+ /**
+ * Match rule to match all wifi networks.
+ *
+ * @hide
+ */
+ public static final int MATCH_WIFI_WILDCARD = 7;
+ /** Match rule to match bluetooth networks. */
+ public static final int MATCH_BLUETOOTH = 8;
+ /**
+ * Match rule to match networks with {@link ConnectivityManager#TYPE_PROXY} as the legacy
+ * network type.
+ */
+ public static final int MATCH_PROXY = 9;
+ /**
+ * Match rule to match all networks with subscriberId inside the template. Some carriers
+ * may offer non-cellular networks like WiFi, which will be matched by this rule.
+ */
+ public static final int MATCH_CARRIER = 10;
+
+ // TODO: Remove this and replace all callers with WIFI_NETWORK_KEY_ALL.
+ /** @hide */
+ public static final String WIFI_NETWORKID_ALL = null;
+
+ /**
+ * Wi-Fi Network Key is never supposed to be null (if it is, it is a bug that
+ * should be fixed), so it's not possible to want to match null vs
+ * non-null. Therefore it's fine to use null as a sentinel for Wifi Network Key.
+ *
+ * @hide
+ */
+ public static final String WIFI_NETWORK_KEY_ALL = WIFI_NETWORKID_ALL;
+
+ /**
+ * Include all network types when filtering. This is meant to merge in with the
+ * {@code TelephonyManager.NETWORK_TYPE_*} constants, and thus needs to stay in sync.
+ */
+ public static final int NETWORK_TYPE_ALL = -1;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "OEM_MANAGED_" }, value = {
+ OEM_MANAGED_ALL,
+ OEM_MANAGED_NO,
+ OEM_MANAGED_YES,
+ OEM_MANAGED_PAID,
+ OEM_MANAGED_PRIVATE
+ })
+ public @interface OemManaged{}
+
+ /**
+ * Value to match both OEM managed and unmanaged networks (all networks).
+ */
+ public static final int OEM_MANAGED_ALL = -1;
+ /**
+ * Value to match networks which are not OEM managed.
+ */
+ public static final int OEM_MANAGED_NO = OEM_NONE;
+ /**
+ * Value to match any OEM managed network.
+ */
+ public static final int OEM_MANAGED_YES = -2;
+ /**
+ * Network has {@link NetworkCapabilities#NET_CAPABILITY_OEM_PAID}.
+ */
+ public static final int OEM_MANAGED_PAID = OEM_PAID;
+ /**
+ * Network has {@link NetworkCapabilities#NET_CAPABILITY_OEM_PRIVATE}.
+ */
+ public static final int OEM_MANAGED_PRIVATE = OEM_PRIVATE;
+
+ private static boolean isKnownMatchRule(final int rule) {
+ switch (rule) {
+ case MATCH_MOBILE:
+ case MATCH_WIFI:
+ case MATCH_ETHERNET:
+ case MATCH_MOBILE_WILDCARD:
+ case MATCH_WIFI_WILDCARD:
+ case MATCH_BLUETOOTH:
+ case MATCH_PROXY:
+ case MATCH_CARRIER:
+ return true;
+
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Template to match {@link ConnectivityManager#TYPE_MOBILE} networks with
+ * the given IMSI.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static NetworkTemplate buildTemplateMobileAll(String subscriberId) {
+ return new NetworkTemplate(MATCH_MOBILE, subscriberId, null);
+ }
+
+ /**
+ * Template to match cellular networks with the given IMSI, {@code ratType} and
+ * {@code metered}. Use {@link #NETWORK_TYPE_ALL} to include all network types when
+ * filtering. See {@code TelephonyManager.NETWORK_TYPE_*}.
+ *
+ * @hide
+ */
+ public static NetworkTemplate buildTemplateMobileWithRatType(@Nullable String subscriberId,
+ int ratType, int metered) {
+ if (TextUtils.isEmpty(subscriberId)) {
+ return new NetworkTemplate(MATCH_MOBILE_WILDCARD, null /* subscriberId */,
+ null /* matchSubscriberIds */,
+ new String[0] /* matchWifiNetworkKeys */, metered, ROAMING_ALL,
+ DEFAULT_NETWORK_ALL, ratType, OEM_MANAGED_ALL,
+ NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT);
+ }
+ return new NetworkTemplate(MATCH_MOBILE, subscriberId, new String[] { subscriberId },
+ new String[0] /* matchWifiNetworkKeys */,
+ metered, ROAMING_ALL, DEFAULT_NETWORK_ALL, ratType, OEM_MANAGED_ALL,
+ NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT);
+ }
+
+ /**
+ * Template to match metered {@link ConnectivityManager#TYPE_MOBILE} networks,
+ * regardless of IMSI.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static NetworkTemplate buildTemplateMobileWildcard() {
+ return new NetworkTemplate(MATCH_MOBILE_WILDCARD, null, null);
+ }
+
+ /**
+ * Template to match all metered {@link ConnectivityManager#TYPE_WIFI} networks,
+ * regardless of key of the wifi network.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static NetworkTemplate buildTemplateWifiWildcard() {
+ // TODO: Consider replace this with MATCH_WIFI with NETWORK_ID_ALL
+ // and SUBSCRIBER_ID_MATCH_RULE_ALL.
+ return new NetworkTemplate(MATCH_WIFI_WILDCARD, null, null);
+ }
+
+ /** @hide */
+ @Deprecated
+ @UnsupportedAppUsage
+ public static NetworkTemplate buildTemplateWifi() {
+ return buildTemplateWifiWildcard();
+ }
+
+ /**
+ * Template to match {@link ConnectivityManager#TYPE_WIFI} networks with the
+ * given key of the wifi network.
+ *
+ * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getNetworkKey()}
+ * to know details about the key.
+ * @hide
+ */
+ public static NetworkTemplate buildTemplateWifi(@NonNull String wifiNetworkKey) {
+ Objects.requireNonNull(wifiNetworkKey);
+ return new NetworkTemplate(MATCH_WIFI, null /* subscriberId */,
+ new String[] { null } /* matchSubscriberIds */,
+ new String[] { wifiNetworkKey }, METERED_ALL, ROAMING_ALL,
+ DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_ALL,
+ NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_ALL);
+ }
+
+ /**
+ * Template to match all {@link ConnectivityManager#TYPE_WIFI} networks with the given
+ * key of the wifi network and IMSI.
+ *
+ * Call with {@link #WIFI_NETWORK_KEY_ALL} for {@code wifiNetworkKey} to get result regardless
+ * of key of the wifi network.
+ *
+ * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getNetworkKey()}
+ * to know details about the key.
+ * @param subscriberId the IMSI associated to this wifi network.
+ *
+ * @hide
+ */
+ public static NetworkTemplate buildTemplateWifi(@Nullable String wifiNetworkKey,
+ @Nullable String subscriberId) {
+ return new NetworkTemplate(MATCH_WIFI, subscriberId, new String[] { subscriberId },
+ wifiNetworkKey != null
+ ? new String[] { wifiNetworkKey } : new String[0],
+ METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_ALL,
+ NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT);
+ }
+
+ /**
+ * Template to combine all {@link ConnectivityManager#TYPE_ETHERNET} style
+ * networks together.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static NetworkTemplate buildTemplateEthernet() {
+ return new NetworkTemplate(MATCH_ETHERNET, null, null);
+ }
+
+ /**
+ * Template to combine all {@link ConnectivityManager#TYPE_BLUETOOTH} style
+ * networks together.
+ *
+ * @hide
+ */
+ public static NetworkTemplate buildTemplateBluetooth() {
+ return new NetworkTemplate(MATCH_BLUETOOTH, null, null);
+ }
+
+ /**
+ * Template to combine all {@link ConnectivityManager#TYPE_PROXY} style
+ * networks together.
+ *
+ * @hide
+ */
+ public static NetworkTemplate buildTemplateProxy() {
+ return new NetworkTemplate(MATCH_PROXY, null, null);
+ }
+
+ /**
+ * Template to match all metered carrier networks with the given IMSI.
+ *
+ * @hide
+ */
+ public static NetworkTemplate buildTemplateCarrierMetered(@NonNull String subscriberId) {
+ Objects.requireNonNull(subscriberId);
+ return new NetworkTemplate(MATCH_CARRIER, subscriberId,
+ new String[] { subscriberId },
+ new String[0] /* matchWifiNetworkKeys */,
+ METERED_YES, ROAMING_ALL,
+ DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_ALL,
+ NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT);
+ }
+
+ private final int mMatchRule;
+ private final String mSubscriberId;
+
+ /**
+ * Ugh, templates are designed to target a single subscriber, but we might
+ * need to match several "merged" subscribers. These are the subscribers
+ * that should be considered to match this template.
+ * <p>
+ * Since the merge set is dynamic, it should <em>not</em> be persisted or
+ * used for determining equality.
+ */
+ private final String[] mMatchSubscriberIds;
+
+ @NonNull
+ private final String[] mMatchWifiNetworkKeys;
+
+ // Matches for the NetworkStats constants METERED_*, ROAMING_* and DEFAULT_NETWORK_*.
+ private final int mMetered;
+ private final int mRoaming;
+ private final int mDefaultNetwork;
+ private final int mRatType;
+ /**
+ * The subscriber Id match rule defines how the template should match networks with
+ * specific subscriberId(s). See NetworkTemplate#SUBSCRIBER_ID_MATCH_RULE_* for more detail.
+ */
+ private final int mSubscriberIdMatchRule;
+
+ // Bitfield containing OEM network properties{@code NetworkIdentity#OEM_*}.
+ private final int mOemManaged;
+
+ private static void checkValidSubscriberIdMatchRule(int matchRule, int subscriberIdMatchRule) {
+ switch (matchRule) {
+ case MATCH_MOBILE:
+ case MATCH_CARRIER:
+ // MOBILE and CARRIER templates must always specify a subscriber ID.
+ if (subscriberIdMatchRule == NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_ALL) {
+ throw new IllegalArgumentException("Invalid SubscriberIdMatchRule "
+ + "on match rule: " + getMatchRuleName(matchRule));
+ }
+ return;
+ default:
+ return;
+ }
+ }
+
+ /** @hide */
+ // TODO: Deprecate this constructor, mark it @UnsupportedAppUsage(maxTargetSdk = S)
+ @UnsupportedAppUsage
+ public NetworkTemplate(int matchRule, String subscriberId, String wifiNetworkKey) {
+ this(matchRule, subscriberId, new String[] { subscriberId }, wifiNetworkKey);
+ }
+
+ /** @hide */
+ public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds,
+ String wifiNetworkKey) {
+ // Older versions used to only match MATCH_MOBILE and MATCH_MOBILE_WILDCARD templates
+ // to metered networks. It is now possible to match mobile with any meteredness, but
+ // in order to preserve backward compatibility of @UnsupportedAppUsage methods, this
+ //constructor passes METERED_YES for these types.
+ this(matchRule, subscriberId, matchSubscriberIds,
+ wifiNetworkKey != null ? new String[] { wifiNetworkKey } : new String[0],
+ (matchRule == MATCH_MOBILE || matchRule == MATCH_MOBILE_WILDCARD
+ || matchRule == MATCH_CARRIER) ? METERED_YES : METERED_ALL,
+ ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
+ OEM_MANAGED_ALL, NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT);
+ }
+
+ /** @hide */
+ // TODO: Remove it after updating all of the caller.
+ public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds,
+ String wifiNetworkKey, int metered, int roaming, int defaultNetwork, int ratType,
+ int oemManaged) {
+ this(matchRule, subscriberId, matchSubscriberIds,
+ wifiNetworkKey != null ? new String[] { wifiNetworkKey } : new String[0],
+ metered, roaming, defaultNetwork, ratType, oemManaged,
+ NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT);
+ }
+
+ /** @hide */
+ public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds,
+ String[] matchWifiNetworkKeys, int metered, int roaming,
+ int defaultNetwork, int ratType, int oemManaged, int subscriberIdMatchRule) {
+ Objects.requireNonNull(matchWifiNetworkKeys);
+ mMatchRule = matchRule;
+ mSubscriberId = subscriberId;
+ // TODO: Check whether mMatchSubscriberIds = null or mMatchSubscriberIds = {null} when
+ // mSubscriberId is null
+ mMatchSubscriberIds = matchSubscriberIds;
+ mMatchWifiNetworkKeys = matchWifiNetworkKeys;
+ mMetered = metered;
+ mRoaming = roaming;
+ mDefaultNetwork = defaultNetwork;
+ mRatType = ratType;
+ mOemManaged = oemManaged;
+ mSubscriberIdMatchRule = subscriberIdMatchRule;
+ checkValidSubscriberIdMatchRule(matchRule, subscriberIdMatchRule);
+ if (!isKnownMatchRule(matchRule)) {
+ throw new IllegalArgumentException("Unknown network template rule " + matchRule
+ + " will not match any identity.");
+ }
+ }
+
+ private NetworkTemplate(Parcel in) {
+ mMatchRule = in.readInt();
+ mSubscriberId = in.readString();
+ mMatchSubscriberIds = in.createStringArray();
+ mMatchWifiNetworkKeys = in.createStringArray();
+ mMetered = in.readInt();
+ mRoaming = in.readInt();
+ mDefaultNetwork = in.readInt();
+ mRatType = in.readInt();
+ mOemManaged = in.readInt();
+ mSubscriberIdMatchRule = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mMatchRule);
+ dest.writeString(mSubscriberId);
+ dest.writeStringArray(mMatchSubscriberIds);
+ dest.writeStringArray(mMatchWifiNetworkKeys);
+ dest.writeInt(mMetered);
+ dest.writeInt(mRoaming);
+ dest.writeInt(mDefaultNetwork);
+ dest.writeInt(mRatType);
+ dest.writeInt(mOemManaged);
+ dest.writeInt(mSubscriberIdMatchRule);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder("NetworkTemplate: ");
+ builder.append("matchRule=").append(getMatchRuleName(mMatchRule));
+ if (mSubscriberId != null) {
+ builder.append(", subscriberId=").append(
+ NetworkIdentityUtils.scrubSubscriberId(mSubscriberId));
+ }
+ if (mMatchSubscriberIds != null) {
+ builder.append(", matchSubscriberIds=").append(
+ Arrays.toString(NetworkIdentityUtils.scrubSubscriberIds(mMatchSubscriberIds)));
+ }
+ builder.append(", matchWifiNetworkKeys=").append(Arrays.toString(mMatchWifiNetworkKeys));
+ if (mMetered != METERED_ALL) {
+ builder.append(", metered=").append(NetworkStats.meteredToString(mMetered));
+ }
+ if (mRoaming != ROAMING_ALL) {
+ builder.append(", roaming=").append(NetworkStats.roamingToString(mRoaming));
+ }
+ if (mDefaultNetwork != DEFAULT_NETWORK_ALL) {
+ builder.append(", defaultNetwork=").append(NetworkStats.defaultNetworkToString(
+ mDefaultNetwork));
+ }
+ if (mRatType != NETWORK_TYPE_ALL) {
+ builder.append(", ratType=").append(mRatType);
+ }
+ if (mOemManaged != OEM_MANAGED_ALL) {
+ builder.append(", oemManaged=").append(getOemManagedNames(mOemManaged));
+ }
+ builder.append(", subscriberIdMatchRule=")
+ .append(subscriberIdMatchRuleToString(mSubscriberIdMatchRule));
+ return builder.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mMatchRule, mSubscriberId, Arrays.hashCode(mMatchWifiNetworkKeys),
+ mMetered, mRoaming, mDefaultNetwork, mRatType, mOemManaged, mSubscriberIdMatchRule);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (obj instanceof NetworkTemplate) {
+ final NetworkTemplate other = (NetworkTemplate) obj;
+ return mMatchRule == other.mMatchRule
+ && Objects.equals(mSubscriberId, other.mSubscriberId)
+ && mMetered == other.mMetered
+ && mRoaming == other.mRoaming
+ && mDefaultNetwork == other.mDefaultNetwork
+ && mRatType == other.mRatType
+ && mOemManaged == other.mOemManaged
+ && mSubscriberIdMatchRule == other.mSubscriberIdMatchRule
+ && Arrays.equals(mMatchWifiNetworkKeys, other.mMatchWifiNetworkKeys);
+ }
+ return false;
+ }
+
+ private static String subscriberIdMatchRuleToString(int rule) {
+ switch (rule) {
+ case NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT:
+ return "EXACT_MATCH";
+ case NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_ALL:
+ return "ALL";
+ default:
+ return "Unknown rule " + rule;
+ }
+ }
+
+ /** @hide */
+ public boolean isMatchRuleMobile() {
+ switch (mMatchRule) {
+ case MATCH_MOBILE:
+ case MATCH_MOBILE_WILDCARD:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Get match rule of the template. See {@code MATCH_*}.
+ */
+ @UnsupportedAppUsage
+ public int getMatchRule() {
+ // Wildcard rules are not exposed. For external callers, convert wildcard rules to
+ // exposed rules before returning.
+ switch (mMatchRule) {
+ case MATCH_MOBILE_WILDCARD:
+ return MATCH_MOBILE;
+ case MATCH_WIFI_WILDCARD:
+ return MATCH_WIFI;
+ default:
+ return mMatchRule;
+ }
+ }
+
+ /**
+ * Get subscriber Id of the template.
+ * @hide
+ */
+ @Nullable
+ @UnsupportedAppUsage
+ public String getSubscriberId() {
+ return mSubscriberId;
+ }
+
+ /**
+ * Get set of subscriber Ids of the template.
+ */
+ @NonNull
+ public Set<String> getSubscriberIds() {
+ return new ArraySet<>(Arrays.asList(mMatchSubscriberIds));
+ }
+
+ /**
+ * Get the set of Wifi Network Keys of the template.
+ * See {@link WifiInfo#getNetworkKey()}.
+ */
+ @NonNull
+ public Set<String> getWifiNetworkKeys() {
+ return new ArraySet<>(Arrays.asList(mMatchWifiNetworkKeys));
+ }
+
+ /** @hide */
+ // TODO: Remove this and replace all callers with {@link #getWifiNetworkKeys()}.
+ @Nullable
+ public String getNetworkId() {
+ return getWifiNetworkKeys().isEmpty() ? null : getWifiNetworkKeys().iterator().next();
+ }
+
+ /**
+ * Get meteredness filter of the template.
+ */
+ @NetworkStats.Meteredness
+ public int getMeteredness() {
+ return mMetered;
+ }
+
+ /**
+ * Get roaming filter of the template.
+ */
+ @NetworkStats.Roaming
+ public int getRoaming() {
+ return mRoaming;
+ }
+
+ /**
+ * Get the default network status filter of the template.
+ */
+ @NetworkStats.DefaultNetwork
+ public int getDefaultNetworkStatus() {
+ return mDefaultNetwork;
+ }
+
+ /**
+ * Get the Radio Access Technology(RAT) type filter of the template.
+ */
+ public int getRatType() {
+ return mRatType;
+ }
+
+ /**
+ * Get the OEM managed filter of the template. See {@code OEM_MANAGED_*} or
+ * {@code android.net.NetworkIdentity#OEM_*}.
+ */
+ @OemManaged
+ public int getOemManaged() {
+ return mOemManaged;
+ }
+
+ /**
+ * Test if given {@link NetworkIdentity} matches this template.
+ *
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public boolean matches(@NonNull NetworkIdentity ident) {
+ Objects.requireNonNull(ident);
+ if (!matchesMetered(ident)) return false;
+ if (!matchesRoaming(ident)) return false;
+ if (!matchesDefaultNetwork(ident)) return false;
+ if (!matchesOemNetwork(ident)) return false;
+
+ switch (mMatchRule) {
+ case MATCH_MOBILE:
+ return matchesMobile(ident);
+ case MATCH_WIFI:
+ return matchesWifi(ident);
+ case MATCH_ETHERNET:
+ return matchesEthernet(ident);
+ case MATCH_MOBILE_WILDCARD:
+ return matchesMobileWildcard(ident);
+ case MATCH_WIFI_WILDCARD:
+ return matchesWifiWildcard(ident);
+ case MATCH_BLUETOOTH:
+ return matchesBluetooth(ident);
+ case MATCH_PROXY:
+ return matchesProxy(ident);
+ case MATCH_CARRIER:
+ return matchesCarrier(ident);
+ default:
+ // We have no idea what kind of network template we are, so we
+ // just claim not to match anything.
+ return false;
+ }
+ }
+
+ private boolean matchesMetered(NetworkIdentity ident) {
+ return (mMetered == METERED_ALL)
+ || (mMetered == METERED_YES && ident.mMetered)
+ || (mMetered == METERED_NO && !ident.mMetered);
+ }
+
+ private boolean matchesRoaming(NetworkIdentity ident) {
+ return (mRoaming == ROAMING_ALL)
+ || (mRoaming == ROAMING_YES && ident.mRoaming)
+ || (mRoaming == ROAMING_NO && !ident.mRoaming);
+ }
+
+ private boolean matchesDefaultNetwork(NetworkIdentity ident) {
+ return (mDefaultNetwork == DEFAULT_NETWORK_ALL)
+ || (mDefaultNetwork == DEFAULT_NETWORK_YES && ident.mDefaultNetwork)
+ || (mDefaultNetwork == DEFAULT_NETWORK_NO && !ident.mDefaultNetwork);
+ }
+
+ private boolean matchesOemNetwork(NetworkIdentity ident) {
+ return (mOemManaged == OEM_MANAGED_ALL)
+ || (mOemManaged == OEM_MANAGED_YES
+ && ident.mOemManaged != OEM_NONE)
+ || (mOemManaged == ident.mOemManaged);
+ }
+
+ private boolean matchesCollapsedRatType(NetworkIdentity ident) {
+ return mRatType == NETWORK_TYPE_ALL
+ || NetworkStatsManager.getCollapsedRatType(mRatType)
+ == NetworkStatsManager.getCollapsedRatType(ident.mRatType);
+ }
+
+ /**
+ * Check if this template matches {@code subscriberId}. Returns true if this
+ * template was created with {@code SUBSCRIBER_ID_MATCH_RULE_ALL}, or with a
+ * {@code mMatchSubscriberIds} array that contains {@code subscriberId}.
+ *
+ * @hide
+ */
+ public boolean matchesSubscriberId(@Nullable String subscriberId) {
+ return mSubscriberIdMatchRule == NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_ALL
+ || CollectionUtils.contains(mMatchSubscriberIds, subscriberId);
+ }
+
+ /**
+ * Check if network matches key of the wifi network.
+ * Returns true when the key matches, or when {@code mMatchWifiNetworkKeys} is
+ * empty.
+ *
+ * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getNetworkKey()}
+ * to know details about the key.
+ */
+ private boolean matchesWifiNetworkKey(@NonNull String wifiNetworkKey) {
+ Objects.requireNonNull(wifiNetworkKey);
+ return CollectionUtils.isEmpty(mMatchWifiNetworkKeys)
+ || CollectionUtils.contains(mMatchWifiNetworkKeys, wifiNetworkKey);
+ }
+
+ /**
+ * Check if mobile network matches IMSI.
+ */
+ private boolean matchesMobile(NetworkIdentity ident) {
+ if (ident.mType == TYPE_WIMAX) {
+ // TODO: consider matching against WiMAX subscriber identity
+ return true;
+ } else {
+ return ident.mType == TYPE_MOBILE && !CollectionUtils.isEmpty(mMatchSubscriberIds)
+ && CollectionUtils.contains(mMatchSubscriberIds, ident.mSubscriberId)
+ && matchesCollapsedRatType(ident);
+ }
+ }
+
+ /**
+ * Check if matches Wi-Fi network template.
+ */
+ private boolean matchesWifi(NetworkIdentity ident) {
+ switch (ident.mType) {
+ case TYPE_WIFI:
+ return matchesSubscriberId(ident.mSubscriberId)
+ && matchesWifiNetworkKey(ident.mWifiNetworkKey);
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Check if matches Ethernet network template.
+ */
+ private boolean matchesEthernet(NetworkIdentity ident) {
+ if (ident.mType == TYPE_ETHERNET) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Check if matches carrier network. The carrier networks means it includes the subscriberId.
+ */
+ private boolean matchesCarrier(NetworkIdentity ident) {
+ return ident.mSubscriberId != null
+ && !CollectionUtils.isEmpty(mMatchSubscriberIds)
+ && CollectionUtils.contains(mMatchSubscriberIds, ident.mSubscriberId);
+ }
+
+ private boolean matchesMobileWildcard(NetworkIdentity ident) {
+ if (ident.mType == TYPE_WIMAX) {
+ return true;
+ } else {
+ return ident.mType == TYPE_MOBILE && matchesCollapsedRatType(ident);
+ }
+ }
+
+ private boolean matchesWifiWildcard(NetworkIdentity ident) {
+ switch (ident.mType) {
+ case TYPE_WIFI:
+ case TYPE_WIFI_P2P:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Check if matches Bluetooth network template.
+ */
+ private boolean matchesBluetooth(NetworkIdentity ident) {
+ if (ident.mType == TYPE_BLUETOOTH) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Check if matches Proxy network template.
+ */
+ private boolean matchesProxy(NetworkIdentity ident) {
+ return ident.mType == TYPE_PROXY;
+ }
+
+ private static String getMatchRuleName(int matchRule) {
+ switch (matchRule) {
+ case MATCH_MOBILE:
+ return "MOBILE";
+ case MATCH_WIFI:
+ return "WIFI";
+ case MATCH_ETHERNET:
+ return "ETHERNET";
+ case MATCH_MOBILE_WILDCARD:
+ return "MOBILE_WILDCARD";
+ case MATCH_WIFI_WILDCARD:
+ return "WIFI_WILDCARD";
+ case MATCH_BLUETOOTH:
+ return "BLUETOOTH";
+ case MATCH_PROXY:
+ return "PROXY";
+ case MATCH_CARRIER:
+ return "CARRIER";
+ default:
+ return "UNKNOWN(" + matchRule + ")";
+ }
+ }
+
+ private static String getOemManagedNames(int oemManaged) {
+ switch (oemManaged) {
+ case OEM_MANAGED_ALL:
+ return "OEM_MANAGED_ALL";
+ case OEM_MANAGED_NO:
+ return "OEM_MANAGED_NO";
+ case OEM_MANAGED_YES:
+ return "OEM_MANAGED_YES";
+ default:
+ return NetworkIdentity.getOemManagedNames(oemManaged);
+ }
+ }
+
+ /**
+ * Examine the given template and normalize it.
+ * We pick the "lowest" merged subscriber as the primary
+ * for key purposes, and expand the template to match all other merged
+ * subscribers.
+ * <p>
+ * For example, given an incoming template matching B, and the currently
+ * active merge set [A,B], we'd return a new template that primarily matches
+ * A, but also matches B.
+ * TODO: remove and use {@link #normalize(NetworkTemplate, List)}.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static NetworkTemplate normalize(NetworkTemplate template, String[] merged) {
+ return normalize(template, Arrays.<String[]>asList(merged));
+ }
+
+ /**
+ * Examine the given template and normalize it.
+ * We pick the "lowest" merged subscriber as the primary
+ * for key purposes, and expand the template to match all other merged
+ * subscribers.
+ *
+ * There can be multiple merged subscriberIds for multi-SIM devices.
+ *
+ * <p>
+ * For example, given an incoming template matching B, and the currently
+ * active merge set [A,B], we'd return a new template that primarily matches
+ * A, but also matches B.
+ *
+ * @hide
+ */
+ // TODO: @SystemApi when ready.
+ public static NetworkTemplate normalize(NetworkTemplate template, List<String[]> mergedList) {
+ // Now there are several types of network which uses SubscriberId to store network
+ // information. For instances:
+ // The TYPE_WIFI with subscriberId means that it is a merged carrier wifi network.
+ // The TYPE_CARRIER means that the network associate to specific carrier network.
+
+ if (template.mSubscriberId == null) return template;
+
+ for (String[] merged : mergedList) {
+ if (CollectionUtils.contains(merged, template.mSubscriberId)) {
+ // Requested template subscriber is part of the merge group; return
+ // a template that matches all merged subscribers.
+ final String[] matchWifiNetworkKeys = template.mMatchWifiNetworkKeys;
+ return new NetworkTemplate(template.mMatchRule, merged[0], merged,
+ CollectionUtils.isEmpty(matchWifiNetworkKeys)
+ ? null : matchWifiNetworkKeys[0]);
+ }
+ }
+
+ return template;
+ }
+
+ @UnsupportedAppUsage
+ public static final @android.annotation.NonNull Creator<NetworkTemplate> CREATOR = new Creator<NetworkTemplate>() {
+ @Override
+ public NetworkTemplate createFromParcel(Parcel in) {
+ return new NetworkTemplate(in);
+ }
+
+ @Override
+ public NetworkTemplate[] newArray(int size) {
+ return new NetworkTemplate[size];
+ }
+ };
+
+ /**
+ * Builder class for NetworkTemplate.
+ */
+ public static final class Builder {
+ private final int mMatchRule;
+ // Use a SortedSet to provide a deterministic order when fetching the first one.
+ @NonNull
+ private final SortedSet<String> mMatchSubscriberIds =
+ new TreeSet<>(Comparator.nullsFirst(Comparator.naturalOrder()));
+ @NonNull
+ private final SortedSet<String> mMatchWifiNetworkKeys = new TreeSet<>();
+
+ // Matches for the NetworkStats constants METERED_*, ROAMING_* and DEFAULT_NETWORK_*.
+ private int mMetered;
+ private int mRoaming;
+ private int mDefaultNetwork;
+ private int mRatType;
+
+ // Bitfield containing OEM network properties {@code NetworkIdentity#OEM_*}.
+ private int mOemManaged;
+
+ /**
+ * Creates a new Builder with given match rule to construct NetworkTemplate objects.
+ *
+ * @param matchRule the match rule of the template, see {@code MATCH_*}.
+ */
+ public Builder(@TemplateMatchRule final int matchRule) {
+ assertRequestableMatchRule(matchRule);
+ // Initialize members with default values.
+ mMatchRule = matchRule;
+ mMetered = METERED_ALL;
+ mRoaming = ROAMING_ALL;
+ mDefaultNetwork = DEFAULT_NETWORK_ALL;
+ mRatType = NETWORK_TYPE_ALL;
+ mOemManaged = OEM_MANAGED_ALL;
+ }
+
+ /**
+ * Set the Subscriber Ids. Calling this function with an empty set represents
+ * the intention of matching any Subscriber Ids.
+ *
+ * @param subscriberIds the list of Subscriber Ids.
+ * @return this builder.
+ */
+ @NonNull
+ public Builder setSubscriberIds(@NonNull Set<String> subscriberIds) {
+ Objects.requireNonNull(subscriberIds);
+ mMatchSubscriberIds.clear();
+ mMatchSubscriberIds.addAll(subscriberIds);
+ return this;
+ }
+
+ /**
+ * Set the Wifi Network Keys. Calling this function with an empty set represents
+ * the intention of matching any Wifi Network Key.
+ *
+ * @param wifiNetworkKeys the list of Wifi Network Key,
+ * see {@link WifiInfo#getNetworkKey()}.
+ * Or an empty list to match all networks.
+ * Note that {@code getNetworkKey()} might get null key
+ * when wifi disconnects. However, the caller should never invoke
+ * this function with a null Wifi Network Key since such statistics
+ * never exists.
+ * @return this builder.
+ */
+ @NonNull
+ public Builder setWifiNetworkKeys(@NonNull Set<String> wifiNetworkKeys) {
+ Objects.requireNonNull(wifiNetworkKeys);
+ for (String key : wifiNetworkKeys) {
+ if (key == null) {
+ throw new IllegalArgumentException("Null is not a valid key");
+ }
+ }
+ mMatchWifiNetworkKeys.clear();
+ mMatchWifiNetworkKeys.addAll(wifiNetworkKeys);
+ return this;
+ }
+
+ /**
+ * Set the meteredness filter.
+ *
+ * @param metered the meteredness filter.
+ * @return this builder.
+ */
+ @NonNull
+ public Builder setMeteredness(@NetworkStats.Meteredness int metered) {
+ mMetered = metered;
+ return this;
+ }
+
+ /**
+ * Set the roaming filter.
+ *
+ * @param roaming the roaming filter.
+ * @return this builder.
+ */
+ @NonNull
+ public Builder setRoaming(@NetworkStats.Roaming int roaming) {
+ mRoaming = roaming;
+ return this;
+ }
+
+ /**
+ * Set the default network status filter.
+ *
+ * @param defaultNetwork the default network status filter.
+ * @return this builder.
+ */
+ @NonNull
+ public Builder setDefaultNetworkStatus(@NetworkStats.DefaultNetwork int defaultNetwork) {
+ mDefaultNetwork = defaultNetwork;
+ return this;
+ }
+
+ /**
+ * Set the Radio Access Technology(RAT) type filter.
+ *
+ * @param ratType the Radio Access Technology(RAT) type filter. Use
+ * {@link #NETWORK_TYPE_ALL} to include all network types when filtering.
+ * See {@code TelephonyManager.NETWORK_TYPE_*}.
+ * @return this builder.
+ */
+ @NonNull
+ public Builder setRatType(int ratType) {
+ // Input will be validated with the match rule when building the template.
+ mRatType = ratType;
+ return this;
+ }
+
+ /**
+ * Set the OEM managed filter.
+ *
+ * @param oemManaged the match rule to match different type of OEM managed network or
+ * unmanaged networks. See {@code OEM_MANAGED_*}.
+ * @return this builder.
+ */
+ @NonNull
+ public Builder setOemManaged(@OemManaged int oemManaged) {
+ mOemManaged = oemManaged;
+ return this;
+ }
+
+ /**
+ * Check whether the match rule is requestable.
+ *
+ * @param matchRule the target match rule to be checked.
+ */
+ private static void assertRequestableMatchRule(final int matchRule) {
+ if (!isKnownMatchRule(matchRule)
+ || matchRule == MATCH_PROXY
+ || matchRule == MATCH_MOBILE_WILDCARD
+ || matchRule == MATCH_WIFI_WILDCARD) {
+ throw new IllegalArgumentException("Invalid match rule: "
+ + getMatchRuleName(matchRule));
+ }
+ }
+
+ private void assertRequestableParameters() {
+ validateWifiNetworkKeys();
+ // TODO: Check all the input are legitimate.
+ }
+
+ private void validateWifiNetworkKeys() {
+ if (mMatchRule != MATCH_WIFI && !mMatchWifiNetworkKeys.isEmpty()) {
+ throw new IllegalArgumentException("Trying to build non wifi match rule: "
+ + mMatchRule + " with wifi network keys");
+ }
+ }
+
+ /**
+ * For backward compatibility, deduce match rule to a wildcard match rule
+ * if the Subscriber Ids are empty.
+ */
+ private int getWildcardDeducedMatchRule() {
+ if (mMatchRule == MATCH_MOBILE && mMatchSubscriberIds.isEmpty()) {
+ return MATCH_MOBILE_WILDCARD;
+ } else if (mMatchRule == MATCH_WIFI && mMatchSubscriberIds.isEmpty()
+ && mMatchWifiNetworkKeys.isEmpty()) {
+ return MATCH_WIFI_WILDCARD;
+ }
+ return mMatchRule;
+ }
+
+ /**
+ * Builds the instance of the NetworkTemplate.
+ *
+ * @return the built instance of NetworkTemplate.
+ */
+ @NonNull
+ public NetworkTemplate build() {
+ assertRequestableParameters();
+ final int subscriberIdMatchRule = mMatchSubscriberIds.isEmpty()
+ ? NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_ALL
+ : NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT;
+ return new NetworkTemplate(getWildcardDeducedMatchRule(),
+ mMatchSubscriberIds.isEmpty() ? null : mMatchSubscriberIds.iterator().next(),
+ mMatchSubscriberIds.toArray(new String[0]),
+ mMatchWifiNetworkKeys.toArray(new String[0]), mMetered, mRoaming,
+ mDefaultNetwork, mRatType, mOemManaged, subscriberIdMatchRule);
+ }
+ }
+}
diff --git a/framework-t/src/android/net/TrafficStats.java b/framework-t/src/android/net/TrafficStats.java
new file mode 100644
index 0000000..dc4ac55
--- /dev/null
+++ b/framework-t/src/android/net/TrafficStats.java
@@ -0,0 +1,1148 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.app.DownloadManager;
+import android.app.backup.BackupManager;
+import android.app.usage.NetworkStatsManager;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.media.MediaPlayer;
+import android.os.Binder;
+import android.os.Build;
+import android.os.RemoteException;
+import android.os.StrictMode;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.net.DatagramSocket;
+import java.net.Socket;
+import java.net.SocketException;
+
+/**
+ * Class that provides network traffic statistics. These statistics include
+ * bytes transmitted and received and network packets transmitted and received,
+ * over all interfaces, over the mobile interface, and on a per-UID basis.
+ * <p>
+ * These statistics may not be available on all platforms. If the statistics are
+ * not supported by this device, {@link #UNSUPPORTED} will be returned.
+ * <p>
+ * Note that the statistics returned by this class reset and start from zero
+ * after every reboot. To access more robust historical network statistics data,
+ * use {@link NetworkStatsManager} instead.
+ */
+public class TrafficStats {
+ static {
+ System.loadLibrary("framework-connectivity-tiramisu-jni");
+ }
+
+ private static final String TAG = TrafficStats.class.getSimpleName();
+ /**
+ * The return value to indicate that the device does not support the statistic.
+ */
+ public final static int UNSUPPORTED = -1;
+
+ /** @hide @deprecated use {@code DataUnit} instead to clarify SI-vs-IEC */
+ @Deprecated
+ public static final long KB_IN_BYTES = 1024;
+ /** @hide @deprecated use {@code DataUnit} instead to clarify SI-vs-IEC */
+ @Deprecated
+ public static final long MB_IN_BYTES = KB_IN_BYTES * 1024;
+ /** @hide @deprecated use {@code DataUnit} instead to clarify SI-vs-IEC */
+ @Deprecated
+ public static final long GB_IN_BYTES = MB_IN_BYTES * 1024;
+ /** @hide @deprecated use {@code DataUnit} instead to clarify SI-vs-IEC */
+ @Deprecated
+ public static final long TB_IN_BYTES = GB_IN_BYTES * 1024;
+ /** @hide @deprecated use {@code DataUnit} instead to clarify SI-vs-IEC */
+ @Deprecated
+ public static final long PB_IN_BYTES = TB_IN_BYTES * 1024;
+
+ /**
+ * Special UID value used when collecting {@link NetworkStatsHistory} for
+ * removed applications.
+ *
+ * @hide
+ */
+ public static final int UID_REMOVED = -4;
+
+ /**
+ * Special UID value used when collecting {@link NetworkStatsHistory} for
+ * tethering traffic.
+ *
+ * @hide
+ */
+ public static final int UID_TETHERING = NetworkStats.UID_TETHERING;
+
+ /**
+ * Tag values in this range are reserved for the network stack. The network stack is
+ * running as UID {@link android.os.Process.NETWORK_STACK_UID} when in the mainline
+ * module separate process, and as the system UID otherwise.
+ */
+ /** @hide */
+ @SystemApi
+ public static final int TAG_NETWORK_STACK_RANGE_START = 0xFFFFFD00;
+ /** @hide */
+ @SystemApi
+ public static final int TAG_NETWORK_STACK_RANGE_END = 0xFFFFFEFF;
+
+ /**
+ * Tags between 0xFFFFFF00 and 0xFFFFFFFF are reserved and used internally by system services
+ * like DownloadManager when performing traffic on behalf of an application.
+ */
+ // Please note there is no enforcement of these constants, so do not rely on them to
+ // determine that the caller is a system caller.
+ /** @hide */
+ @SystemApi
+ public static final int TAG_SYSTEM_IMPERSONATION_RANGE_START = 0xFFFFFF00;
+ /** @hide */
+ @SystemApi
+ public static final int TAG_SYSTEM_IMPERSONATION_RANGE_END = 0xFFFFFF0F;
+
+ /**
+ * Tag values between these ranges are reserved for the network stack to do traffic
+ * on behalf of applications. It is a subrange of the range above.
+ */
+ /** @hide */
+ @SystemApi
+ public static final int TAG_NETWORK_STACK_IMPERSONATION_RANGE_START = 0xFFFFFF80;
+ /** @hide */
+ @SystemApi
+ public static final int TAG_NETWORK_STACK_IMPERSONATION_RANGE_END = 0xFFFFFF8F;
+
+ /**
+ * Default tag value for {@link DownloadManager} traffic.
+ *
+ * @hide
+ */
+ public static final int TAG_SYSTEM_DOWNLOAD = 0xFFFFFF01;
+
+ /**
+ * Default tag value for {@link MediaPlayer} traffic.
+ *
+ * @hide
+ */
+ public static final int TAG_SYSTEM_MEDIA = 0xFFFFFF02;
+
+ /**
+ * Default tag value for {@link BackupManager} backup traffic; that is,
+ * traffic from the device to the storage backend.
+ *
+ * @hide
+ */
+ public static final int TAG_SYSTEM_BACKUP = 0xFFFFFF03;
+
+ /**
+ * Default tag value for {@link BackupManager} restore traffic; that is,
+ * app data retrieved from the storage backend at install time.
+ *
+ * @hide
+ */
+ public static final int TAG_SYSTEM_RESTORE = 0xFFFFFF04;
+
+ /**
+ * Default tag value for code (typically APKs) downloaded by an app store on
+ * behalf of the app, such as updates.
+ *
+ * @hide
+ */
+ public static final int TAG_SYSTEM_APP = 0xFFFFFF05;
+
+ // TODO : remove this constant when Wifi code is updated
+ /** @hide */
+ public static final int TAG_SYSTEM_PROBE = 0xFFFFFF42;
+
+ private static INetworkStatsService sStatsService;
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
+ private synchronized static INetworkStatsService getStatsService() {
+ if (sStatsService == null) {
+ throw new IllegalStateException("TrafficStats not initialized, uid="
+ + Binder.getCallingUid());
+ }
+ return sStatsService;
+ }
+
+ /**
+ * Snapshot of {@link NetworkStats} when the currently active profiling
+ * session started, or {@code null} if no session active.
+ *
+ * @see #startDataProfiling(Context)
+ * @see #stopDataProfiling(Context)
+ */
+ private static NetworkStats sActiveProfilingStart;
+
+ private static Object sProfilingLock = new Object();
+
+ private static final String LOOPBACK_IFACE = "lo";
+
+ /**
+ * Initialization {@link TrafficStats} with the context, to
+ * allow {@link TrafficStats} to fetch the needed binder.
+ *
+ * @param context a long-lived context, such as the application context or system
+ * server context.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ @SuppressLint("VisiblySynchronized")
+ public static synchronized void init(@NonNull final Context context) {
+ if (sStatsService != null) {
+ throw new IllegalStateException("TrafficStats is already initialized, uid="
+ + Binder.getCallingUid());
+ }
+ final NetworkStatsManager statsManager =
+ context.getSystemService(NetworkStatsManager.class);
+ if (statsManager == null) {
+ // TODO: Currently Process.isSupplemental is not working yet, because it depends on
+ // process to run in a certain UID range, which is not true for now. Change this
+ // to Log.wtf once Process.isSupplemental is ready.
+ Log.e(TAG, "TrafficStats not initialized, uid=" + Binder.getCallingUid());
+ return;
+ }
+ sStatsService = statsManager.getBinder();
+ }
+
+ /**
+ * Attach the socket tagger implementation to the current process, to
+ * get notified when a socket's {@link FileDescriptor} is assigned to
+ * a thread. See {@link SocketTagger#set(SocketTagger)}.
+ *
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static void attachSocketTagger() {
+ dalvik.system.SocketTagger.set(new SocketTagger());
+ }
+
+ private static class SocketTagger extends dalvik.system.SocketTagger {
+
+ // TODO: set to false
+ private static final boolean LOGD = true;
+
+ SocketTagger() {
+ }
+
+ @Override
+ public void tag(FileDescriptor fd) throws SocketException {
+ final UidTag tagInfo = sThreadUidTag.get();
+ if (LOGD) {
+ Log.d(TAG, "tagSocket(" + fd.getInt$() + ") with statsTag=0x"
+ + Integer.toHexString(tagInfo.tag) + ", statsUid=" + tagInfo.uid);
+ }
+ if (tagInfo.tag == -1) {
+ StrictMode.noteUntaggedSocket();
+ }
+
+ if (tagInfo.tag == -1 && tagInfo.uid == -1) return;
+ final int errno = native_tagSocketFd(fd, tagInfo.tag, tagInfo.uid);
+ if (errno < 0) {
+ Log.i(TAG, "tagSocketFd(" + fd.getInt$() + ", "
+ + tagInfo.tag + ", "
+ + tagInfo.uid + ") failed with errno" + errno);
+ }
+ }
+
+ @Override
+ public void untag(FileDescriptor fd) throws SocketException {
+ if (LOGD) {
+ Log.i(TAG, "untagSocket(" + fd.getInt$() + ")");
+ }
+
+ final UidTag tagInfo = sThreadUidTag.get();
+ if (tagInfo.tag == -1 && tagInfo.uid == -1) return;
+
+ final int errno = native_untagSocketFd(fd);
+ if (errno < 0) {
+ Log.w(TAG, "untagSocket(" + fd.getInt$() + ") failed with errno " + errno);
+ }
+ }
+ }
+
+ private static native int native_tagSocketFd(FileDescriptor fd, int tag, int uid);
+ private static native int native_untagSocketFd(FileDescriptor fd);
+
+ private static class UidTag {
+ public int tag = -1;
+ public int uid = -1;
+ }
+
+ private static ThreadLocal<UidTag> sThreadUidTag = new ThreadLocal<UidTag>() {
+ @Override
+ protected UidTag initialValue() {
+ return new UidTag();
+ }
+ };
+
+ /**
+ * Set active tag to use when accounting {@link Socket} traffic originating
+ * from the current thread. Only one active tag per thread is supported.
+ * <p>
+ * Changes only take effect during subsequent calls to
+ * {@link #tagSocket(Socket)}.
+ * <p>
+ * Tags between {@code 0xFFFFFF00} and {@code 0xFFFFFFFF} are reserved and
+ * used internally by system services like {@link DownloadManager} when
+ * performing traffic on behalf of an application.
+ *
+ * @see #clearThreadStatsTag()
+ */
+ public static void setThreadStatsTag(int tag) {
+ getAndSetThreadStatsTag(tag);
+ }
+
+ /**
+ * Set active tag to use when accounting {@link Socket} traffic originating
+ * from the current thread. Only one active tag per thread is supported.
+ * <p>
+ * Changes only take effect during subsequent calls to
+ * {@link #tagSocket(Socket)}.
+ * <p>
+ * Tags between {@code 0xFFFFFF00} and {@code 0xFFFFFFFF} are reserved and
+ * used internally by system services like {@link DownloadManager} when
+ * performing traffic on behalf of an application.
+ *
+ * @return the current tag for the calling thread, which can be used to
+ * restore any existing values after a nested operation is finished
+ */
+ public static int getAndSetThreadStatsTag(int tag) {
+ final int old = sThreadUidTag.get().tag;
+ sThreadUidTag.get().tag = tag;
+ return old;
+ }
+
+ /**
+ * Set active tag to use when accounting {@link Socket} traffic originating
+ * from the current thread. The tag used internally is well-defined to
+ * distinguish all backup-related traffic.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static void setThreadStatsTagBackup() {
+ setThreadStatsTag(TAG_SYSTEM_BACKUP);
+ }
+
+ /**
+ * Set active tag to use when accounting {@link Socket} traffic originating
+ * from the current thread. The tag used internally is well-defined to
+ * distinguish all restore-related traffic.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static void setThreadStatsTagRestore() {
+ setThreadStatsTag(TAG_SYSTEM_RESTORE);
+ }
+
+ /**
+ * Set active tag to use when accounting {@link Socket} traffic originating
+ * from the current thread. The tag used internally is well-defined to
+ * distinguish all code (typically APKs) downloaded by an app store on
+ * behalf of the app, such as updates.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static void setThreadStatsTagApp() {
+ setThreadStatsTag(TAG_SYSTEM_APP);
+ }
+
+ /**
+ * Set active tag to use when accounting {@link Socket} traffic originating
+ * from the current thread. The tag used internally is well-defined to
+ * distinguish all download provider traffic.
+ *
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static void setThreadStatsTagDownload() {
+ setThreadStatsTag(TAG_SYSTEM_DOWNLOAD);
+ }
+
+ /**
+ * Get the active tag used when accounting {@link Socket} traffic originating
+ * from the current thread. Only one active tag per thread is supported.
+ * {@link #tagSocket(Socket)}.
+ *
+ * @see #setThreadStatsTag(int)
+ */
+ public static int getThreadStatsTag() {
+ return sThreadUidTag.get().tag;
+ }
+
+ /**
+ * Clear any active tag set to account {@link Socket} traffic originating
+ * from the current thread.
+ *
+ * @see #setThreadStatsTag(int)
+ */
+ public static void clearThreadStatsTag() {
+ sThreadUidTag.get().tag = -1;
+ }
+
+ /**
+ * Set specific UID to use when accounting {@link Socket} traffic
+ * originating from the current thread. Designed for use when performing an
+ * operation on behalf of another application, or when another application
+ * is performing operations on your behalf.
+ * <p>
+ * Any app can <em>accept</em> blame for traffic performed on a socket
+ * originally created by another app by calling this method with the
+ * {@link android.system.Os#getuid()} value. However, only apps holding the
+ * {@code android.Manifest.permission#UPDATE_DEVICE_STATS} permission may
+ * <em>assign</em> blame to another UIDs.
+ * <p>
+ * Changes only take effect during subsequent calls to
+ * {@link #tagSocket(Socket)}.
+ */
+ @SuppressLint("RequiresPermission")
+ public static void setThreadStatsUid(int uid) {
+ sThreadUidTag.get().uid = uid;
+ }
+
+ /**
+ * Get the active UID used when accounting {@link Socket} traffic originating
+ * from the current thread. Only one active tag per thread is supported.
+ * {@link #tagSocket(Socket)}.
+ *
+ * @see #setThreadStatsUid(int)
+ */
+ public static int getThreadStatsUid() {
+ return sThreadUidTag.get().uid;
+ }
+
+ /**
+ * Set specific UID to use when accounting {@link Socket} traffic
+ * originating from the current thread as the calling UID. Designed for use
+ * when another application is performing operations on your behalf.
+ * <p>
+ * Changes only take effect during subsequent calls to
+ * {@link #tagSocket(Socket)}.
+ *
+ * @removed
+ * @deprecated use {@link #setThreadStatsUid(int)} instead.
+ */
+ @Deprecated
+ public static void setThreadStatsUidSelf() {
+ setThreadStatsUid(android.os.Process.myUid());
+ }
+
+ /**
+ * Clear any active UID set to account {@link Socket} traffic originating
+ * from the current thread.
+ *
+ * @see #setThreadStatsUid(int)
+ */
+ @SuppressLint("RequiresPermission")
+ public static void clearThreadStatsUid() {
+ setThreadStatsUid(-1);
+ }
+
+ /**
+ * Tag the given {@link Socket} with any statistics parameters active for
+ * the current thread. Subsequent calls always replace any existing
+ * parameters. When finished, call {@link #untagSocket(Socket)} to remove
+ * statistics parameters.
+ *
+ * @see #setThreadStatsTag(int)
+ */
+ public static void tagSocket(@NonNull Socket socket) throws SocketException {
+ SocketTagger.get().tag(socket);
+ }
+
+ /**
+ * Remove any statistics parameters from the given {@link Socket}.
+ * <p>
+ * In Android 8.1 (API level 27) and lower, a socket is automatically
+ * untagged when it's sent to another process using binder IPC with a
+ * {@code ParcelFileDescriptor} container. In Android 9.0 (API level 28)
+ * and higher, the socket tag is kept when the socket is sent to another
+ * process using binder IPC. You can mimic the previous behavior by
+ * calling {@code untagSocket()} before sending the socket to another
+ * process.
+ */
+ public static void untagSocket(@NonNull Socket socket) throws SocketException {
+ SocketTagger.get().untag(socket);
+ }
+
+ /**
+ * Tag the given {@link DatagramSocket} with any statistics parameters
+ * active for the current thread. Subsequent calls always replace any
+ * existing parameters. When finished, call
+ * {@link #untagDatagramSocket(DatagramSocket)} to remove statistics
+ * parameters.
+ *
+ * @see #setThreadStatsTag(int)
+ */
+ public static void tagDatagramSocket(@NonNull DatagramSocket socket) throws SocketException {
+ SocketTagger.get().tag(socket);
+ }
+
+ /**
+ * Remove any statistics parameters from the given {@link DatagramSocket}.
+ */
+ public static void untagDatagramSocket(@NonNull DatagramSocket socket) throws SocketException {
+ SocketTagger.get().untag(socket);
+ }
+
+ /**
+ * Tag the given {@link FileDescriptor} socket with any statistics
+ * parameters active for the current thread. Subsequent calls always replace
+ * any existing parameters. When finished, call
+ * {@link #untagFileDescriptor(FileDescriptor)} to remove statistics
+ * parameters.
+ *
+ * @see #setThreadStatsTag(int)
+ */
+ public static void tagFileDescriptor(@NonNull FileDescriptor fd) throws IOException {
+ SocketTagger.get().tag(fd);
+ }
+
+ /**
+ * Remove any statistics parameters from the given {@link FileDescriptor}
+ * socket.
+ */
+ public static void untagFileDescriptor(@NonNull FileDescriptor fd) throws IOException {
+ SocketTagger.get().untag(fd);
+ }
+
+ /**
+ * Start profiling data usage for current UID. Only one profiling session
+ * can be active at a time.
+ *
+ * @hide
+ */
+ public static void startDataProfiling(Context context) {
+ synchronized (sProfilingLock) {
+ if (sActiveProfilingStart != null) {
+ throw new IllegalStateException("already profiling data");
+ }
+
+ // take snapshot in time; we calculate delta later
+ sActiveProfilingStart = getDataLayerSnapshotForUid(context);
+ }
+ }
+
+ /**
+ * Stop profiling data usage for current UID.
+ *
+ * @return Detailed {@link NetworkStats} of data that occurred since last
+ * {@link #startDataProfiling(Context)} call.
+ * @hide
+ */
+ public static NetworkStats stopDataProfiling(Context context) {
+ synchronized (sProfilingLock) {
+ if (sActiveProfilingStart == null) {
+ throw new IllegalStateException("not profiling data");
+ }
+
+ // subtract starting values and return delta
+ final NetworkStats profilingStop = getDataLayerSnapshotForUid(context);
+ final NetworkStats profilingDelta = NetworkStats.subtract(
+ profilingStop, sActiveProfilingStart, null, null);
+ sActiveProfilingStart = null;
+ return profilingDelta;
+ }
+ }
+
+ /**
+ * Increment count of network operations performed under the accounting tag
+ * currently active on the calling thread. This can be used to derive
+ * bytes-per-operation.
+ *
+ * @param operationCount Number of operations to increment count by.
+ */
+ public static void incrementOperationCount(int operationCount) {
+ final int tag = getThreadStatsTag();
+ incrementOperationCount(tag, operationCount);
+ }
+
+ /**
+ * Increment count of network operations performed under the given
+ * accounting tag. This can be used to derive bytes-per-operation.
+ *
+ * @param tag Accounting tag used in {@link #setThreadStatsTag(int)}.
+ * @param operationCount Number of operations to increment count by.
+ */
+ public static void incrementOperationCount(int tag, int operationCount) {
+ final int uid = android.os.Process.myUid();
+ try {
+ getStatsService().incrementOperationCount(uid, tag, operationCount);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** {@hide} */
+ public static void closeQuietly(INetworkStatsSession session) {
+ // TODO: move to NetworkStatsService once it exists
+ if (session != null) {
+ try {
+ session.close();
+ } catch (RuntimeException rethrown) {
+ throw rethrown;
+ } catch (Exception ignored) {
+ }
+ }
+ }
+
+ private static long addIfSupported(long stat) {
+ return (stat == UNSUPPORTED) ? 0 : stat;
+ }
+
+ /**
+ * Return number of packets transmitted across mobile networks since device
+ * boot. Counts packets across all mobile network interfaces, and always
+ * increases monotonically since device boot. Statistics are measured at the
+ * network layer, so they include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may
+ * return {@link #UNSUPPORTED} on devices where statistics aren't available.
+ */
+ public static long getMobileTxPackets() {
+ long total = 0;
+ for (String iface : getMobileIfaces()) {
+ total += addIfSupported(getTxPackets(iface));
+ }
+ return total;
+ }
+
+ /**
+ * Return number of packets received across mobile networks since device
+ * boot. Counts packets across all mobile network interfaces, and always
+ * increases monotonically since device boot. Statistics are measured at the
+ * network layer, so they include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may
+ * return {@link #UNSUPPORTED} on devices where statistics aren't available.
+ */
+ public static long getMobileRxPackets() {
+ long total = 0;
+ for (String iface : getMobileIfaces()) {
+ total += addIfSupported(getRxPackets(iface));
+ }
+ return total;
+ }
+
+ /**
+ * Return number of bytes transmitted across mobile networks since device
+ * boot. Counts packets across all mobile network interfaces, and always
+ * increases monotonically since device boot. Statistics are measured at the
+ * network layer, so they include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may
+ * return {@link #UNSUPPORTED} on devices where statistics aren't available.
+ */
+ public static long getMobileTxBytes() {
+ long total = 0;
+ for (String iface : getMobileIfaces()) {
+ total += addIfSupported(getTxBytes(iface));
+ }
+ return total;
+ }
+
+ /**
+ * Return number of bytes received across mobile networks since device boot.
+ * Counts packets across all mobile network interfaces, and always increases
+ * monotonically since device boot. Statistics are measured at the network
+ * layer, so they include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may
+ * return {@link #UNSUPPORTED} on devices where statistics aren't available.
+ */
+ public static long getMobileRxBytes() {
+ long total = 0;
+ for (String iface : getMobileIfaces()) {
+ total += addIfSupported(getRxBytes(iface));
+ }
+ return total;
+ }
+
+ /** {@hide} */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static long getMobileTcpRxPackets() {
+ long total = 0;
+ for (String iface : getMobileIfaces()) {
+ long stat = UNSUPPORTED;
+ try {
+ stat = getStatsService().getIfaceStats(iface, TYPE_TCP_RX_PACKETS);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ total += addIfSupported(stat);
+ }
+ return total;
+ }
+
+ /** {@hide} */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static long getMobileTcpTxPackets() {
+ long total = 0;
+ for (String iface : getMobileIfaces()) {
+ long stat = UNSUPPORTED;
+ try {
+ stat = getStatsService().getIfaceStats(iface, TYPE_TCP_TX_PACKETS);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ total += addIfSupported(stat);
+ }
+ return total;
+ }
+
+ /**
+ * Return the number of packets transmitted on the specified interface since the interface
+ * was created. Statistics are measured at the network layer, so both TCP and
+ * UDP usage are included.
+ *
+ * Note that the returned values are partial statistics that do not count data from several
+ * sources and do not apply several adjustments that are necessary for correctness, such
+ * as adjusting for VPN apps, IPv6-in-IPv4 translation, etc. These values can be used to
+ * determine whether traffic is being transferred on the specific interface but are not a
+ * substitute for the more accurate statistics provided by the {@link NetworkStatsManager}
+ * APIs.
+ *
+ * @param iface The name of the interface.
+ * @return The number of transmitted packets.
+ */
+ public static long getTxPackets(@NonNull String iface) {
+ try {
+ return getStatsService().getIfaceStats(iface, TYPE_TX_PACKETS);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return the number of packets received on the specified interface since the interface was
+ * created. Statistics are measured at the network layer, so both TCP
+ * and UDP usage are included.
+ *
+ * Note that the returned values are partial statistics that do not count data from several
+ * sources and do not apply several adjustments that are necessary for correctness, such
+ * as adjusting for VPN apps, IPv6-in-IPv4 translation, etc. These values can be used to
+ * determine whether traffic is being transferred on the specific interface but are not a
+ * substitute for the more accurate statistics provided by the {@link NetworkStatsManager}
+ * APIs.
+ *
+ * @param iface The name of the interface.
+ * @return The number of received packets.
+ */
+ public static long getRxPackets(@NonNull String iface) {
+ try {
+ return getStatsService().getIfaceStats(iface, TYPE_RX_PACKETS);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return the number of bytes transmitted on the specified interface since the interface
+ * was created. Statistics are measured at the network layer, so both TCP and
+ * UDP usage are included.
+ *
+ * Note that the returned values are partial statistics that do not count data from several
+ * sources and do not apply several adjustments that are necessary for correctness, such
+ * as adjusting for VPN apps, IPv6-in-IPv4 translation, etc. These values can be used to
+ * determine whether traffic is being transferred on the specific interface but are not a
+ * substitute for the more accurate statistics provided by the {@link NetworkStatsManager}
+ * APIs.
+ *
+ * @param iface The name of the interface.
+ * @return The number of transmitted bytes.
+ */
+ public static long getTxBytes(@NonNull String iface) {
+ try {
+ return getStatsService().getIfaceStats(iface, TYPE_TX_BYTES);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return the number of bytes received on the specified interface since the interface
+ * was created. Statistics are measured at the network layer, so both TCP
+ * and UDP usage are included.
+ *
+ * Note that the returned values are partial statistics that do not count data from several
+ * sources and do not apply several adjustments that are necessary for correctness, such
+ * as adjusting for VPN apps, IPv6-in-IPv4 translation, etc. These values can be used to
+ * determine whether traffic is being transferred on the specific interface but are not a
+ * substitute for the more accurate statistics provided by the {@link NetworkStatsManager}
+ * APIs.
+ *
+ * @param iface The name of the interface.
+ * @return The number of received bytes.
+ */
+ public static long getRxBytes(@NonNull String iface) {
+ try {
+ return getStatsService().getIfaceStats(iface, TYPE_RX_BYTES);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** {@hide} */
+ @TestApi
+ public static long getLoopbackTxPackets() {
+ try {
+ return getStatsService().getIfaceStats(LOOPBACK_IFACE, TYPE_TX_PACKETS);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** {@hide} */
+ @TestApi
+ public static long getLoopbackRxPackets() {
+ try {
+ return getStatsService().getIfaceStats(LOOPBACK_IFACE, TYPE_RX_PACKETS);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** {@hide} */
+ @TestApi
+ public static long getLoopbackTxBytes() {
+ try {
+ return getStatsService().getIfaceStats(LOOPBACK_IFACE, TYPE_TX_BYTES);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** {@hide} */
+ @TestApi
+ public static long getLoopbackRxBytes() {
+ try {
+ return getStatsService().getIfaceStats(LOOPBACK_IFACE, TYPE_RX_BYTES);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return number of packets transmitted since device boot. Counts packets
+ * across all network interfaces, and always increases monotonically since
+ * device boot. Statistics are measured at the network layer, so they
+ * include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may
+ * return {@link #UNSUPPORTED} on devices where statistics aren't available.
+ */
+ public static long getTotalTxPackets() {
+ try {
+ return getStatsService().getTotalStats(TYPE_TX_PACKETS);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return number of packets received since device boot. Counts packets
+ * across all network interfaces, and always increases monotonically since
+ * device boot. Statistics are measured at the network layer, so they
+ * include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may
+ * return {@link #UNSUPPORTED} on devices where statistics aren't available.
+ */
+ public static long getTotalRxPackets() {
+ try {
+ return getStatsService().getTotalStats(TYPE_RX_PACKETS);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return number of bytes transmitted since device boot. Counts packets
+ * across all network interfaces, and always increases monotonically since
+ * device boot. Statistics are measured at the network layer, so they
+ * include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may
+ * return {@link #UNSUPPORTED} on devices where statistics aren't available.
+ */
+ public static long getTotalTxBytes() {
+ try {
+ return getStatsService().getTotalStats(TYPE_TX_BYTES);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return number of bytes received since device boot. Counts packets across
+ * all network interfaces, and always increases monotonically since device
+ * boot. Statistics are measured at the network layer, so they include both
+ * TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may
+ * return {@link #UNSUPPORTED} on devices where statistics aren't available.
+ */
+ public static long getTotalRxBytes() {
+ try {
+ return getStatsService().getTotalStats(TYPE_RX_BYTES);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return number of bytes transmitted by the given UID since device boot.
+ * Counts packets across all network interfaces, and always increases
+ * monotonically since device boot. Statistics are measured at the network
+ * layer, so they include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may
+ * return {@link #UNSUPPORTED} on devices where statistics aren't available.
+ * <p>
+ * Starting in {@link android.os.Build.VERSION_CODES#N} this will only
+ * report traffic statistics for the calling UID. It will return
+ * {@link #UNSUPPORTED} for all other UIDs for privacy reasons. To access
+ * historical network statistics belonging to other UIDs, use
+ * {@link NetworkStatsManager}.
+ *
+ * @see android.os.Process#myUid()
+ * @see android.content.pm.ApplicationInfo#uid
+ */
+ public static long getUidTxBytes(int uid) {
+ try {
+ return getStatsService().getUidStats(uid, TYPE_TX_BYTES);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return number of bytes received by the given UID since device boot.
+ * Counts packets across all network interfaces, and always increases
+ * monotonically since device boot. Statistics are measured at the network
+ * layer, so they include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may return
+ * {@link #UNSUPPORTED} on devices where statistics aren't available.
+ * <p>
+ * Starting in {@link android.os.Build.VERSION_CODES#N} this will only
+ * report traffic statistics for the calling UID. It will return
+ * {@link #UNSUPPORTED} for all other UIDs for privacy reasons. To access
+ * historical network statistics belonging to other UIDs, use
+ * {@link NetworkStatsManager}.
+ *
+ * @see android.os.Process#myUid()
+ * @see android.content.pm.ApplicationInfo#uid
+ */
+ public static long getUidRxBytes(int uid) {
+ try {
+ return getStatsService().getUidStats(uid, TYPE_RX_BYTES);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return number of packets transmitted by the given UID since device boot.
+ * Counts packets across all network interfaces, and always increases
+ * monotonically since device boot. Statistics are measured at the network
+ * layer, so they include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may return
+ * {@link #UNSUPPORTED} on devices where statistics aren't available.
+ * <p>
+ * Starting in {@link android.os.Build.VERSION_CODES#N} this will only
+ * report traffic statistics for the calling UID. It will return
+ * {@link #UNSUPPORTED} for all other UIDs for privacy reasons. To access
+ * historical network statistics belonging to other UIDs, use
+ * {@link NetworkStatsManager}.
+ *
+ * @see android.os.Process#myUid()
+ * @see android.content.pm.ApplicationInfo#uid
+ */
+ public static long getUidTxPackets(int uid) {
+ try {
+ return getStatsService().getUidStats(uid, TYPE_TX_PACKETS);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return number of packets received by the given UID since device boot.
+ * Counts packets across all network interfaces, and always increases
+ * monotonically since device boot. Statistics are measured at the network
+ * layer, so they include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may return
+ * {@link #UNSUPPORTED} on devices where statistics aren't available.
+ * <p>
+ * Starting in {@link android.os.Build.VERSION_CODES#N} this will only
+ * report traffic statistics for the calling UID. It will return
+ * {@link #UNSUPPORTED} for all other UIDs for privacy reasons. To access
+ * historical network statistics belonging to other UIDs, use
+ * {@link NetworkStatsManager}.
+ *
+ * @see android.os.Process#myUid()
+ * @see android.content.pm.ApplicationInfo#uid
+ */
+ public static long getUidRxPackets(int uid) {
+ try {
+ return getStatsService().getUidStats(uid, TYPE_RX_PACKETS);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @deprecated Starting in {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
+ * transport layer statistics are no longer available, and will
+ * always return {@link #UNSUPPORTED}.
+ * @see #getUidTxBytes(int)
+ */
+ @Deprecated
+ public static long getUidTcpTxBytes(int uid) {
+ return UNSUPPORTED;
+ }
+
+ /**
+ * @deprecated Starting in {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
+ * transport layer statistics are no longer available, and will
+ * always return {@link #UNSUPPORTED}.
+ * @see #getUidRxBytes(int)
+ */
+ @Deprecated
+ public static long getUidTcpRxBytes(int uid) {
+ return UNSUPPORTED;
+ }
+
+ /**
+ * @deprecated Starting in {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
+ * transport layer statistics are no longer available, and will
+ * always return {@link #UNSUPPORTED}.
+ * @see #getUidTxBytes(int)
+ */
+ @Deprecated
+ public static long getUidUdpTxBytes(int uid) {
+ return UNSUPPORTED;
+ }
+
+ /**
+ * @deprecated Starting in {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
+ * transport layer statistics are no longer available, and will
+ * always return {@link #UNSUPPORTED}.
+ * @see #getUidRxBytes(int)
+ */
+ @Deprecated
+ public static long getUidUdpRxBytes(int uid) {
+ return UNSUPPORTED;
+ }
+
+ /**
+ * @deprecated Starting in {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
+ * transport layer statistics are no longer available, and will
+ * always return {@link #UNSUPPORTED}.
+ * @see #getUidTxPackets(int)
+ */
+ @Deprecated
+ public static long getUidTcpTxSegments(int uid) {
+ return UNSUPPORTED;
+ }
+
+ /**
+ * @deprecated Starting in {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
+ * transport layer statistics are no longer available, and will
+ * always return {@link #UNSUPPORTED}.
+ * @see #getUidRxPackets(int)
+ */
+ @Deprecated
+ public static long getUidTcpRxSegments(int uid) {
+ return UNSUPPORTED;
+ }
+
+ /**
+ * @deprecated Starting in {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
+ * transport layer statistics are no longer available, and will
+ * always return {@link #UNSUPPORTED}.
+ * @see #getUidTxPackets(int)
+ */
+ @Deprecated
+ public static long getUidUdpTxPackets(int uid) {
+ return UNSUPPORTED;
+ }
+
+ /**
+ * @deprecated Starting in {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
+ * transport layer statistics are no longer available, and will
+ * always return {@link #UNSUPPORTED}.
+ * @see #getUidRxPackets(int)
+ */
+ @Deprecated
+ public static long getUidUdpRxPackets(int uid) {
+ return UNSUPPORTED;
+ }
+
+ /**
+ * Return detailed {@link NetworkStats} for the current UID. Requires no
+ * special permission.
+ */
+ private static NetworkStats getDataLayerSnapshotForUid(Context context) {
+ // TODO: take snapshot locally, since proc file is now visible
+ final int uid = android.os.Process.myUid();
+ try {
+ return getStatsService().getDataLayerSnapshotForUid(uid);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return set of any ifaces associated with mobile networks since boot.
+ * Interfaces are never removed from this list, so counters should always be
+ * monotonic.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
+ private static String[] getMobileIfaces() {
+ try {
+ return getStatsService().getMobileIfaces();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ // NOTE: keep these in sync with {@code com_android_server_net_NetworkStatsService.cpp}.
+ /** {@hide} */
+ public static final int TYPE_RX_BYTES = 0;
+ /** {@hide} */
+ public static final int TYPE_RX_PACKETS = 1;
+ /** {@hide} */
+ public static final int TYPE_TX_BYTES = 2;
+ /** {@hide} */
+ public static final int TYPE_TX_PACKETS = 3;
+ /** {@hide} */
+ public static final int TYPE_TCP_RX_PACKETS = 4;
+ /** {@hide} */
+ public static final int TYPE_TCP_TX_PACKETS = 5;
+}
diff --git a/framework-t/src/android/net/UnderlyingNetworkInfo.aidl b/framework-t/src/android/net/UnderlyingNetworkInfo.aidl
new file mode 100644
index 0000000..a56f2f4
--- /dev/null
+++ b/framework-t/src/android/net/UnderlyingNetworkInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+parcelable UnderlyingNetworkInfo;
diff --git a/framework-t/src/android/net/UnderlyingNetworkInfo.java b/framework-t/src/android/net/UnderlyingNetworkInfo.java
new file mode 100644
index 0000000..33f9375
--- /dev/null
+++ b/framework-t/src/android/net/UnderlyingNetworkInfo.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A lightweight container used to carry information on the networks that underly a given
+ * virtual network.
+ *
+ * @hide
+ */
+@SystemApi(client = MODULE_LIBRARIES)
+public final class UnderlyingNetworkInfo implements Parcelable {
+ /** The owner of this network. */
+ private final int mOwnerUid;
+
+ /** The interface name of this network. */
+ @NonNull
+ private final String mIface;
+
+ /** The names of the interfaces underlying this network. */
+ @NonNull
+ private final List<String> mUnderlyingIfaces;
+
+ public UnderlyingNetworkInfo(int ownerUid, @NonNull String iface,
+ @NonNull List<String> underlyingIfaces) {
+ Objects.requireNonNull(iface);
+ Objects.requireNonNull(underlyingIfaces);
+ mOwnerUid = ownerUid;
+ mIface = iface;
+ mUnderlyingIfaces = Collections.unmodifiableList(new ArrayList<>(underlyingIfaces));
+ }
+
+ private UnderlyingNetworkInfo(@NonNull Parcel in) {
+ mOwnerUid = in.readInt();
+ mIface = in.readString();
+ List<String> underlyingIfaces = new ArrayList<>();
+ in.readList(underlyingIfaces, null /*classLoader*/);
+ mUnderlyingIfaces = Collections.unmodifiableList(underlyingIfaces);
+ }
+
+ /** Get the owner of this network. */
+ public int getOwnerUid() {
+ return mOwnerUid;
+ }
+
+ /** Get the interface name of this network. */
+ @NonNull
+ public String getInterface() {
+ return mIface;
+ }
+
+ /** Get the names of the interfaces underlying this network. */
+ @NonNull
+ public List<String> getUnderlyingInterfaces() {
+ return mUnderlyingIfaces;
+ }
+
+ @Override
+ public String toString() {
+ return "UnderlyingNetworkInfo{"
+ + "ownerUid=" + mOwnerUid
+ + ", iface='" + mIface + '\''
+ + ", underlyingIfaces='" + mUnderlyingIfaces.toString() + '\''
+ + '}';
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mOwnerUid);
+ dest.writeString(mIface);
+ dest.writeList(mUnderlyingIfaces);
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<UnderlyingNetworkInfo> CREATOR =
+ new Parcelable.Creator<UnderlyingNetworkInfo>() {
+ @NonNull
+ @Override
+ public UnderlyingNetworkInfo createFromParcel(@NonNull Parcel in) {
+ return new UnderlyingNetworkInfo(in);
+ }
+
+ @NonNull
+ @Override
+ public UnderlyingNetworkInfo[] newArray(int size) {
+ return new UnderlyingNetworkInfo[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof UnderlyingNetworkInfo)) return false;
+ final UnderlyingNetworkInfo that = (UnderlyingNetworkInfo) o;
+ return mOwnerUid == that.getOwnerUid()
+ && Objects.equals(mIface, that.getInterface())
+ && Objects.equals(mUnderlyingIfaces, that.getUnderlyingInterfaces());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mOwnerUid, mIface, mUnderlyingIfaces);
+ }
+}
diff --git a/framework-t/src/android/net/netstats/IUsageCallback.aidl b/framework-t/src/android/net/netstats/IUsageCallback.aidl
new file mode 100644
index 0000000..4e8a5b2
--- /dev/null
+++ b/framework-t/src/android/net/netstats/IUsageCallback.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.netstats;
+
+import android.net.DataUsageRequest;
+
+/**
+ * Interface for NetworkStatsService to notify events to the callers of registerUsageCallback.
+ *
+ * @hide
+ */
+oneway interface IUsageCallback {
+ void onThresholdReached(in DataUsageRequest request);
+ void onCallbackReleased(in DataUsageRequest request);
+}
diff --git a/framework-t/src/android/net/netstats/provider/INetworkStatsProvider.aidl b/framework-t/src/android/net/netstats/provider/INetworkStatsProvider.aidl
new file mode 100644
index 0000000..74c3ba4
--- /dev/null
+++ b/framework-t/src/android/net/netstats/provider/INetworkStatsProvider.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.netstats.provider;
+
+/**
+ * Interface for NetworkStatsService to query network statistics and set data limits.
+ *
+ * @hide
+ */
+oneway interface INetworkStatsProvider {
+ void onRequestStatsUpdate(int token);
+ void onSetAlert(long quotaBytes);
+ void onSetWarningAndLimit(String iface, long warningBytes, long limitBytes);
+}
diff --git a/framework-t/src/android/net/netstats/provider/INetworkStatsProviderCallback.aidl b/framework-t/src/android/net/netstats/provider/INetworkStatsProviderCallback.aidl
new file mode 100644
index 0000000..01ff02d
--- /dev/null
+++ b/framework-t/src/android/net/netstats/provider/INetworkStatsProviderCallback.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.netstats.provider;
+
+import android.net.NetworkStats;
+
+/**
+ * Interface for implementor of {@link INetworkStatsProviderCallback} to push events
+ * such as network statistics update or notify limit reached.
+ * @hide
+ */
+oneway interface INetworkStatsProviderCallback {
+ void notifyStatsUpdated(int token, in NetworkStats ifaceStats, in NetworkStats uidStats);
+ void notifyAlertReached();
+ void notifyWarningReached();
+ void notifyLimitReached();
+ void unregister();
+}
diff --git a/framework-t/src/android/net/netstats/provider/NetworkStatsProvider.java b/framework-t/src/android/net/netstats/provider/NetworkStatsProvider.java
new file mode 100644
index 0000000..d37a53d
--- /dev/null
+++ b/framework-t/src/android/net/netstats/provider/NetworkStatsProvider.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.netstats.provider;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.net.NetworkStats;
+import android.os.RemoteException;
+
+/**
+ * A base class that allows external modules to implement a custom network statistics provider.
+ * @hide
+ */
+@SystemApi
+public abstract class NetworkStatsProvider {
+ /**
+ * A value used by {@link #onSetLimit}, {@link #onSetAlert} and {@link #onSetWarningAndLimit}
+ * indicates there is no limit.
+ */
+ public static final int QUOTA_UNLIMITED = -1;
+
+ @NonNull private final INetworkStatsProvider mProviderBinder =
+ new INetworkStatsProvider.Stub() {
+
+ @Override
+ public void onRequestStatsUpdate(int token) {
+ NetworkStatsProvider.this.onRequestStatsUpdate(token);
+ }
+
+ @Override
+ public void onSetAlert(long quotaBytes) {
+ NetworkStatsProvider.this.onSetAlert(quotaBytes);
+ }
+
+ @Override
+ public void onSetWarningAndLimit(String iface, long warningBytes, long limitBytes) {
+ NetworkStatsProvider.this.onSetWarningAndLimit(iface, warningBytes, limitBytes);
+ }
+ };
+
+ // The binder given by the service when successfully registering. Only null before registering,
+ // never null once non-null.
+ @Nullable
+ private INetworkStatsProviderCallback mProviderCbBinder;
+
+ /**
+ * Return the binder invoked by the service and redirect function calls to the overridden
+ * methods.
+ * @hide
+ */
+ @NonNull
+ public INetworkStatsProvider getProviderBinder() {
+ return mProviderBinder;
+ }
+
+ /**
+ * Store the binder that was returned by the service when successfully registering. Note that
+ * the provider cannot be re-registered. Hence this method can only be called once per provider.
+ *
+ * @hide
+ */
+ public void setProviderCallbackBinder(@NonNull INetworkStatsProviderCallback binder) {
+ if (mProviderCbBinder != null) {
+ throw new IllegalArgumentException("provider is already registered");
+ }
+ mProviderCbBinder = binder;
+ }
+
+ /**
+ * Get the binder that was returned by the service when successfully registering. Or null if the
+ * provider was never registered.
+ *
+ * @hide
+ */
+ @Nullable
+ public INetworkStatsProviderCallback getProviderCallbackBinder() {
+ return mProviderCbBinder;
+ }
+
+ /**
+ * Get the binder that was returned by the service when successfully registering. Throw an
+ * {@link IllegalStateException} if the provider is not registered.
+ *
+ * @hide
+ */
+ @NonNull
+ public INetworkStatsProviderCallback getProviderCallbackBinderOrThrow() {
+ if (mProviderCbBinder == null) {
+ throw new IllegalStateException("the provider is not registered");
+ }
+ return mProviderCbBinder;
+ }
+
+ /**
+ * Notify the system of new network statistics.
+ *
+ * Send the network statistics recorded since the last call to {@link #notifyStatsUpdated}. Must
+ * be called as soon as possible after {@link NetworkStatsProvider#onRequestStatsUpdate(int)}
+ * being called. Responding later increases the probability stats will be dropped. The
+ * provider can also call this whenever it wants to reports new stats for any reason.
+ * Note that the system will not necessarily immediately propagate the statistics to
+ * reflect the update.
+ *
+ * @param token the token under which these stats were gathered. Providers can call this method
+ * with the current token as often as they want, until the token changes.
+ * {@see NetworkStatsProvider#onRequestStatsUpdate()}
+ * @param ifaceStats the {@link NetworkStats} per interface to be reported.
+ * The provider should not include any traffic that is already counted by
+ * kernel interface counters.
+ * @param uidStats the same stats as above, but counts {@link NetworkStats}
+ * per uid.
+ */
+ public void notifyStatsUpdated(int token, @NonNull NetworkStats ifaceStats,
+ @NonNull NetworkStats uidStats) {
+ try {
+ getProviderCallbackBinderOrThrow().notifyStatsUpdated(token, ifaceStats, uidStats);
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Notify system that the quota set by {@code onSetAlert} has been reached.
+ */
+ public void notifyAlertReached() {
+ try {
+ getProviderCallbackBinderOrThrow().notifyAlertReached();
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Notify system that the warning set by {@link #onSetWarningAndLimit} has been reached.
+ */
+ public void notifyWarningReached() {
+ try {
+ // Reuse the code path to notify warning reached with limit reached
+ // since framework handles them in the same way.
+ getProviderCallbackBinderOrThrow().notifyWarningReached();
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Notify system that the limit set by {@link #onSetLimit} or limit set by
+ * {@link #onSetWarningAndLimit} has been reached.
+ */
+ public void notifyLimitReached() {
+ try {
+ getProviderCallbackBinderOrThrow().notifyLimitReached();
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Called by {@code NetworkStatsService} when it requires to know updated stats.
+ * The provider MUST respond by calling {@link #notifyStatsUpdated} as soon as possible.
+ * Responding later increases the probability stats will be dropped. Memory allowing, the
+ * system will try to take stats into account up to one minute after calling
+ * {@link #onRequestStatsUpdate}.
+ *
+ * @param token a positive number identifying the new state of the system under which
+ * {@link NetworkStats} have to be gathered from now on. When this is called,
+ * custom implementations of providers MUST tally and report the latest stats with
+ * the previous token, under which stats were being gathered so far.
+ */
+ public abstract void onRequestStatsUpdate(int token);
+
+ /**
+ * Called by {@code NetworkStatsService} when setting the interface quota for the specified
+ * upstream interface. When this is called, the custom implementation should block all egress
+ * packets on the {@code iface} associated with the provider when {@code quotaBytes} bytes have
+ * been reached, and MUST respond to it by calling
+ * {@link NetworkStatsProvider#notifyLimitReached()}.
+ *
+ * @param iface the interface requiring the operation.
+ * @param quotaBytes the quota defined as the number of bytes, starting from zero and counting
+ * from now. A value of {@link #QUOTA_UNLIMITED} indicates there is no limit.
+ */
+ public abstract void onSetLimit(@NonNull String iface, long quotaBytes);
+
+ /**
+ * Called by {@code NetworkStatsService} when setting the interface quotas for the specified
+ * upstream interface. If a provider implements {@link #onSetWarningAndLimit}, the system
+ * will not call {@link #onSetLimit}. When this method is called, the implementation
+ * should behave as follows:
+ * 1. If {@code warningBytes} is reached on {@code iface}, block all further traffic on
+ * {@code iface} and call {@link NetworkStatsProvider@notifyWarningReached()}.
+ * 2. If {@code limitBytes} is reached on {@code iface}, block all further traffic on
+ * {@code iface} and call {@link NetworkStatsProvider#notifyLimitReached()}.
+ *
+ * @param iface the interface requiring the operation.
+ * @param warningBytes the warning defined as the number of bytes, starting from zero and
+ * counting from now. A value of {@link #QUOTA_UNLIMITED} indicates
+ * there is no warning.
+ * @param limitBytes the limit defined as the number of bytes, starting from zero and counting
+ * from now. A value of {@link #QUOTA_UNLIMITED} indicates there is no limit.
+ */
+ public void onSetWarningAndLimit(@NonNull String iface, long warningBytes, long limitBytes) {
+ // Backward compatibility for those who didn't override this function.
+ onSetLimit(iface, limitBytes);
+ }
+
+ /**
+ * Called by {@code NetworkStatsService} when setting the alert bytes. Custom implementations
+ * MUST call {@link NetworkStatsProvider#notifyAlertReached()} when {@code quotaBytes} bytes
+ * have been reached. Unlike {@link #onSetLimit(String, long)}, the custom implementation should
+ * not block all egress packets.
+ *
+ * @param quotaBytes the quota defined as the number of bytes, starting from zero and counting
+ * from now. A value of {@link #QUOTA_UNLIMITED} indicates there is no alert.
+ */
+ public abstract void onSetAlert(long quotaBytes);
+}
diff --git a/framework-t/src/android/net/nsd/INsdManager.aidl b/framework-t/src/android/net/nsd/INsdManager.aidl
new file mode 100644
index 0000000..89e9cdb
--- /dev/null
+++ b/framework-t/src/android/net/nsd/INsdManager.aidl
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.nsd;
+
+import android.net.nsd.INsdManagerCallback;
+import android.net.nsd.INsdServiceConnector;
+import android.os.Messenger;
+
+/**
+ * Interface that NsdService implements to connect NsdManager clients.
+ *
+ * {@hide}
+ */
+interface INsdManager {
+ INsdServiceConnector connect(INsdManagerCallback cb);
+}
diff --git a/framework-t/src/android/net/nsd/INsdManagerCallback.aidl b/framework-t/src/android/net/nsd/INsdManagerCallback.aidl
new file mode 100644
index 0000000..1a262ec
--- /dev/null
+++ b/framework-t/src/android/net/nsd/INsdManagerCallback.aidl
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.nsd;
+
+import android.os.Messenger;
+import android.net.nsd.NsdServiceInfo;
+
+/**
+ * Callbacks from NsdService to NsdManager
+ * @hide
+ */
+oneway interface INsdManagerCallback {
+ void onDiscoverServicesStarted(int listenerKey, in NsdServiceInfo info);
+ void onDiscoverServicesFailed(int listenerKey, int error);
+ void onServiceFound(int listenerKey, in NsdServiceInfo info);
+ void onServiceLost(int listenerKey, in NsdServiceInfo info);
+ void onStopDiscoveryFailed(int listenerKey, int error);
+ void onStopDiscoverySucceeded(int listenerKey);
+ void onRegisterServiceFailed(int listenerKey, int error);
+ void onRegisterServiceSucceeded(int listenerKey, in NsdServiceInfo info);
+ void onUnregisterServiceFailed(int listenerKey, int error);
+ void onUnregisterServiceSucceeded(int listenerKey);
+ void onResolveServiceFailed(int listenerKey, int error);
+ void onResolveServiceSucceeded(int listenerKey, in NsdServiceInfo info);
+}
diff --git a/framework-t/src/android/net/nsd/INsdServiceConnector.aidl b/framework-t/src/android/net/nsd/INsdServiceConnector.aidl
new file mode 100644
index 0000000..b06ae55
--- /dev/null
+++ b/framework-t/src/android/net/nsd/INsdServiceConnector.aidl
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.nsd;
+
+import android.net.nsd.INsdManagerCallback;
+import android.net.nsd.NsdServiceInfo;
+import android.os.Messenger;
+
+/**
+ * Interface that NsdService implements for each NsdManager client.
+ *
+ * {@hide}
+ */
+interface INsdServiceConnector {
+ void registerService(int listenerKey, in NsdServiceInfo serviceInfo);
+ void unregisterService(int listenerKey);
+ void discoverServices(int listenerKey, in NsdServiceInfo serviceInfo);
+ void stopDiscovery(int listenerKey);
+ void resolveService(int listenerKey, in NsdServiceInfo serviceInfo);
+ void startDaemon();
+}
\ No newline at end of file
diff --git a/framework-t/src/android/net/nsd/MDnsManager.java b/framework-t/src/android/net/nsd/MDnsManager.java
new file mode 100644
index 0000000..c11e60c
--- /dev/null
+++ b/framework-t/src/android/net/nsd/MDnsManager.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.nsd;
+
+import android.annotation.NonNull;
+import android.net.mdns.aidl.DiscoveryInfo;
+import android.net.mdns.aidl.GetAddressInfo;
+import android.net.mdns.aidl.IMDns;
+import android.net.mdns.aidl.IMDnsEventListener;
+import android.net.mdns.aidl.RegistrationInfo;
+import android.net.mdns.aidl.ResolutionInfo;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.util.Log;
+
+/**
+ * A manager class for mdns service.
+ *
+ * @hide
+ */
+public class MDnsManager {
+ private static final String TAG = MDnsManager.class.getSimpleName();
+ private final IMDns mMdns;
+
+ /** Service name for this. */
+ public static final String MDNS_SERVICE = "mdns";
+
+ private static final int NO_RESULT = -1;
+ private static final int NETID_UNSET = 0;
+
+ public MDnsManager(IMDns mdns) {
+ mMdns = mdns;
+ }
+
+ /**
+ * Start the MDNSResponder daemon.
+ */
+ public void startDaemon() {
+ try {
+ mMdns.startDaemon();
+ } catch (RemoteException | ServiceSpecificException e) {
+ Log.e(TAG, "Start mdns failed.", e);
+ }
+ }
+
+ /**
+ * Stop the MDNSResponder daemon.
+ */
+ public void stopDaemon() {
+ try {
+ mMdns.stopDaemon();
+ } catch (RemoteException | ServiceSpecificException e) {
+ Log.e(TAG, "Stop mdns failed.", e);
+ }
+ }
+
+ /**
+ * Start registering a service.
+ *
+ * @param id The operation ID.
+ * @param serviceName The service name to be registered.
+ * @param registrationType The service type to be registered.
+ * @param port The port on which the service accepts connections.
+ * @param txtRecord The txt record. Refer to {@code NsdServiceInfo#setTxtRecords} for details.
+ * @param interfaceIdx The interface index on which to register the service.
+ * @return {@code true} if registration is successful, else {@code false}.
+ */
+ public boolean registerService(int id, @NonNull String serviceName,
+ @NonNull String registrationType, int port, @NonNull byte[] txtRecord,
+ int interfaceIdx) {
+ final RegistrationInfo info = new RegistrationInfo(id, NO_RESULT, serviceName,
+ registrationType, port, txtRecord, interfaceIdx);
+ try {
+ mMdns.registerService(info);
+ } catch (RemoteException | ServiceSpecificException e) {
+ Log.e(TAG, "Register service failed.", e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Start discovering services.
+ *
+ * @param id The operation ID.
+ * @param registrationType The service type to be discovered.
+ * @param interfaceIdx The interface index on which to discover for services.
+ * @return {@code true} if discovery is started successfully, else {@code false}.
+ */
+ public boolean discover(int id, @NonNull String registrationType, int interfaceIdx) {
+ final DiscoveryInfo info = new DiscoveryInfo(id, NO_RESULT, "" /* serviceName */,
+ registrationType, "" /* domainName */, interfaceIdx, NETID_UNSET);
+ try {
+ mMdns.discover(info);
+ } catch (RemoteException | ServiceSpecificException e) {
+ Log.e(TAG, "Discover service failed.", e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Start resolving the target service.
+ *
+ * @param id The operation ID.
+ * @param serviceName The service name to be resolved.
+ * @param registrationType The service type to be resolved.
+ * @param domain The service domain to be resolved.
+ * @param interfaceIdx The interface index on which to resolve the service.
+ * @return {@code true} if resolution is started successfully, else {@code false}.
+ */
+ public boolean resolve(int id, @NonNull String serviceName, @NonNull String registrationType,
+ @NonNull String domain, int interfaceIdx) {
+ final ResolutionInfo info = new ResolutionInfo(id, NO_RESULT, serviceName,
+ registrationType, domain, "" /* serviceFullName */, "" /* hostname */, 0 /* port */,
+ new byte[0] /* txtRecord */, interfaceIdx);
+ try {
+ mMdns.resolve(info);
+ } catch (RemoteException | ServiceSpecificException e) {
+ Log.e(TAG, "Resolve service failed.", e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Start getting the target service address.
+ *
+ * @param id The operation ID.
+ * @param hostname The fully qualified domain name of the host to be queried for.
+ * @param interfaceIdx The interface index on which to issue the query.
+ * @return {@code true} if getting address is started successful, else {@code false}.
+ */
+ public boolean getServiceAddress(int id, @NonNull String hostname, int interfaceIdx) {
+ final GetAddressInfo info = new GetAddressInfo(id, NO_RESULT, hostname,
+ "" /* address */, interfaceIdx, NETID_UNSET);
+ try {
+ mMdns.getServiceAddress(info);
+ } catch (RemoteException | ServiceSpecificException e) {
+ Log.e(TAG, "Get service address failed.", e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Stop an operation which was requested before.
+ *
+ * @param id the operation id to be stopped.
+ * @return {@code true} if operation is stopped successfully, else {@code false}.
+ */
+ public boolean stopOperation(int id) {
+ try {
+ mMdns.stopOperation(id);
+ } catch (RemoteException | ServiceSpecificException e) {
+ Log.e(TAG, "Stop operation failed.", e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Register an event listener.
+ *
+ * @param listener The listener to be registered.
+ */
+ public void registerEventListener(@NonNull IMDnsEventListener listener) {
+ try {
+ mMdns.registerEventListener(listener);
+ } catch (RemoteException | ServiceSpecificException e) {
+ Log.e(TAG, "Register listener failed.", e);
+ }
+ }
+
+ /**
+ * Unregister an event listener.
+ *
+ * @param listener The listener to be unregistered.
+ */
+ public void unregisterEventListener(@NonNull IMDnsEventListener listener) {
+ try {
+ mMdns.unregisterEventListener(listener);
+ } catch (RemoteException | ServiceSpecificException e) {
+ Log.e(TAG, "Unregister listener failed.", e);
+ }
+ }
+}
diff --git a/framework-t/src/android/net/nsd/NsdManager.java b/framework-t/src/android/net/nsd/NsdManager.java
new file mode 100644
index 0000000..fad63e5
--- /dev/null
+++ b/framework-t/src/android/net/nsd/NsdManager.java
@@ -0,0 +1,1094 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.nsd;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemService;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.Network;
+import android.net.NetworkRequest;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * The Network Service Discovery Manager class provides the API to discover services
+ * on a network. As an example, if device A and device B are connected over a Wi-Fi
+ * network, a game registered on device A can be discovered by a game on device
+ * B. Another example use case is an application discovering printers on the network.
+ *
+ * <p> The API currently supports DNS based service discovery and discovery is currently
+ * limited to a local network over Multicast DNS. DNS service discovery is described at
+ * http://files.dns-sd.org/draft-cheshire-dnsext-dns-sd.txt
+ *
+ * <p> The API is asynchronous, and responses to requests from an application are on listener
+ * callbacks on a separate internal thread.
+ *
+ * <p> There are three main operations the API supports - registration, discovery and resolution.
+ * <pre>
+ * Application start
+ * |
+ * |
+ * | onServiceRegistered()
+ * Register any local services /
+ * to be advertised with \
+ * registerService() onRegistrationFailed()
+ * |
+ * |
+ * discoverServices()
+ * |
+ * Maintain a list to track
+ * discovered services
+ * |
+ * |--------->
+ * | |
+ * | onServiceFound()
+ * | |
+ * | add service to list
+ * | |
+ * |<----------
+ * |
+ * |--------->
+ * | |
+ * | onServiceLost()
+ * | |
+ * | remove service from list
+ * | |
+ * |<----------
+ * |
+ * |
+ * | Connect to a service
+ * | from list ?
+ * |
+ * resolveService()
+ * |
+ * onServiceResolved()
+ * |
+ * Establish connection to service
+ * with the host and port information
+ *
+ * </pre>
+ * An application that needs to advertise itself over a network for other applications to
+ * discover it can do so with a call to {@link #registerService}. If Example is a http based
+ * application that can provide HTML data to peer services, it can register a name "Example"
+ * with service type "_http._tcp". A successful registration is notified with a callback to
+ * {@link RegistrationListener#onServiceRegistered} and a failure to register is notified
+ * over {@link RegistrationListener#onRegistrationFailed}
+ *
+ * <p> A peer application looking for http services can initiate a discovery for "_http._tcp"
+ * with a call to {@link #discoverServices}. A service found is notified with a callback
+ * to {@link DiscoveryListener#onServiceFound} and a service lost is notified on
+ * {@link DiscoveryListener#onServiceLost}.
+ *
+ * <p> Once the peer application discovers the "Example" http service, and either needs to read the
+ * attributes of the service or wants to receive data from the "Example" application, it can
+ * initiate a resolve with {@link #resolveService} to resolve the attributes, host, and port
+ * details. A successful resolve is notified on {@link ResolveListener#onServiceResolved} and a
+ * failure is notified on {@link ResolveListener#onResolveFailed}.
+ *
+ * Applications can reserve for a service type at
+ * http://www.iana.org/form/ports-service. Existing services can be found at
+ * http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xml
+ *
+ * {@see NsdServiceInfo}
+ */
+@SystemService(Context.NSD_SERVICE)
+public final class NsdManager {
+ private static final String TAG = NsdManager.class.getSimpleName();
+ private static final boolean DBG = false;
+
+ /**
+ * When enabled, apps targeting < Android 12 are considered legacy for
+ * the NSD native daemon.
+ * The platform will only keep the daemon running as long as there are
+ * any legacy apps connected.
+ *
+ * After Android 12, directly communicate with native daemon might not
+ * work since the native damon won't always stay alive.
+ * Use the NSD APIs from NsdManager as the replacement is recommended.
+ * An another alternative could be bundling your own mdns solutions instead of
+ * depending on the system mdns native daemon.
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.S)
+ public static final long RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS = 191844585L;
+
+ /**
+ * Broadcast intent action to indicate whether network service discovery is
+ * enabled or disabled. An extra {@link #EXTRA_NSD_STATE} provides the state
+ * information as int.
+ *
+ * @see #EXTRA_NSD_STATE
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_NSD_STATE_CHANGED = "android.net.nsd.STATE_CHANGED";
+
+ /**
+ * The lookup key for an int that indicates whether network service discovery is enabled
+ * or disabled. Retrieve it with {@link android.content.Intent#getIntExtra(String,int)}.
+ *
+ * @see #NSD_STATE_DISABLED
+ * @see #NSD_STATE_ENABLED
+ */
+ public static final String EXTRA_NSD_STATE = "nsd_state";
+
+ /**
+ * Network service discovery is disabled
+ *
+ * @see #ACTION_NSD_STATE_CHANGED
+ */
+ // TODO: Deprecate this since NSD service is never disabled.
+ public static final int NSD_STATE_DISABLED = 1;
+
+ /**
+ * Network service discovery is enabled
+ *
+ * @see #ACTION_NSD_STATE_CHANGED
+ */
+ public static final int NSD_STATE_ENABLED = 2;
+
+ /** @hide */
+ public static final int DISCOVER_SERVICES = 1;
+ /** @hide */
+ public static final int DISCOVER_SERVICES_STARTED = 2;
+ /** @hide */
+ public static final int DISCOVER_SERVICES_FAILED = 3;
+ /** @hide */
+ public static final int SERVICE_FOUND = 4;
+ /** @hide */
+ public static final int SERVICE_LOST = 5;
+
+ /** @hide */
+ public static final int STOP_DISCOVERY = 6;
+ /** @hide */
+ public static final int STOP_DISCOVERY_FAILED = 7;
+ /** @hide */
+ public static final int STOP_DISCOVERY_SUCCEEDED = 8;
+
+ /** @hide */
+ public static final int REGISTER_SERVICE = 9;
+ /** @hide */
+ public static final int REGISTER_SERVICE_FAILED = 10;
+ /** @hide */
+ public static final int REGISTER_SERVICE_SUCCEEDED = 11;
+
+ /** @hide */
+ public static final int UNREGISTER_SERVICE = 12;
+ /** @hide */
+ public static final int UNREGISTER_SERVICE_FAILED = 13;
+ /** @hide */
+ public static final int UNREGISTER_SERVICE_SUCCEEDED = 14;
+
+ /** @hide */
+ public static final int RESOLVE_SERVICE = 15;
+ /** @hide */
+ public static final int RESOLVE_SERVICE_FAILED = 16;
+ /** @hide */
+ public static final int RESOLVE_SERVICE_SUCCEEDED = 17;
+
+ /** @hide */
+ public static final int DAEMON_CLEANUP = 18;
+
+ /** @hide */
+ public static final int DAEMON_STARTUP = 19;
+
+ /** @hide */
+ public static final int MDNS_SERVICE_EVENT = 20;
+
+ /** @hide */
+ public static final int REGISTER_CLIENT = 21;
+ /** @hide */
+ public static final int UNREGISTER_CLIENT = 22;
+
+ /** Dns based service discovery protocol */
+ public static final int PROTOCOL_DNS_SD = 0x0001;
+
+ private static final SparseArray<String> EVENT_NAMES = new SparseArray<>();
+ static {
+ EVENT_NAMES.put(DISCOVER_SERVICES, "DISCOVER_SERVICES");
+ EVENT_NAMES.put(DISCOVER_SERVICES_STARTED, "DISCOVER_SERVICES_STARTED");
+ EVENT_NAMES.put(DISCOVER_SERVICES_FAILED, "DISCOVER_SERVICES_FAILED");
+ EVENT_NAMES.put(SERVICE_FOUND, "SERVICE_FOUND");
+ EVENT_NAMES.put(SERVICE_LOST, "SERVICE_LOST");
+ EVENT_NAMES.put(STOP_DISCOVERY, "STOP_DISCOVERY");
+ EVENT_NAMES.put(STOP_DISCOVERY_FAILED, "STOP_DISCOVERY_FAILED");
+ EVENT_NAMES.put(STOP_DISCOVERY_SUCCEEDED, "STOP_DISCOVERY_SUCCEEDED");
+ EVENT_NAMES.put(REGISTER_SERVICE, "REGISTER_SERVICE");
+ EVENT_NAMES.put(REGISTER_SERVICE_FAILED, "REGISTER_SERVICE_FAILED");
+ EVENT_NAMES.put(REGISTER_SERVICE_SUCCEEDED, "REGISTER_SERVICE_SUCCEEDED");
+ EVENT_NAMES.put(UNREGISTER_SERVICE, "UNREGISTER_SERVICE");
+ EVENT_NAMES.put(UNREGISTER_SERVICE_FAILED, "UNREGISTER_SERVICE_FAILED");
+ EVENT_NAMES.put(UNREGISTER_SERVICE_SUCCEEDED, "UNREGISTER_SERVICE_SUCCEEDED");
+ EVENT_NAMES.put(RESOLVE_SERVICE, "RESOLVE_SERVICE");
+ EVENT_NAMES.put(RESOLVE_SERVICE_FAILED, "RESOLVE_SERVICE_FAILED");
+ EVENT_NAMES.put(RESOLVE_SERVICE_SUCCEEDED, "RESOLVE_SERVICE_SUCCEEDED");
+ EVENT_NAMES.put(DAEMON_CLEANUP, "DAEMON_CLEANUP");
+ EVENT_NAMES.put(DAEMON_STARTUP, "DAEMON_STARTUP");
+ EVENT_NAMES.put(MDNS_SERVICE_EVENT, "MDNS_SERVICE_EVENT");
+ }
+
+ /** @hide */
+ public static String nameOf(int event) {
+ String name = EVENT_NAMES.get(event);
+ if (name == null) {
+ return Integer.toString(event);
+ }
+ return name;
+ }
+
+ private static final int FIRST_LISTENER_KEY = 1;
+
+ private final INsdServiceConnector mService;
+ private final Context mContext;
+
+ private int mListenerKey = FIRST_LISTENER_KEY;
+ @GuardedBy("mMapLock")
+ private final SparseArray mListenerMap = new SparseArray();
+ @GuardedBy("mMapLock")
+ private final SparseArray<NsdServiceInfo> mServiceMap = new SparseArray<>();
+ @GuardedBy("mMapLock")
+ private final SparseArray<Executor> mExecutorMap = new SparseArray<>();
+ private final Object mMapLock = new Object();
+ // Map of listener key sent by client -> per-network discovery tracker
+ @GuardedBy("mPerNetworkDiscoveryMap")
+ private final ArrayMap<Integer, PerNetworkDiscoveryTracker>
+ mPerNetworkDiscoveryMap = new ArrayMap<>();
+
+ private final ServiceHandler mHandler;
+
+ private class PerNetworkDiscoveryTracker {
+ final String mServiceType;
+ final int mProtocolType;
+ final DiscoveryListener mBaseListener;
+ final Executor mBaseExecutor;
+ final ArrayMap<Network, DelegatingDiscoveryListener> mPerNetworkListeners =
+ new ArrayMap<>();
+
+ final NetworkCallback mNetworkCb = new NetworkCallback() {
+ @Override
+ public void onAvailable(@NonNull Network network) {
+ final DelegatingDiscoveryListener wrappedListener = new DelegatingDiscoveryListener(
+ network, mBaseListener, mBaseExecutor);
+ mPerNetworkListeners.put(network, wrappedListener);
+ // Run discovery callbacks inline on the service handler thread, which is the
+ // same thread used by this NetworkCallback, but DelegatingDiscoveryListener will
+ // use the base executor to run the wrapped callbacks.
+ discoverServices(mServiceType, mProtocolType, network, Runnable::run,
+ wrappedListener);
+ }
+
+ @Override
+ public void onLost(@NonNull Network network) {
+ final DelegatingDiscoveryListener listener = mPerNetworkListeners.get(network);
+ if (listener == null) return;
+ listener.notifyAllServicesLost();
+ // Listener will be removed from map in discovery stopped callback
+ stopServiceDiscovery(listener);
+ }
+ };
+
+ // Accessed from mHandler
+ private boolean mStopRequested;
+
+ public void start(@NonNull NetworkRequest request) {
+ final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
+ cm.registerNetworkCallback(request, mNetworkCb, mHandler);
+ mHandler.post(() -> mBaseExecutor.execute(() ->
+ mBaseListener.onDiscoveryStarted(mServiceType)));
+ }
+
+ /**
+ * Stop discovery on all networks tracked by this class.
+ *
+ * This will request all underlying listeners to stop, and the last one to stop will call
+ * onDiscoveryStopped or onStopDiscoveryFailed.
+ *
+ * Must be called on the handler thread.
+ */
+ public void requestStop() {
+ mHandler.post(() -> {
+ mStopRequested = true;
+ final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
+ cm.unregisterNetworkCallback(mNetworkCb);
+ if (mPerNetworkListeners.size() == 0) {
+ mBaseExecutor.execute(() -> mBaseListener.onDiscoveryStopped(mServiceType));
+ return;
+ }
+ for (int i = 0; i < mPerNetworkListeners.size(); i++) {
+ final DelegatingDiscoveryListener listener = mPerNetworkListeners.valueAt(i);
+ stopServiceDiscovery(listener);
+ }
+ });
+ }
+
+ private PerNetworkDiscoveryTracker(String serviceType, int protocolType,
+ Executor baseExecutor, DiscoveryListener baseListener) {
+ mServiceType = serviceType;
+ mProtocolType = protocolType;
+ mBaseExecutor = baseExecutor;
+ mBaseListener = baseListener;
+ }
+
+ /**
+ * Subset of NsdServiceInfo that is tracked to generate service lost notifications when a
+ * network is lost.
+ *
+ * Service lost notifications only contain service name, type and network, so only track
+ * that information (Network is known from the listener). This also implements
+ * equals/hashCode for usage in maps.
+ */
+ private class TrackedNsdInfo {
+ private final String mServiceName;
+ private final String mServiceType;
+ TrackedNsdInfo(NsdServiceInfo info) {
+ mServiceName = info.getServiceName();
+ mServiceType = info.getServiceType();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mServiceName, mServiceType);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof TrackedNsdInfo)) return false;
+ final TrackedNsdInfo other = (TrackedNsdInfo) obj;
+ return Objects.equals(mServiceName, other.mServiceName)
+ && Objects.equals(mServiceType, other.mServiceType);
+ }
+ }
+
+ /**
+ * A listener wrapping calls to an app-provided listener, while keeping track of found
+ * services, so they can all be reported lost when the underlying network is lost.
+ *
+ * This should be registered to run on the service handler.
+ */
+ private class DelegatingDiscoveryListener implements DiscoveryListener {
+ private final Network mNetwork;
+ private final DiscoveryListener mWrapped;
+ private final Executor mWrappedExecutor;
+ private final ArraySet<TrackedNsdInfo> mFoundInfo = new ArraySet<>();
+
+ private DelegatingDiscoveryListener(Network network, DiscoveryListener listener,
+ Executor executor) {
+ mNetwork = network;
+ mWrapped = listener;
+ mWrappedExecutor = executor;
+ }
+
+ void notifyAllServicesLost() {
+ for (int i = 0; i < mFoundInfo.size(); i++) {
+ final TrackedNsdInfo trackedInfo = mFoundInfo.valueAt(i);
+ final NsdServiceInfo serviceInfo = new NsdServiceInfo(
+ trackedInfo.mServiceName, trackedInfo.mServiceType);
+ serviceInfo.setNetwork(mNetwork);
+ mWrappedExecutor.execute(() -> mWrapped.onServiceLost(serviceInfo));
+ }
+ }
+
+ @Override
+ public void onStartDiscoveryFailed(String serviceType, int errorCode) {
+ // The delegated listener is used when NsdManager takes care of starting/stopping
+ // discovery on multiple networks. Failure to start on one network is not a global
+ // failure to be reported up, as other networks may succeed: just log.
+ Log.e(TAG, "Failed to start discovery for " + serviceType + " on " + mNetwork
+ + " with code " + errorCode);
+ mPerNetworkListeners.remove(mNetwork);
+ }
+
+ @Override
+ public void onDiscoveryStarted(String serviceType) {
+ // Wrapped listener was called upon registration, it is not called for discovery
+ // on each network
+ }
+
+ @Override
+ public void onStopDiscoveryFailed(String serviceType, int errorCode) {
+ Log.e(TAG, "Failed to stop discovery for " + serviceType + " on " + mNetwork
+ + " with code " + errorCode);
+ mPerNetworkListeners.remove(mNetwork);
+ if (mStopRequested && mPerNetworkListeners.size() == 0) {
+ // Do not report onStopDiscoveryFailed when some underlying listeners failed:
+ // this does not mean that all listeners did, and onStopDiscoveryFailed is not
+ // actionable anyway. Just report that discovery stopped.
+ mWrappedExecutor.execute(() -> mWrapped.onDiscoveryStopped(serviceType));
+ }
+ }
+
+ @Override
+ public void onDiscoveryStopped(String serviceType) {
+ mPerNetworkListeners.remove(mNetwork);
+ if (mStopRequested && mPerNetworkListeners.size() == 0) {
+ mWrappedExecutor.execute(() -> mWrapped.onDiscoveryStopped(serviceType));
+ }
+ }
+
+ @Override
+ public void onServiceFound(NsdServiceInfo serviceInfo) {
+ mFoundInfo.add(new TrackedNsdInfo(serviceInfo));
+ mWrappedExecutor.execute(() -> mWrapped.onServiceFound(serviceInfo));
+ }
+
+ @Override
+ public void onServiceLost(NsdServiceInfo serviceInfo) {
+ mFoundInfo.remove(new TrackedNsdInfo(serviceInfo));
+ mWrappedExecutor.execute(() -> mWrapped.onServiceLost(serviceInfo));
+ }
+ }
+ }
+
+ /**
+ * Create a new Nsd instance. Applications use
+ * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
+ * {@link android.content.Context#NSD_SERVICE Context.NSD_SERVICE}.
+ * @param service the Binder interface
+ * @hide - hide this because it takes in a parameter of type INsdManager, which
+ * is a system private class.
+ */
+ public NsdManager(Context context, INsdManager service) {
+ mContext = context;
+
+ HandlerThread t = new HandlerThread("NsdManager");
+ t.start();
+ mHandler = new ServiceHandler(t.getLooper());
+
+ try {
+ mService = service.connect(new NsdCallbackImpl(mHandler));
+ } catch (RemoteException e) {
+ throw new RuntimeException("Failed to connect to NsdService");
+ }
+
+ // Only proactively start the daemon if the target SDK < S, otherwise the internal service
+ // would automatically start/stop the native daemon as needed.
+ if (!CompatChanges.isChangeEnabled(RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS)) {
+ try {
+ mService.startDaemon();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to proactively start daemon");
+ // Continue: the daemon can still be started on-demand later
+ }
+ }
+ }
+
+ private static class NsdCallbackImpl extends INsdManagerCallback.Stub {
+ private final Handler mServHandler;
+
+ NsdCallbackImpl(Handler serviceHandler) {
+ mServHandler = serviceHandler;
+ }
+
+ private void sendInfo(int message, int listenerKey, NsdServiceInfo info) {
+ mServHandler.sendMessage(mServHandler.obtainMessage(message, 0, listenerKey, info));
+ }
+
+ private void sendError(int message, int listenerKey, int error) {
+ mServHandler.sendMessage(mServHandler.obtainMessage(message, error, listenerKey));
+ }
+
+ private void sendNoArg(int message, int listenerKey) {
+ mServHandler.sendMessage(mServHandler.obtainMessage(message, 0, listenerKey));
+ }
+
+ @Override
+ public void onDiscoverServicesStarted(int listenerKey, NsdServiceInfo info) {
+ sendInfo(DISCOVER_SERVICES_STARTED, listenerKey, info);
+ }
+
+ @Override
+ public void onDiscoverServicesFailed(int listenerKey, int error) {
+ sendError(DISCOVER_SERVICES_FAILED, listenerKey, error);
+ }
+
+ @Override
+ public void onServiceFound(int listenerKey, NsdServiceInfo info) {
+ sendInfo(SERVICE_FOUND, listenerKey, info);
+ }
+
+ @Override
+ public void onServiceLost(int listenerKey, NsdServiceInfo info) {
+ sendInfo(SERVICE_LOST, listenerKey, info);
+ }
+
+ @Override
+ public void onStopDiscoveryFailed(int listenerKey, int error) {
+ sendError(STOP_DISCOVERY_FAILED, listenerKey, error);
+ }
+
+ @Override
+ public void onStopDiscoverySucceeded(int listenerKey) {
+ sendNoArg(STOP_DISCOVERY_SUCCEEDED, listenerKey);
+ }
+
+ @Override
+ public void onRegisterServiceFailed(int listenerKey, int error) {
+ sendError(REGISTER_SERVICE_FAILED, listenerKey, error);
+ }
+
+ @Override
+ public void onRegisterServiceSucceeded(int listenerKey, NsdServiceInfo info) {
+ sendInfo(REGISTER_SERVICE_SUCCEEDED, listenerKey, info);
+ }
+
+ @Override
+ public void onUnregisterServiceFailed(int listenerKey, int error) {
+ sendError(UNREGISTER_SERVICE_FAILED, listenerKey, error);
+ }
+
+ @Override
+ public void onUnregisterServiceSucceeded(int listenerKey) {
+ sendNoArg(UNREGISTER_SERVICE_SUCCEEDED, listenerKey);
+ }
+
+ @Override
+ public void onResolveServiceFailed(int listenerKey, int error) {
+ sendError(RESOLVE_SERVICE_FAILED, listenerKey, error);
+ }
+
+ @Override
+ public void onResolveServiceSucceeded(int listenerKey, NsdServiceInfo info) {
+ sendInfo(RESOLVE_SERVICE_SUCCEEDED, listenerKey, info);
+ }
+ }
+
+ /**
+ * Failures are passed with {@link RegistrationListener#onRegistrationFailed},
+ * {@link RegistrationListener#onUnregistrationFailed},
+ * {@link DiscoveryListener#onStartDiscoveryFailed},
+ * {@link DiscoveryListener#onStopDiscoveryFailed} or {@link ResolveListener#onResolveFailed}.
+ *
+ * Indicates that the operation failed due to an internal error.
+ */
+ public static final int FAILURE_INTERNAL_ERROR = 0;
+
+ /**
+ * Indicates that the operation failed because it is already active.
+ */
+ public static final int FAILURE_ALREADY_ACTIVE = 3;
+
+ /**
+ * Indicates that the operation failed because the maximum outstanding
+ * requests from the applications have reached.
+ */
+ public static final int FAILURE_MAX_LIMIT = 4;
+
+ /** Interface for callback invocation for service discovery */
+ public interface DiscoveryListener {
+
+ public void onStartDiscoveryFailed(String serviceType, int errorCode);
+
+ public void onStopDiscoveryFailed(String serviceType, int errorCode);
+
+ public void onDiscoveryStarted(String serviceType);
+
+ public void onDiscoveryStopped(String serviceType);
+
+ public void onServiceFound(NsdServiceInfo serviceInfo);
+
+ public void onServiceLost(NsdServiceInfo serviceInfo);
+ }
+
+ /** Interface for callback invocation for service registration */
+ public interface RegistrationListener {
+
+ public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode);
+
+ public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode);
+
+ public void onServiceRegistered(NsdServiceInfo serviceInfo);
+
+ public void onServiceUnregistered(NsdServiceInfo serviceInfo);
+ }
+
+ /** Interface for callback invocation for service resolution */
+ public interface ResolveListener {
+
+ public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode);
+
+ public void onServiceResolved(NsdServiceInfo serviceInfo);
+ }
+
+ @VisibleForTesting
+ class ServiceHandler extends Handler {
+ ServiceHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ // Do not use message in the executor lambdas, as it will be recycled once this method
+ // returns. Keep references to its content instead.
+ final int what = message.what;
+ final int errorCode = message.arg1;
+ final int key = message.arg2;
+ final Object obj = message.obj;
+ final Object listener;
+ final NsdServiceInfo ns;
+ final Executor executor;
+ synchronized (mMapLock) {
+ listener = mListenerMap.get(key);
+ ns = mServiceMap.get(key);
+ executor = mExecutorMap.get(key);
+ }
+ if (listener == null) {
+ Log.d(TAG, "Stale key " + key);
+ return;
+ }
+ if (DBG) {
+ Log.d(TAG, "received " + nameOf(what) + " for key " + key + ", service " + ns);
+ }
+ switch (what) {
+ case DISCOVER_SERVICES_STARTED:
+ final String s = getNsdServiceInfoType((NsdServiceInfo) obj);
+ executor.execute(() -> ((DiscoveryListener) listener).onDiscoveryStarted(s));
+ break;
+ case DISCOVER_SERVICES_FAILED:
+ removeListener(key);
+ executor.execute(() -> ((DiscoveryListener) listener).onStartDiscoveryFailed(
+ getNsdServiceInfoType(ns), errorCode));
+ break;
+ case SERVICE_FOUND:
+ executor.execute(() -> ((DiscoveryListener) listener).onServiceFound(
+ (NsdServiceInfo) obj));
+ break;
+ case SERVICE_LOST:
+ executor.execute(() -> ((DiscoveryListener) listener).onServiceLost(
+ (NsdServiceInfo) obj));
+ break;
+ case STOP_DISCOVERY_FAILED:
+ // TODO: failure to stop discovery should be internal and retried internally, as
+ // the effect for the client is indistinguishable from STOP_DISCOVERY_SUCCEEDED
+ removeListener(key);
+ executor.execute(() -> ((DiscoveryListener) listener).onStopDiscoveryFailed(
+ getNsdServiceInfoType(ns), errorCode));
+ break;
+ case STOP_DISCOVERY_SUCCEEDED:
+ removeListener(key);
+ executor.execute(() -> ((DiscoveryListener) listener).onDiscoveryStopped(
+ getNsdServiceInfoType(ns)));
+ break;
+ case REGISTER_SERVICE_FAILED:
+ removeListener(key);
+ executor.execute(() -> ((RegistrationListener) listener).onRegistrationFailed(
+ ns, errorCode));
+ break;
+ case REGISTER_SERVICE_SUCCEEDED:
+ executor.execute(() -> ((RegistrationListener) listener).onServiceRegistered(
+ (NsdServiceInfo) obj));
+ break;
+ case UNREGISTER_SERVICE_FAILED:
+ removeListener(key);
+ executor.execute(() -> ((RegistrationListener) listener).onUnregistrationFailed(
+ ns, errorCode));
+ break;
+ case UNREGISTER_SERVICE_SUCCEEDED:
+ // TODO: do not unregister listener until service is unregistered, or provide
+ // alternative way for unregistering ?
+ removeListener(key);
+ executor.execute(() -> ((RegistrationListener) listener).onServiceUnregistered(
+ ns));
+ break;
+ case RESOLVE_SERVICE_FAILED:
+ removeListener(key);
+ executor.execute(() -> ((ResolveListener) listener).onResolveFailed(
+ ns, errorCode));
+ break;
+ case RESOLVE_SERVICE_SUCCEEDED:
+ removeListener(key);
+ executor.execute(() -> ((ResolveListener) listener).onServiceResolved(
+ (NsdServiceInfo) obj));
+ break;
+ default:
+ Log.d(TAG, "Ignored " + message);
+ break;
+ }
+ }
+ }
+
+ private int nextListenerKey() {
+ // Ensure mListenerKey >= FIRST_LISTENER_KEY;
+ mListenerKey = Math.max(FIRST_LISTENER_KEY, mListenerKey + 1);
+ return mListenerKey;
+ }
+
+ // Assert that the listener is not in the map, then add it and returns its key
+ private int putListener(Object listener, Executor e, NsdServiceInfo s) {
+ checkListener(listener);
+ final int key;
+ synchronized (mMapLock) {
+ int valueIndex = mListenerMap.indexOfValue(listener);
+ if (valueIndex != -1) {
+ throw new IllegalArgumentException("listener already in use");
+ }
+ key = nextListenerKey();
+ mListenerMap.put(key, listener);
+ mServiceMap.put(key, s);
+ mExecutorMap.put(key, e);
+ }
+ return key;
+ }
+
+ private void removeListener(int key) {
+ synchronized (mMapLock) {
+ mListenerMap.remove(key);
+ mServiceMap.remove(key);
+ mExecutorMap.remove(key);
+ }
+ }
+
+ private int getListenerKey(Object listener) {
+ checkListener(listener);
+ synchronized (mMapLock) {
+ int valueIndex = mListenerMap.indexOfValue(listener);
+ if (valueIndex == -1) {
+ throw new IllegalArgumentException("listener not registered");
+ }
+ return mListenerMap.keyAt(valueIndex);
+ }
+ }
+
+ private static String getNsdServiceInfoType(NsdServiceInfo s) {
+ if (s == null) return "?";
+ return s.getServiceType();
+ }
+
+ /**
+ * Register a service to be discovered by other services.
+ *
+ * <p> The function call immediately returns after sending a request to register service
+ * to the framework. The application is notified of a successful registration
+ * through the callback {@link RegistrationListener#onServiceRegistered} or a failure
+ * through {@link RegistrationListener#onRegistrationFailed}.
+ *
+ * <p> The application should call {@link #unregisterService} when the service
+ * registration is no longer required, and/or whenever the application is stopped.
+ *
+ * @param serviceInfo The service being registered
+ * @param protocolType The service discovery protocol
+ * @param listener The listener notifies of a successful registration and is used to
+ * unregister this service through a call on {@link #unregisterService}. Cannot be null.
+ * Cannot be in use for an active service registration.
+ */
+ public void registerService(NsdServiceInfo serviceInfo, int protocolType,
+ RegistrationListener listener) {
+ registerService(serviceInfo, protocolType, Runnable::run, listener);
+ }
+
+ /**
+ * Register a service to be discovered by other services.
+ *
+ * <p> The function call immediately returns after sending a request to register service
+ * to the framework. The application is notified of a successful registration
+ * through the callback {@link RegistrationListener#onServiceRegistered} or a failure
+ * through {@link RegistrationListener#onRegistrationFailed}.
+ *
+ * <p> The application should call {@link #unregisterService} when the service
+ * registration is no longer required, and/or whenever the application is stopped.
+ * @param serviceInfo The service being registered
+ * @param protocolType The service discovery protocol
+ * @param executor Executor to run listener callbacks with
+ * @param listener The listener notifies of a successful registration and is used to
+ * unregister this service through a call on {@link #unregisterService}. Cannot be null.
+ */
+ public void registerService(@NonNull NsdServiceInfo serviceInfo, int protocolType,
+ @NonNull Executor executor, @NonNull RegistrationListener listener) {
+ if (serviceInfo.getPort() <= 0) {
+ throw new IllegalArgumentException("Invalid port number");
+ }
+ checkServiceInfo(serviceInfo);
+ checkProtocol(protocolType);
+ int key = putListener(listener, executor, serviceInfo);
+ try {
+ mService.registerService(key, serviceInfo);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Unregister a service registered through {@link #registerService}. A successful
+ * unregister is notified to the application with a call to
+ * {@link RegistrationListener#onServiceUnregistered}.
+ *
+ * @param listener This should be the listener object that was passed to
+ * {@link #registerService}. It identifies the service that should be unregistered
+ * and notifies of a successful or unsuccessful unregistration via the listener
+ * callbacks. In API versions 20 and above, the listener object may be used for
+ * another service registration once the callback has been called. In API versions <= 19,
+ * there is no entirely reliable way to know when a listener may be re-used, and a new
+ * listener should be created for each service registration request.
+ */
+ public void unregisterService(RegistrationListener listener) {
+ int id = getListenerKey(listener);
+ try {
+ mService.unregisterService(id);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Initiate service discovery to browse for instances of a service type. Service discovery
+ * consumes network bandwidth and will continue until the application calls
+ * {@link #stopServiceDiscovery}.
+ *
+ * <p> The function call immediately returns after sending a request to start service
+ * discovery to the framework. The application is notified of a success to initiate
+ * discovery through the callback {@link DiscoveryListener#onDiscoveryStarted} or a failure
+ * through {@link DiscoveryListener#onStartDiscoveryFailed}.
+ *
+ * <p> Upon successful start, application is notified when a service is found with
+ * {@link DiscoveryListener#onServiceFound} or when a service is lost with
+ * {@link DiscoveryListener#onServiceLost}.
+ *
+ * <p> Upon failure to start, service discovery is not active and application does
+ * not need to invoke {@link #stopServiceDiscovery}
+ *
+ * <p> The application should call {@link #stopServiceDiscovery} when discovery of this
+ * service type is no longer required, and/or whenever the application is paused or
+ * stopped.
+ *
+ * @param serviceType The service type being discovered. Examples include "_http._tcp" for
+ * http services or "_ipp._tcp" for printers
+ * @param protocolType The service discovery protocol
+ * @param listener The listener notifies of a successful discovery and is used
+ * to stop discovery on this serviceType through a call on {@link #stopServiceDiscovery}.
+ * Cannot be null. Cannot be in use for an active service discovery.
+ */
+ public void discoverServices(String serviceType, int protocolType, DiscoveryListener listener) {
+ discoverServices(serviceType, protocolType, (Network) null, Runnable::run, listener);
+ }
+
+ /**
+ * Initiate service discovery to browse for instances of a service type. Service discovery
+ * consumes network bandwidth and will continue until the application calls
+ * {@link #stopServiceDiscovery}.
+ *
+ * <p> The function call immediately returns after sending a request to start service
+ * discovery to the framework. The application is notified of a success to initiate
+ * discovery through the callback {@link DiscoveryListener#onDiscoveryStarted} or a failure
+ * through {@link DiscoveryListener#onStartDiscoveryFailed}.
+ *
+ * <p> Upon successful start, application is notified when a service is found with
+ * {@link DiscoveryListener#onServiceFound} or when a service is lost with
+ * {@link DiscoveryListener#onServiceLost}.
+ *
+ * <p> Upon failure to start, service discovery is not active and application does
+ * not need to invoke {@link #stopServiceDiscovery}
+ *
+ * <p> The application should call {@link #stopServiceDiscovery} when discovery of this
+ * service type is no longer required, and/or whenever the application is paused or
+ * stopped.
+ * @param serviceType The service type being discovered. Examples include "_http._tcp" for
+ * http services or "_ipp._tcp" for printers
+ * @param protocolType The service discovery protocol
+ * @param network Network to discover services on, or null to discover on all available networks
+ * @param executor Executor to run listener callbacks with
+ * @param listener The listener notifies of a successful discovery and is used
+ * to stop discovery on this serviceType through a call on {@link #stopServiceDiscovery}.
+ */
+ public void discoverServices(@NonNull String serviceType, int protocolType,
+ @Nullable Network network, @NonNull Executor executor,
+ @NonNull DiscoveryListener listener) {
+ if (TextUtils.isEmpty(serviceType)) {
+ throw new IllegalArgumentException("Service type cannot be empty");
+ }
+ checkProtocol(protocolType);
+
+ NsdServiceInfo s = new NsdServiceInfo();
+ s.setServiceType(serviceType);
+ s.setNetwork(network);
+
+ int key = putListener(listener, executor, s);
+ try {
+ mService.discoverServices(key, s);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Initiate service discovery to browse for instances of a service type. Service discovery
+ * consumes network bandwidth and will continue until the application calls
+ * {@link #stopServiceDiscovery}.
+ *
+ * <p> The function call immediately returns after sending a request to start service
+ * discovery to the framework. The application is notified of a success to initiate
+ * discovery through the callback {@link DiscoveryListener#onDiscoveryStarted} or a failure
+ * through {@link DiscoveryListener#onStartDiscoveryFailed}.
+ *
+ * <p> Upon successful start, application is notified when a service is found with
+ * {@link DiscoveryListener#onServiceFound} or when a service is lost with
+ * {@link DiscoveryListener#onServiceLost}.
+ *
+ * <p> Upon failure to start, service discovery is not active and application does
+ * not need to invoke {@link #stopServiceDiscovery}
+ *
+ * <p> The application should call {@link #stopServiceDiscovery} when discovery of this
+ * service type is no longer required, and/or whenever the application is paused or
+ * stopped.
+ *
+ * <p> During discovery, new networks may connect or existing networks may disconnect - for
+ * example if wifi is reconnected. When a service was found on a network that disconnects,
+ * {@link DiscoveryListener#onServiceLost} will be called. If a new network connects that
+ * matches the {@link NetworkRequest}, {@link DiscoveryListener#onServiceFound} will be called
+ * for services found on that network. Applications that do not want to track networks
+ * themselves are encouraged to use this method instead of other overloads of
+ * {@code discoverServices}, as they will receive proper notifications when a service becomes
+ * available or unavailable due to network changes.
+ * @param serviceType The service type being discovered. Examples include "_http._tcp" for
+ * http services or "_ipp._tcp" for printers
+ * @param protocolType The service discovery protocol
+ * @param networkRequest Request specifying networks that should be considered when discovering
+ * @param executor Executor to run listener callbacks with
+ * @param listener The listener notifies of a successful discovery and is used
+ * to stop discovery on this serviceType through a call on {@link #stopServiceDiscovery}.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ public void discoverServices(@NonNull String serviceType, int protocolType,
+ @NonNull NetworkRequest networkRequest, @NonNull Executor executor,
+ @NonNull DiscoveryListener listener) {
+ if (TextUtils.isEmpty(serviceType)) {
+ throw new IllegalArgumentException("Service type cannot be empty");
+ }
+ Objects.requireNonNull(networkRequest, "NetworkRequest cannot be null");
+ checkProtocol(protocolType);
+
+ NsdServiceInfo s = new NsdServiceInfo();
+ s.setServiceType(serviceType);
+
+ final int baseListenerKey = putListener(listener, executor, s);
+
+ final PerNetworkDiscoveryTracker discoveryInfo = new PerNetworkDiscoveryTracker(
+ serviceType, protocolType, executor, listener);
+
+ synchronized (mPerNetworkDiscoveryMap) {
+ mPerNetworkDiscoveryMap.put(baseListenerKey, discoveryInfo);
+ discoveryInfo.start(networkRequest);
+ }
+ }
+
+ /**
+ * Stop service discovery initiated with {@link #discoverServices}. An active service
+ * discovery is notified to the application with {@link DiscoveryListener#onDiscoveryStarted}
+ * and it stays active until the application invokes a stop service discovery. A successful
+ * stop is notified to with a call to {@link DiscoveryListener#onDiscoveryStopped}.
+ *
+ * <p> Upon failure to stop service discovery, application is notified through
+ * {@link DiscoveryListener#onStopDiscoveryFailed}.
+ *
+ * @param listener This should be the listener object that was passed to {@link #discoverServices}.
+ * It identifies the discovery that should be stopped and notifies of a successful or
+ * unsuccessful stop. In API versions 20 and above, the listener object may be used for
+ * another service discovery once the callback has been called. In API versions <= 19,
+ * there is no entirely reliable way to know when a listener may be re-used, and a new
+ * listener should be created for each service discovery request.
+ */
+ public void stopServiceDiscovery(DiscoveryListener listener) {
+ int id = getListenerKey(listener);
+ // If this is a PerNetworkDiscovery request, handle it as such
+ synchronized (mPerNetworkDiscoveryMap) {
+ final PerNetworkDiscoveryTracker info = mPerNetworkDiscoveryMap.get(id);
+ if (info != null) {
+ info.requestStop();
+ return;
+ }
+ }
+ try {
+ mService.stopDiscovery(id);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Resolve a discovered service. An application can resolve a service right before
+ * establishing a connection to fetch the IP and port details on which to setup
+ * the connection.
+ *
+ * @param serviceInfo service to be resolved
+ * @param listener to receive callback upon success or failure. Cannot be null.
+ * Cannot be in use for an active service resolution.
+ */
+ public void resolveService(NsdServiceInfo serviceInfo, ResolveListener listener) {
+ resolveService(serviceInfo, Runnable::run, listener);
+ }
+
+ /**
+ * Resolve a discovered service. An application can resolve a service right before
+ * establishing a connection to fetch the IP and port details on which to setup
+ * the connection.
+ * @param serviceInfo service to be resolved
+ * @param executor Executor to run listener callbacks with
+ * @param listener to receive callback upon success or failure.
+ */
+ public void resolveService(@NonNull NsdServiceInfo serviceInfo,
+ @NonNull Executor executor, @NonNull ResolveListener listener) {
+ checkServiceInfo(serviceInfo);
+ int key = putListener(listener, executor, serviceInfo);
+ try {
+ mService.resolveService(key, serviceInfo);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ private static void checkListener(Object listener) {
+ Objects.requireNonNull(listener, "listener cannot be null");
+ }
+
+ private static void checkProtocol(int protocolType) {
+ if (protocolType != PROTOCOL_DNS_SD) {
+ throw new IllegalArgumentException("Unsupported protocol");
+ }
+ }
+
+ private static void checkServiceInfo(NsdServiceInfo serviceInfo) {
+ Objects.requireNonNull(serviceInfo, "NsdServiceInfo cannot be null");
+ if (TextUtils.isEmpty(serviceInfo.getServiceName())) {
+ throw new IllegalArgumentException("Service name cannot be empty");
+ }
+ if (TextUtils.isEmpty(serviceInfo.getServiceType())) {
+ throw new IllegalArgumentException("Service type cannot be empty");
+ }
+ }
+}
diff --git a/framework-t/src/android/net/nsd/NsdServiceInfo.java b/framework-t/src/android/net/nsd/NsdServiceInfo.java
new file mode 100644
index 0000000..200c808
--- /dev/null
+++ b/framework-t/src/android/net/nsd/NsdServiceInfo.java
@@ -0,0 +1,442 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.nsd;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.net.Network;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import java.io.UnsupportedEncodingException;
+import java.net.InetAddress;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * A class representing service information for network service discovery
+ * {@see NsdManager}
+ */
+public final class NsdServiceInfo implements Parcelable {
+
+ private static final String TAG = "NsdServiceInfo";
+
+ private String mServiceName;
+
+ private String mServiceType;
+
+ private final ArrayMap<String, byte[]> mTxtRecord = new ArrayMap<>();
+
+ private InetAddress mHost;
+
+ private int mPort;
+
+ @Nullable
+ private Network mNetwork;
+
+ private int mInterfaceIndex;
+
+ public NsdServiceInfo() {
+ }
+
+ /** @hide */
+ public NsdServiceInfo(String sn, String rt) {
+ mServiceName = sn;
+ mServiceType = rt;
+ }
+
+ /** Get the service name */
+ public String getServiceName() {
+ return mServiceName;
+ }
+
+ /** Set the service name */
+ public void setServiceName(String s) {
+ mServiceName = s;
+ }
+
+ /** Get the service type */
+ public String getServiceType() {
+ return mServiceType;
+ }
+
+ /** Set the service type */
+ public void setServiceType(String s) {
+ mServiceType = s;
+ }
+
+ /** Get the host address. The host address is valid for a resolved service. */
+ public InetAddress getHost() {
+ return mHost;
+ }
+
+ /** Set the host address */
+ public void setHost(InetAddress s) {
+ mHost = s;
+ }
+
+ /** Get port number. The port number is valid for a resolved service. */
+ public int getPort() {
+ return mPort;
+ }
+
+ /** Set port number */
+ public void setPort(int p) {
+ mPort = p;
+ }
+
+ /**
+ * Unpack txt information from a base-64 encoded byte array.
+ *
+ * @param txtRecordsRawBytes The raw base64 encoded byte array.
+ *
+ * @hide
+ */
+ public void setTxtRecords(@NonNull byte[] txtRecordsRawBytes) {
+ // There can be multiple TXT records after each other. Each record has to following format:
+ //
+ // byte type required meaning
+ // ------------------- ------------------- -------- ----------------------------------
+ // 0 unsigned 8 bit yes size of record excluding this byte
+ // 1 - n ASCII but not '=' yes key
+ // n + 1 '=' optional separator of key and value
+ // n + 2 - record size uninterpreted bytes optional value
+ //
+ // Example legal records:
+ // [11, 'm', 'y', 'k', 'e', 'y', '=', 0x0, 0x4, 0x65, 0x7, 0xff]
+ // [17, 'm', 'y', 'K', 'e', 'y', 'W', 'i', 't', 'h', 'N', 'o', 'V', 'a', 'l', 'u', 'e', '=']
+ // [12, 'm', 'y', 'B', 'o', 'o', 'l', 'e', 'a', 'n', 'K', 'e', 'y']
+ //
+ // Example corrupted records
+ // [3, =, 1, 2] <- key is empty
+ // [3, 0, =, 2] <- key contains non-ASCII character. We handle this by replacing the
+ // invalid characters instead of skipping the record.
+ // [30, 'a', =, 2] <- length exceeds total left over bytes in the TXT records array, we
+ // handle this by reducing the length of the record as needed.
+ int pos = 0;
+ while (pos < txtRecordsRawBytes.length) {
+ // recordLen is an unsigned 8 bit value
+ int recordLen = txtRecordsRawBytes[pos] & 0xff;
+ pos += 1;
+
+ try {
+ if (recordLen == 0) {
+ throw new IllegalArgumentException("Zero sized txt record");
+ } else if (pos + recordLen > txtRecordsRawBytes.length) {
+ Log.w(TAG, "Corrupt record length (pos = " + pos + "): " + recordLen);
+ recordLen = txtRecordsRawBytes.length - pos;
+ }
+
+ // Decode key-value records
+ String key = null;
+ byte[] value = null;
+ int valueLen = 0;
+ for (int i = pos; i < pos + recordLen; i++) {
+ if (key == null) {
+ if (txtRecordsRawBytes[i] == '=') {
+ key = new String(txtRecordsRawBytes, pos, i - pos,
+ StandardCharsets.US_ASCII);
+ }
+ } else {
+ if (value == null) {
+ value = new byte[recordLen - key.length() - 1];
+ }
+ value[valueLen] = txtRecordsRawBytes[i];
+ valueLen++;
+ }
+ }
+
+ // If '=' was not found we have a boolean record
+ if (key == null) {
+ key = new String(txtRecordsRawBytes, pos, recordLen, StandardCharsets.US_ASCII);
+ }
+
+ if (TextUtils.isEmpty(key)) {
+ // Empty keys are not allowed (RFC6763 6.4)
+ throw new IllegalArgumentException("Invalid txt record (key is empty)");
+ }
+
+ if (getAttributes().containsKey(key)) {
+ // When we have a duplicate record, the later ones are ignored (RFC6763 6.4)
+ throw new IllegalArgumentException("Invalid txt record (duplicate key \"" + key + "\")");
+ }
+
+ setAttribute(key, value);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "While parsing txt records (pos = " + pos + "): " + e.getMessage());
+ }
+
+ pos += recordLen;
+ }
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public void setAttribute(String key, byte[] value) {
+ if (TextUtils.isEmpty(key)) {
+ throw new IllegalArgumentException("Key cannot be empty");
+ }
+
+ // Key must be printable US-ASCII, excluding =.
+ for (int i = 0; i < key.length(); ++i) {
+ char character = key.charAt(i);
+ if (character < 0x20 || character > 0x7E) {
+ throw new IllegalArgumentException("Key strings must be printable US-ASCII");
+ } else if (character == 0x3D) {
+ throw new IllegalArgumentException("Key strings must not include '='");
+ }
+ }
+
+ // Key length + value length must be < 255.
+ if (key.length() + (value == null ? 0 : value.length) >= 255) {
+ throw new IllegalArgumentException("Key length + value length must be < 255 bytes");
+ }
+
+ // Warn if key is > 9 characters, as recommended by RFC 6763 section 6.4.
+ if (key.length() > 9) {
+ Log.w(TAG, "Key lengths > 9 are discouraged: " + key);
+ }
+
+ // Check against total TXT record size limits.
+ // Arbitrary 400 / 1300 byte limits taken from RFC 6763 section 6.2.
+ int txtRecordSize = getTxtRecordSize();
+ int futureSize = txtRecordSize + key.length() + (value == null ? 0 : value.length) + 2;
+ if (futureSize > 1300) {
+ throw new IllegalArgumentException("Total length of attributes must be < 1300 bytes");
+ } else if (futureSize > 400) {
+ Log.w(TAG, "Total length of all attributes exceeds 400 bytes; truncation may occur");
+ }
+
+ mTxtRecord.put(key, value);
+ }
+
+ /**
+ * Add a service attribute as a key/value pair.
+ *
+ * <p> Service attributes are included as DNS-SD TXT record pairs.
+ *
+ * <p> The key must be US-ASCII printable characters, excluding the '=' character. Values may
+ * be UTF-8 strings or null. The total length of key + value must be less than 255 bytes.
+ *
+ * <p> Keys should be short, ideally no more than 9 characters, and unique per instance of
+ * {@link NsdServiceInfo}. Calling {@link #setAttribute} twice with the same key will overwrite
+ * first value.
+ */
+ public void setAttribute(String key, String value) {
+ try {
+ setAttribute(key, value == null ? (byte []) null : value.getBytes("UTF-8"));
+ } catch (UnsupportedEncodingException e) {
+ throw new IllegalArgumentException("Value must be UTF-8");
+ }
+ }
+
+ /** Remove an attribute by key */
+ public void removeAttribute(String key) {
+ mTxtRecord.remove(key);
+ }
+
+ /**
+ * Retrieve attributes as a map of String keys to byte[] values. The attributes map is only
+ * valid for a resolved service.
+ *
+ * <p> The returned map is unmodifiable; changes must be made through {@link #setAttribute} and
+ * {@link #removeAttribute}.
+ */
+ public Map<String, byte[]> getAttributes() {
+ return Collections.unmodifiableMap(mTxtRecord);
+ }
+
+ private int getTxtRecordSize() {
+ int txtRecordSize = 0;
+ for (Map.Entry<String, byte[]> entry : mTxtRecord.entrySet()) {
+ txtRecordSize += 2; // One for the length byte, one for the = between key and value.
+ txtRecordSize += entry.getKey().length();
+ byte[] value = entry.getValue();
+ txtRecordSize += value == null ? 0 : value.length;
+ }
+ return txtRecordSize;
+ }
+
+ /** @hide */
+ public @NonNull byte[] getTxtRecord() {
+ int txtRecordSize = getTxtRecordSize();
+ if (txtRecordSize == 0) {
+ return new byte[]{};
+ }
+
+ byte[] txtRecord = new byte[txtRecordSize];
+ int ptr = 0;
+ for (Map.Entry<String, byte[]> entry : mTxtRecord.entrySet()) {
+ String key = entry.getKey();
+ byte[] value = entry.getValue();
+
+ // One byte to record the length of this key/value pair.
+ txtRecord[ptr++] = (byte) (key.length() + (value == null ? 0 : value.length) + 1);
+
+ // The key, in US-ASCII.
+ // Note: use the StandardCharsets const here because it doesn't raise exceptions and we
+ // already know the key is ASCII at this point.
+ System.arraycopy(key.getBytes(StandardCharsets.US_ASCII), 0, txtRecord, ptr,
+ key.length());
+ ptr += key.length();
+
+ // US-ASCII '=' character.
+ txtRecord[ptr++] = (byte)'=';
+
+ // The value, as any raw bytes.
+ if (value != null) {
+ System.arraycopy(value, 0, txtRecord, ptr, value.length);
+ ptr += value.length;
+ }
+ }
+ return txtRecord;
+ }
+
+ /**
+ * Get the network where the service can be found.
+ *
+ * This is set if this {@link NsdServiceInfo} was obtained from
+ * {@link NsdManager#discoverServices} or {@link NsdManager#resolveService}, unless the service
+ * was found on a network interface that does not have a {@link Network} (such as a tethering
+ * downstream, where services are advertised from devices connected to this device via
+ * tethering).
+ */
+ @Nullable
+ public Network getNetwork() {
+ return mNetwork;
+ }
+
+ /**
+ * Set the network where the service can be found.
+ * @param network The network, or null to search for, or to announce, the service on all
+ * connected networks.
+ */
+ public void setNetwork(@Nullable Network network) {
+ mNetwork = network;
+ }
+
+ /**
+ * Get the index of the network interface where the service was found.
+ *
+ * This is only set when the service was found on an interface that does not have a usable
+ * Network, in which case {@link #getNetwork()} returns null.
+ * @return The interface index as per {@link java.net.NetworkInterface#getIndex}, or 0 if unset.
+ * @hide
+ */
+ public int getInterfaceIndex() {
+ return mInterfaceIndex;
+ }
+
+ /**
+ * Set the index of the network interface where the service was found.
+ * @hide
+ */
+ public void setInterfaceIndex(int interfaceIndex) {
+ mInterfaceIndex = interfaceIndex;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("name: ").append(mServiceName)
+ .append(", type: ").append(mServiceType)
+ .append(", host: ").append(mHost)
+ .append(", port: ").append(mPort)
+ .append(", network: ").append(mNetwork);
+
+ byte[] txtRecord = getTxtRecord();
+ sb.append(", txtRecord: ").append(new String(txtRecord, StandardCharsets.UTF_8));
+ return sb.toString();
+ }
+
+ /** Implement the Parcelable interface */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mServiceName);
+ dest.writeString(mServiceType);
+ if (mHost != null) {
+ dest.writeInt(1);
+ dest.writeByteArray(mHost.getAddress());
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeInt(mPort);
+
+ // TXT record key/value pairs.
+ dest.writeInt(mTxtRecord.size());
+ for (String key : mTxtRecord.keySet()) {
+ byte[] value = mTxtRecord.get(key);
+ if (value != null) {
+ dest.writeInt(1);
+ dest.writeInt(value.length);
+ dest.writeByteArray(value);
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeString(key);
+ }
+
+ dest.writeParcelable(mNetwork, 0);
+ dest.writeInt(mInterfaceIndex);
+ }
+
+ /** Implement the Parcelable interface */
+ public static final @android.annotation.NonNull Creator<NsdServiceInfo> CREATOR =
+ new Creator<NsdServiceInfo>() {
+ public NsdServiceInfo createFromParcel(Parcel in) {
+ NsdServiceInfo info = new NsdServiceInfo();
+ info.mServiceName = in.readString();
+ info.mServiceType = in.readString();
+
+ if (in.readInt() == 1) {
+ try {
+ info.mHost = InetAddress.getByAddress(in.createByteArray());
+ } catch (java.net.UnknownHostException e) {}
+ }
+
+ info.mPort = in.readInt();
+
+ // TXT record key/value pairs.
+ int recordCount = in.readInt();
+ for (int i = 0; i < recordCount; ++i) {
+ byte[] valueArray = null;
+ if (in.readInt() == 1) {
+ int valueLength = in.readInt();
+ valueArray = new byte[valueLength];
+ in.readByteArray(valueArray);
+ }
+ info.mTxtRecord.put(in.readString(), valueArray);
+ }
+ info.mNetwork = in.readParcelable(null, Network.class);
+ info.mInterfaceIndex = in.readInt();
+ return info;
+ }
+
+ public NsdServiceInfo[] newArray(int size) {
+ return new NsdServiceInfo[size];
+ }
+ };
+}
diff --git a/framework/Android.bp b/framework/Android.bp
index 53f9217..d7de439 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -64,6 +64,7 @@
":framework-connectivity-sources",
":net-utils-framework-common-srcs",
":framework-connectivity-api-shared-srcs",
+ ":framework-connectivity-javastream-protos",
],
aidl: {
generate_get_transaction_name: true,
@@ -85,12 +86,13 @@
"net-utils-device-common",
],
static_libs: [
- "framework-connectivity-protos",
+ "mdns_aidl_interface-lateststable-java",
"modules-utils-backgroundthread",
"modules-utils-build",
"modules-utils-preconditions",
],
libs: [
+ "app-compat-annotations",
"framework-connectivity-t.stubs.module_lib",
"unsupportedappusage",
],
@@ -151,6 +153,11 @@
],
}
+platform_compat_config {
+ name: "connectivity-platform-compat-config",
+ src: ":framework-connectivity",
+}
+
cc_library_shared {
name: "libframework-connectivity-jni",
min_sdk_version: "30",
@@ -182,22 +189,36 @@
],
}
-// TODO: reduce size of this library; consider using
-// proto nano for example
-java_library {
+filegroup {
name: "framework-connectivity-protos",
- sdk_version: "module_current",
- min_sdk_version: "30",
- proto: {
- type: "lite",
- },
srcs: [
- "proto/**/*.*",
+ "proto/**/*.proto",
],
- static_libs: ["libprotobuf-java-lite"],
- apex_available: [
- "com.android.tethering",
+ visibility: ["//frameworks/base"],
+}
+
+gensrcs {
+ name: "framework-connectivity-javastream-protos",
+ depfile: true,
+
+ tools: [
+ "aprotoc",
+ "protoc-gen-javastream",
+ "soong_zip",
],
- lint: { strict_updatability_linting: true },
- visibility: ["//visibility:private"],
+
+ cmd: "mkdir -p $(genDir)/$(in) " +
+ "&& $(location aprotoc) " +
+ " --plugin=$(location protoc-gen-javastream) " +
+ " --dependency_out=$(depfile) " +
+ " --javastream_out=$(genDir)/$(in) " +
+ " -Iexternal/protobuf/src " +
+ " -I . " +
+ " $(in) " +
+ "&& $(location soong_zip) -jar -o $(out) -C $(genDir)/$(in) -D $(genDir)/$(in)",
+
+ srcs: [
+ ":framework-connectivity-protos",
+ ],
+ output_extension: "srcjar",
}
diff --git a/framework/aidl-export/android/net/NetworkStats.aidl b/framework/aidl-export/android/net/NetworkStats.aidl
new file mode 100644
index 0000000..d06ca65
--- /dev/null
+++ b/framework/aidl-export/android/net/NetworkStats.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2011, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+parcelable NetworkStats;
diff --git a/framework/aidl-export/android/net/NetworkTemplate.aidl b/framework/aidl-export/android/net/NetworkTemplate.aidl
new file mode 100644
index 0000000..3d37488
--- /dev/null
+++ b/framework/aidl-export/android/net/NetworkTemplate.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2011, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+parcelable NetworkTemplate;
diff --git a/framework/aidl-export/android/net/nsd/NsdServiceInfo.aidl b/framework/aidl-export/android/net/nsd/NsdServiceInfo.aidl
new file mode 100644
index 0000000..657bdd1
--- /dev/null
+++ b/framework/aidl-export/android/net/nsd/NsdServiceInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.nsd;
+
+@JavaOnlyStableParcelable parcelable NsdServiceInfo;
\ No newline at end of file
diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt
index e4e2151..ddac19d 100644
--- a/framework/api/module-lib-current.txt
+++ b/framework/api/module-lib-current.txt
@@ -30,10 +30,10 @@
method @Deprecated @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setProfileNetworkPreference(@NonNull android.os.UserHandle, int, @Nullable java.util.concurrent.Executor, @Nullable Runnable);
method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setProfileNetworkPreferences(@NonNull android.os.UserHandle, @NonNull java.util.List<android.net.ProfileNetworkPreference>, @Nullable java.util.concurrent.Executor, @Nullable Runnable);
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setRequireVpnForUids(boolean, @NonNull java.util.Collection<android.util.Range<java.lang.Integer>>);
+ method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setUidFirewallRule(int, int, int);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void startCaptivePortalApp(@NonNull android.net.Network);
method public void systemReady();
- method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void updateFirewallRule(int, int, boolean);
field public static final String ACTION_CLEAR_DNS_CACHE = "android.net.action.CLEAR_DNS_CACHE";
field public static final String ACTION_PROMPT_LOST_VALIDATION = "android.net.action.PROMPT_LOST_VALIDATION";
field public static final String ACTION_PROMPT_PARTIAL_CONNECTIVITY = "android.net.action.PROMPT_PARTIAL_CONNECTIVITY";
@@ -54,6 +54,9 @@
field public static final int FIREWALL_CHAIN_POWERSAVE = 3; // 0x3
field public static final int FIREWALL_CHAIN_RESTRICTED = 4; // 0x4
field public static final int FIREWALL_CHAIN_STANDBY = 2; // 0x2
+ field public static final int FIREWALL_RULE_ALLOW = 1; // 0x1
+ field public static final int FIREWALL_RULE_DEFAULT = 0; // 0x0
+ field public static final int FIREWALL_RULE_DENY = 2; // 0x2
field public static final int PROFILE_NETWORK_PREFERENCE_DEFAULT = 0; // 0x0
field public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE = 1; // 0x1
field public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK = 2; // 0x2
diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt
index 53d485d..db1d7e9 100644
--- a/framework/api/system-current.txt
+++ b/framework/api/system-current.txt
@@ -104,12 +104,6 @@
field @NonNull public static final android.os.Parcelable.Creator<android.net.DscpPolicy> CREATOR;
field public static final int PROTOCOL_ANY = -1; // 0xffffffff
field public static final int SOURCE_PORT_ANY = -1; // 0xffffffff
- field public static final int STATUS_DELETED = 4; // 0x4
- field public static final int STATUS_INSUFFICIENT_PROCESSING_RESOURCES = 3; // 0x3
- field public static final int STATUS_POLICY_NOT_FOUND = 5; // 0x5
- field public static final int STATUS_REQUESTED_CLASSIFIER_NOT_SUPPORTED = 2; // 0x2
- field public static final int STATUS_REQUEST_DECLINED = 1; // 0x1
- field public static final int STATUS_SUCCESS = 0; // 0x0
}
public static final class DscpPolicy.Builder {
@@ -236,7 +230,6 @@
public abstract class NetworkAgent {
ctor public NetworkAgent(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String, @NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties, int, @NonNull android.net.NetworkAgentConfig, @Nullable android.net.NetworkProvider);
ctor public NetworkAgent(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String, @NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties, @NonNull android.net.NetworkScore, @NonNull android.net.NetworkAgentConfig, @Nullable android.net.NetworkProvider);
- method public void destroyAndAwaitReplacement(@IntRange(from=0, to=0x1388) int);
method @Nullable public android.net.Network getNetwork();
method public void markConnected();
method public void onAddKeepalivePacketFilter(int, @NonNull android.net.KeepalivePacketData);
@@ -271,6 +264,13 @@
method public void setTeardownDelayMillis(@IntRange(from=0, to=0x1388) int);
method public final void setUnderlyingNetworks(@Nullable java.util.List<android.net.Network>);
method public void unregister();
+ method public void unregisterAfterReplacement(@IntRange(from=0, to=0x1388) int);
+ field public static final int DSCP_POLICY_STATUS_DELETED = 4; // 0x4
+ field public static final int DSCP_POLICY_STATUS_INSUFFICIENT_PROCESSING_RESOURCES = 3; // 0x3
+ field public static final int DSCP_POLICY_STATUS_POLICY_NOT_FOUND = 5; // 0x5
+ field public static final int DSCP_POLICY_STATUS_REQUESTED_CLASSIFIER_NOT_SUPPORTED = 2; // 0x2
+ field public static final int DSCP_POLICY_STATUS_REQUEST_DECLINED = 1; // 0x1
+ field public static final int DSCP_POLICY_STATUS_SUCCESS = 0; // 0x0
field public static final int VALIDATION_STATUS_NOT_VALID = 2; // 0x2
field public static final int VALIDATION_STATUS_VALID = 1; // 0x1
}
@@ -340,7 +340,7 @@
method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setSsid(@Nullable String);
method @NonNull public android.net.NetworkCapabilities.Builder setSubscriptionIds(@NonNull java.util.Set<java.lang.Integer>);
method @NonNull public android.net.NetworkCapabilities.Builder setTransportInfo(@Nullable android.net.TransportInfo);
- method @NonNull public android.net.NetworkCapabilities.Builder setUnderlyingNetworks(@Nullable java.util.List<android.net.Network>);
+ method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setUnderlyingNetworks(@Nullable java.util.List<android.net.Network>);
method @NonNull public static android.net.NetworkCapabilities.Builder withoutDefaultCapabilities();
}
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index a798f6e..fdc7bf7 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -982,6 +982,30 @@
@SystemApi(client = MODULE_LIBRARIES)
public static final int FIREWALL_CHAIN_LOW_POWER_STANDBY = 5;
+ /**
+ * Firewall chain used for lockdown VPN.
+ * Denylist of apps that cannot receive incoming packets except on loopback because they are
+ * subject to an always-on VPN which is not currently connected.
+ *
+ * @see #BLOCKED_REASON_LOCKDOWN_VPN
+ * @hide
+ */
+ public static final int FIREWALL_CHAIN_LOCKDOWN_VPN = 6;
+
+ /**
+ * Firewall chain used for OEM-specific application restrictions.
+ * Denylist of apps that will not have network access due to OEM-specific restrictions.
+ * @hide
+ */
+ public static final int FIREWALL_CHAIN_OEM_DENY_1 = 7;
+
+ /**
+ * Firewall chain used for OEM-specific application restrictions.
+ * Denylist of apps that will not have network access due to OEM-specific restrictions.
+ * @hide
+ */
+ public static final int FIREWALL_CHAIN_OEM_DENY_2 = 8;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = false, prefix = "FIREWALL_CHAIN_", value = {
@@ -989,12 +1013,49 @@
FIREWALL_CHAIN_STANDBY,
FIREWALL_CHAIN_POWERSAVE,
FIREWALL_CHAIN_RESTRICTED,
- FIREWALL_CHAIN_LOW_POWER_STANDBY
+ FIREWALL_CHAIN_LOW_POWER_STANDBY,
+ FIREWALL_CHAIN_LOCKDOWN_VPN,
+ FIREWALL_CHAIN_OEM_DENY_1,
+ FIREWALL_CHAIN_OEM_DENY_2
})
public @interface FirewallChain {}
// LINT.ThenChange(packages/modules/Connectivity/service/native/include/Common.h)
/**
+ * A firewall rule which allows or drops packets depending on existing policy.
+ * Used by {@link #setUidFirewallRule(int, int, int)} to follow existing policy to handle
+ * specific uid's packets in specific firewall chain.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static final int FIREWALL_RULE_DEFAULT = 0;
+
+ /**
+ * A firewall rule which allows packets. Used by {@link #setUidFirewallRule(int, int, int)} to
+ * allow specific uid's packets in specific firewall chain.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static final int FIREWALL_RULE_ALLOW = 1;
+
+ /**
+ * A firewall rule which drops packets. Used by {@link #setUidFirewallRule(int, int, int)} to
+ * drop specific uid's packets in specific firewall chain.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static final int FIREWALL_RULE_DENY = 2;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = false, prefix = "FIREWALL_RULE_", value = {
+ FIREWALL_RULE_DEFAULT,
+ FIREWALL_RULE_ALLOW,
+ FIREWALL_RULE_DENY
+ })
+ public @interface FirewallRule {}
+
+ /**
* A kludge to facilitate static access where a Context pointer isn't available, like in the
* case of the static set/getProcessDefaultNetwork methods and from the Network class.
* TODO: Remove this after deprecating the static methods in favor of non-static methods or
@@ -2555,9 +2616,24 @@
* {@hide}
*/
public ConnectivityManager(Context context, IConnectivityManager service) {
+ this(context, service, true /* newStatic */);
+ }
+
+ private ConnectivityManager(Context context, IConnectivityManager service, boolean newStatic) {
mContext = Objects.requireNonNull(context, "missing context");
mService = Objects.requireNonNull(service, "missing IConnectivityManager");
- sInstance = this;
+ // sInstance is accessed without a lock, so it may actually be reassigned several times with
+ // different ConnectivityManager, but that's still OK considering its usage.
+ if (sInstance == null && newStatic) {
+ final Context appContext = mContext.getApplicationContext();
+ // Don't create static ConnectivityManager instance again to prevent infinite loop.
+ // If the application context is null, we're either in the system process or
+ // it's the application context very early in app initialization. In both these
+ // cases, the passed-in Context will not be freed, so it's safe to pass it to the
+ // service. http://b/27532714 .
+ sInstance = new ConnectivityManager(appContext != null ? appContext : context, service,
+ false /* newStatic */);
+ }
}
/** {@hide} */
@@ -5802,8 +5878,9 @@
*
* @param chain target chain.
* @param uid uid to allow/deny.
- * @param allow whether networking is allowed or denied.
+ * @param rule firewall rule to allow/drop packets.
* @throws IllegalStateException if updating firewall rule failed.
+ * @throws IllegalArgumentException if {@code rule} is not a valid rule.
* @hide
*/
@SystemApi(client = MODULE_LIBRARIES)
@@ -5812,10 +5889,10 @@
android.Manifest.permission.NETWORK_STACK,
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK
})
- public void updateFirewallRule(@FirewallChain final int chain, final int uid,
- final boolean allow) {
+ public void setUidFirewallRule(@FirewallChain final int chain, final int uid,
+ @FirewallRule final int rule) {
try {
- mService.updateFirewallRule(chain, uid, allow);
+ mService.setUidFirewallRule(chain, uid, rule);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/framework/src/android/net/DscpPolicy.java b/framework/src/android/net/DscpPolicy.java
index cda8205..6af795b 100644
--- a/framework/src/android/net/DscpPolicy.java
+++ b/framework/src/android/net/DscpPolicy.java
@@ -16,7 +16,6 @@
package android.net;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
@@ -26,8 +25,6 @@
import com.android.net.module.util.InetAddressUtils;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.util.Objects;
@@ -49,36 +46,6 @@
*/
public static final int SOURCE_PORT_ANY = -1;
- /**
- * Policy was successfully added.
- */
- public static final int STATUS_SUCCESS = 0;
-
- /**
- * Policy was rejected for any reason besides invalid classifier or insufficient resources.
- */
- public static final int STATUS_REQUEST_DECLINED = 1;
-
- /**
- * Requested policy contained a classifier which is not supported.
- */
- public static final int STATUS_REQUESTED_CLASSIFIER_NOT_SUPPORTED = 2;
-
- /**
- * TODO: should this error case be supported?
- */
- public static final int STATUS_INSUFFICIENT_PROCESSING_RESOURCES = 3;
-
- /**
- * Policy was deleted.
- */
- public static final int STATUS_DELETED = 4;
-
- /**
- * Policy was not found during deletion.
- */
- public static final int STATUS_POLICY_NOT_FOUND = 5;
-
/** The unique policy ID. Each requesting network is responsible for maintaining policy IDs
* unique within that network. In the case where a policy with an existing ID is created, the
* new policy will update the existing policy with the same ID.
@@ -112,17 +79,6 @@
return 0;
}
- /** @hide */
- @IntDef(prefix = "STATUS_", value = {
- STATUS_SUCCESS,
- STATUS_REQUEST_DECLINED,
- STATUS_REQUESTED_CLASSIFIER_NOT_SUPPORTED,
- STATUS_INSUFFICIENT_PROCESSING_RESOURCES,
- STATUS_DELETED
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface DscpPolicyStatus {}
-
/* package */ DscpPolicy(
int policyId,
int dscp,
diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl
index 0988bf3..bc73769 100644
--- a/framework/src/android/net/IConnectivityManager.aidl
+++ b/framework/src/android/net/IConnectivityManager.aidl
@@ -240,7 +240,7 @@
void updateMeteredNetworkDenyList(int uid, boolean add);
- void updateFirewallRule(int chain, int uid, boolean allow);
+ void setUidFirewallRule(int chain, int uid, int rule);
void setFirewallChainEnabled(int chain, boolean enable);
diff --git a/framework/src/android/net/INetworkAgentRegistry.aidl b/framework/src/android/net/INetworkAgentRegistry.aidl
index 2b22a5c..b375b7b 100644
--- a/framework/src/android/net/INetworkAgentRegistry.aidl
+++ b/framework/src/android/net/INetworkAgentRegistry.aidl
@@ -47,5 +47,5 @@
void sendAddDscpPolicy(in DscpPolicy policy);
void sendRemoveDscpPolicy(int policyId);
void sendRemoveAllDscpPolicies();
- void sendDestroyAndAwaitReplacement(int timeoutMillis);
+ void sendUnregisterAfterReplacement(int timeoutMillis);
}
diff --git a/framework/src/android/net/ITestNetworkManager.aidl b/framework/src/android/net/ITestNetworkManager.aidl
index 847f14e..27d13c1 100644
--- a/framework/src/android/net/ITestNetworkManager.aidl
+++ b/framework/src/android/net/ITestNetworkManager.aidl
@@ -29,7 +29,8 @@
*/
interface ITestNetworkManager
{
- TestNetworkInterface createInterface(boolean isTun, boolean bringUp, in LinkAddress[] addrs);
+ TestNetworkInterface createInterface(boolean isTun, boolean bringUp, in LinkAddress[] addrs,
+ in @nullable String iface);
void setupTestNetwork(in String iface, in LinkProperties lp, in boolean isMetered,
in int[] administratorUids, in IBinder binder);
diff --git a/framework/src/android/net/LinkProperties.java b/framework/src/android/net/LinkProperties.java
index 99f48b4..a8f707e 100644
--- a/framework/src/android/net/LinkProperties.java
+++ b/framework/src/android/net/LinkProperties.java
@@ -19,12 +19,16 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.net.module.util.LinkPropertiesUtils;
import java.net.Inet4Address;
@@ -38,6 +42,7 @@
import java.util.List;
import java.util.Objects;
import java.util.StringJoiner;
+import java.util.stream.Collectors;
/**
* Describes the properties of a network link.
@@ -52,6 +57,17 @@
*
*/
public final class LinkProperties implements Parcelable {
+ /**
+ * The {@link #getRoutes()} now can contain excluded as well as included routes. Use
+ * {@link RouteInfo#getType()} to determine route type.
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S_V2)
+ @VisibleForTesting
+ public static final long EXCLUDED_ROUTES = 186082280;
+
// The interface described by the network link.
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private String mIfaceName;
@@ -738,10 +754,25 @@
/**
* Returns all the {@link RouteInfo} set on this link.
*
+ * Only unicast routes are returned for apps targeting Android S or below.
+ *
* @return An unmodifiable {@link List} of {@link RouteInfo} for this link.
*/
public @NonNull List<RouteInfo> getRoutes() {
- return Collections.unmodifiableList(mRoutes);
+ if (CompatChanges.isChangeEnabled(EXCLUDED_ROUTES)) {
+ return Collections.unmodifiableList(mRoutes);
+ } else {
+ return Collections.unmodifiableList(getUnicastRoutes());
+ }
+ }
+
+ /**
+ * Returns all the {@link RouteInfo} of type {@link RouteInfo#RTN_UNICAST} set on this link.
+ */
+ private @NonNull List<RouteInfo> getUnicastRoutes() {
+ return mRoutes.stream()
+ .filter(route -> route.getType() == RouteInfo.RTN_UNICAST)
+ .collect(Collectors.toList());
}
/**
@@ -757,11 +788,14 @@
/**
* Returns all the routes on this link and all the links stacked above it.
+ *
+ * Only unicast routes are returned for apps targeting Android S or below.
+ *
* @hide
*/
@SystemApi
public @NonNull List<RouteInfo> getAllRoutes() {
- List<RouteInfo> routes = new ArrayList<>(mRoutes);
+ final List<RouteInfo> routes = new ArrayList<>(getRoutes());
for (LinkProperties stacked: mStackedLinks.values()) {
routes.addAll(stacked.getAllRoutes());
}
@@ -1332,6 +1366,21 @@
}
/**
+ * Returns true if this link has a throw route.
+ *
+ * @return {@code true} if there is an exclude route, {@code false} otherwise.
+ * @hide
+ */
+ public boolean hasExcludeRoute() {
+ for (RouteInfo r : mRoutes) {
+ if (r.getType() == RouteInfo.RTN_THROW) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
* Compares this {@code LinkProperties} interface name against the target
*
* @param target LinkProperties to compare.
diff --git a/framework/src/android/net/NetworkAgent.java b/framework/src/android/net/NetworkAgent.java
index fdc9081..2c50c73 100644
--- a/framework/src/android/net/NetworkAgent.java
+++ b/framework/src/android/net/NetworkAgent.java
@@ -25,7 +25,6 @@
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
-import android.net.DscpPolicy.DscpPolicyStatus;
import android.os.Build;
import android.os.Bundle;
import android.os.ConditionVariable;
@@ -435,12 +434,54 @@
public static final int CMD_DSCP_POLICY_STATUS = BASE + 28;
/**
+ * DSCP policy was successfully added.
+ */
+ public static final int DSCP_POLICY_STATUS_SUCCESS = 0;
+
+ /**
+ * DSCP policy was rejected for any reason besides invalid classifier or insufficient resources.
+ */
+ public static final int DSCP_POLICY_STATUS_REQUEST_DECLINED = 1;
+
+ /**
+ * Requested DSCP policy contained a classifier which is not supported.
+ */
+ public static final int DSCP_POLICY_STATUS_REQUESTED_CLASSIFIER_NOT_SUPPORTED = 2;
+
+ /**
+ * Requested DSCP policy was not added due to insufficient processing resources.
+ */
+ // TODO: should this error case be supported?
+ public static final int DSCP_POLICY_STATUS_INSUFFICIENT_PROCESSING_RESOURCES = 3;
+
+ /**
+ * DSCP policy was deleted.
+ */
+ public static final int DSCP_POLICY_STATUS_DELETED = 4;
+
+ /**
+ * DSCP policy was not found during deletion.
+ */
+ public static final int DSCP_POLICY_STATUS_POLICY_NOT_FOUND = 5;
+
+ /** @hide */
+ @IntDef(prefix = "DSCP_POLICY_STATUS_", value = {
+ DSCP_POLICY_STATUS_SUCCESS,
+ DSCP_POLICY_STATUS_REQUEST_DECLINED,
+ DSCP_POLICY_STATUS_REQUESTED_CLASSIFIER_NOT_SUPPORTED,
+ DSCP_POLICY_STATUS_INSUFFICIENT_PROCESSING_RESOURCES,
+ DSCP_POLICY_STATUS_DELETED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DscpPolicyStatus {}
+
+ /**
* Sent by the NetworkAgent to ConnectivityService to notify that this network is expected to be
* replaced within the specified time by a similar network.
* arg1 = timeout in milliseconds
* @hide
*/
- public static final int EVENT_DESTROY_AND_AWAIT_REPLACEMENT = BASE + 29;
+ public static final int EVENT_UNREGISTER_AFTER_REPLACEMENT = BASE + 29;
private static NetworkInfo getLegacyNetworkInfo(final NetworkAgentConfig config) {
final NetworkInfo ni = new NetworkInfo(config.legacyType, config.legacySubType,
@@ -984,9 +1025,9 @@
* @param timeoutMillis the timeout after which this network will be unregistered even if
* {@link #unregister} was not called.
*/
- public void destroyAndAwaitReplacement(
+ public void unregisterAfterReplacement(
@IntRange(from = 0, to = MAX_TEARDOWN_DELAY_MS) int timeoutMillis) {
- queueOrSendMessage(reg -> reg.sendDestroyAndAwaitReplacement(timeoutMillis));
+ queueOrSendMessage(reg -> reg.sendUnregisterAfterReplacement(timeoutMillis));
}
/**
@@ -1035,11 +1076,12 @@
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public final void sendNetworkInfo(NetworkInfo networkInfo) {
- queueOrSendNetworkInfo(new NetworkInfo(networkInfo));
+ queueOrSendNetworkInfo(networkInfo);
}
private void queueOrSendNetworkInfo(NetworkInfo networkInfo) {
- queueOrSendMessage(reg -> reg.sendNetworkInfo(networkInfo));
+ final NetworkInfo ni = new NetworkInfo(networkInfo);
+ queueOrSendMessage(reg -> reg.sendNetworkInfo(ni));
}
/**
diff --git a/framework/src/android/net/NetworkAgentConfig.java b/framework/src/android/net/NetworkAgentConfig.java
index b28c006..0d2b620 100644
--- a/framework/src/android/net/NetworkAgentConfig.java
+++ b/framework/src/android/net/NetworkAgentConfig.java
@@ -24,6 +24,8 @@
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.modules.utils.build.SdkLevel;
+
import java.util.Objects;
/**
@@ -473,6 +475,9 @@
@NonNull
@SystemApi(client = MODULE_LIBRARIES)
public Builder setLocalRoutesExcludedForVpn(boolean excludeLocalRoutes) {
+ if (!SdkLevel.isAtLeastT()) {
+ throw new UnsupportedOperationException("Method is not supported");
+ }
mConfig.excludeLocalRouteVpn = excludeLocalRoutes;
return this;
}
diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java
index f7f2f57..97b1f32 100644
--- a/framework/src/android/net/NetworkCapabilities.java
+++ b/framework/src/android/net/NetworkCapabilities.java
@@ -863,8 +863,11 @@
}
/**
- * Get the underlying networks of this network. If the caller is not system privileged, this is
- * always redacted to null and it will be never useful to the caller.
+ * Get the underlying networks of this network. If the caller doesn't have one of
+ * {@link android.Manifest.permission.NETWORK_FACTORY},
+ * {@link android.Manifest.permission.NETWORK_SETTINGS} and
+ * {@link NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}, this is always redacted to null and
+ * it will be never useful to the caller.
*
* @return <li>If the list is null, this network hasn't declared underlying networks.</li>
* <li>If the list is empty, this network has declared that it has no underlying
@@ -2650,7 +2653,7 @@
/**
* Builder class for NetworkCapabilities.
*
- * This class is mainly for for {@link NetworkAgent} instances to use. Many fields in
+ * This class is mainly for {@link NetworkAgent} instances to use. Many fields in
* the built class require holding a signature permission to use - mostly
* {@link android.Manifest.permission.NETWORK_FACTORY}, but refer to the specific
* description of each setter. As this class lives entirely in app space it does not
@@ -3058,9 +3061,20 @@
/**
* Set the underlying networks of this network.
*
+ * <p>This API is mainly for {@link NetworkAgent}s who hold
+ * {@link android.Manifest.permission.NETWORK_FACTORY} to set its underlying networks.
+ *
+ * <p>The underlying networks are only visible for the receiver who has one of
+ * {@link android.Manifest.permission.NETWORK_FACTORY},
+ * {@link android.Manifest.permission.NETWORK_SETTINGS} and
+ * {@link NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}.
+ * If the receiver doesn't have required permissions, the field will be cleared before
+ * sending to the caller.</p>
+ *
* @param networks The underlying networks of this network.
*/
@NonNull
+ @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
public Builder setUnderlyingNetworks(@Nullable List<Network> networks) {
mCaps.setUnderlyingNetworks(networks);
return this;
diff --git a/framework/src/android/net/ProfileNetworkPreference.java b/framework/src/android/net/ProfileNetworkPreference.java
index fb271e3..fdcab02 100644
--- a/framework/src/android/net/ProfileNetworkPreference.java
+++ b/framework/src/android/net/ProfileNetworkPreference.java
@@ -120,8 +120,8 @@
public String toString() {
return "ProfileNetworkPreference{"
+ "mPreference=" + getPreference()
- + "mIncludedUids=" + mIncludedUids.toString()
- + "mExcludedUids=" + mExcludedUids.toString()
+ + "mIncludedUids=" + Arrays.toString(mIncludedUids)
+ + "mExcludedUids=" + Arrays.toString(mExcludedUids)
+ "mPreferenceEnterpriseId=" + mPreferenceEnterpriseId
+ '}';
}
diff --git a/framework/src/android/net/QosCallbackException.java b/framework/src/android/net/QosCallbackException.java
index ed6eb15..b80cff4 100644
--- a/framework/src/android/net/QosCallbackException.java
+++ b/framework/src/android/net/QosCallbackException.java
@@ -46,8 +46,10 @@
EX_TYPE_FILTER_NONE,
EX_TYPE_FILTER_NETWORK_RELEASED,
EX_TYPE_FILTER_SOCKET_NOT_BOUND,
+ EX_TYPE_FILTER_SOCKET_NOT_CONNECTED,
EX_TYPE_FILTER_NOT_SUPPORTED,
EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED,
+ EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ExceptionType {}
@@ -65,10 +67,16 @@
public static final int EX_TYPE_FILTER_SOCKET_NOT_BOUND = 2;
/** {@hide} */
- public static final int EX_TYPE_FILTER_NOT_SUPPORTED = 3;
+ public static final int EX_TYPE_FILTER_SOCKET_NOT_CONNECTED = 3;
/** {@hide} */
- public static final int EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED = 4;
+ public static final int EX_TYPE_FILTER_NOT_SUPPORTED = 4;
+
+ /** {@hide} */
+ public static final int EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED = 5;
+
+ /** {@hide} */
+ public static final int EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED = 6;
/**
* Creates exception based off of a type and message. Not all types of exceptions accept a
@@ -83,12 +91,17 @@
return new QosCallbackException(new NetworkReleasedException());
case EX_TYPE_FILTER_SOCKET_NOT_BOUND:
return new QosCallbackException(new SocketNotBoundException());
+ case EX_TYPE_FILTER_SOCKET_NOT_CONNECTED:
+ return new QosCallbackException(new SocketNotConnectedException());
case EX_TYPE_FILTER_NOT_SUPPORTED:
return new QosCallbackException(new UnsupportedOperationException(
"This device does not support the specified filter"));
case EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED:
return new QosCallbackException(
new SocketLocalAddressChangedException());
+ case EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED:
+ return new QosCallbackException(
+ new SocketRemoteAddressChangedException());
default:
Log.wtf(TAG, "create: No case setup for exception type: '" + type + "'");
return new QosCallbackException(
diff --git a/framework/src/android/net/QosFilter.java b/framework/src/android/net/QosFilter.java
index 5c1c3cc..b432644 100644
--- a/framework/src/android/net/QosFilter.java
+++ b/framework/src/android/net/QosFilter.java
@@ -90,5 +90,15 @@
*/
public abstract boolean matchesRemoteAddress(@NonNull InetAddress address,
int startPort, int endPort);
+
+ /**
+ * Determines whether or not the parameter will be matched with this filter.
+ *
+ * @param protocol the protocol such as TCP or UDP included in IP packet filter set of a QoS
+ * flow assigned on {@link Network}.
+ * @return whether the parameters match the socket type of the filter
+ * @hide
+ */
+ public abstract boolean matchesProtocol(int protocol);
}
diff --git a/framework/src/android/net/QosFilterParcelable.java b/framework/src/android/net/QosFilterParcelable.java
index da3b2cf..6e71fa3 100644
--- a/framework/src/android/net/QosFilterParcelable.java
+++ b/framework/src/android/net/QosFilterParcelable.java
@@ -104,7 +104,7 @@
if (mQosFilter instanceof QosSocketFilter) {
dest.writeInt(QOS_SOCKET_FILTER);
final QosSocketFilter qosSocketFilter = (QosSocketFilter) mQosFilter;
- qosSocketFilter.getQosSocketInfo().writeToParcel(dest, 0);
+ qosSocketFilter.getQosSocketInfo().writeToParcelWithoutFd(dest, 0);
return;
}
dest.writeInt(NO_FILTER_PRESENT);
diff --git a/framework/src/android/net/QosSocketFilter.java b/framework/src/android/net/QosSocketFilter.java
index 69da7f4..5ceeb67 100644
--- a/framework/src/android/net/QosSocketFilter.java
+++ b/framework/src/android/net/QosSocketFilter.java
@@ -18,6 +18,13 @@
import static android.net.QosCallbackException.EX_TYPE_FILTER_NONE;
import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED;
+import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_NOT_BOUND;
+import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_NOT_CONNECTED;
+import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED;
+import static android.system.OsConstants.IPPROTO_TCP;
+import static android.system.OsConstants.IPPROTO_UDP;
+import static android.system.OsConstants.SOCK_DGRAM;
+import static android.system.OsConstants.SOCK_STREAM;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -74,19 +81,34 @@
* 2. In the scenario that the socket is now bound to a different local address, which can
* happen in the case of UDP, then
* {@link QosCallbackException.EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED} is returned.
+ * 3. In the scenario that the UDP socket changed remote address, then
+ * {@link QosCallbackException.EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED} is returned.
+ *
* @return validation error code
*/
@Override
public int validate() {
- final InetSocketAddress sa = getAddressFromFileDescriptor();
- if (sa == null) {
- return QosCallbackException.EX_TYPE_FILTER_SOCKET_NOT_BOUND;
+ final InetSocketAddress sa = getLocalAddressFromFileDescriptor();
+
+ if (sa == null || (sa.getAddress().isAnyLocalAddress() && sa.getPort() == 0)) {
+ return EX_TYPE_FILTER_SOCKET_NOT_BOUND;
}
if (!sa.equals(mQosSocketInfo.getLocalSocketAddress())) {
return EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED;
}
+ if (mQosSocketInfo.getRemoteSocketAddress() != null) {
+ final InetSocketAddress da = getRemoteAddressFromFileDescriptor();
+ if (da == null) {
+ return EX_TYPE_FILTER_SOCKET_NOT_CONNECTED;
+ }
+
+ if (!da.equals(mQosSocketInfo.getRemoteSocketAddress())) {
+ return EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED;
+ }
+ }
+
return EX_TYPE_FILTER_NONE;
}
@@ -98,17 +120,14 @@
* @return the local address
*/
@Nullable
- private InetSocketAddress getAddressFromFileDescriptor() {
+ private InetSocketAddress getLocalAddressFromFileDescriptor() {
final ParcelFileDescriptor parcelFileDescriptor = mQosSocketInfo.getParcelFileDescriptor();
- if (parcelFileDescriptor == null) return null;
-
final FileDescriptor fd = parcelFileDescriptor.getFileDescriptor();
- if (fd == null) return null;
final SocketAddress address;
try {
address = Os.getsockname(fd);
- } catch (final ErrnoException e) {
+ } catch (ErrnoException e) {
Log.e(TAG, "getAddressFromFileDescriptor: getLocalAddress exception", e);
return null;
}
@@ -119,6 +138,31 @@
}
/**
+ * The remote address of the socket's connected.
+ *
+ * <p>Note: If the socket is no longer connected, null is returned.
+ *
+ * @return the remote address
+ */
+ @Nullable
+ private InetSocketAddress getRemoteAddressFromFileDescriptor() {
+ final ParcelFileDescriptor parcelFileDescriptor = mQosSocketInfo.getParcelFileDescriptor();
+ final FileDescriptor fd = parcelFileDescriptor.getFileDescriptor();
+
+ final SocketAddress address;
+ try {
+ address = Os.getpeername(fd);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "getAddressFromFileDescriptor: getRemoteAddress exception", e);
+ return null;
+ }
+ if (address instanceof InetSocketAddress) {
+ return (InetSocketAddress) address;
+ }
+ return null;
+ }
+
+ /**
* The network used with this filter.
*
* @return the registered {@link Network}
@@ -156,6 +200,18 @@
}
/**
+ * @inheritDoc
+ */
+ @Override
+ public boolean matchesProtocol(final int protocol) {
+ if ((mQosSocketInfo.getSocketType() == SOCK_STREAM && protocol == IPPROTO_TCP)
+ || (mQosSocketInfo.getSocketType() == SOCK_DGRAM && protocol == IPPROTO_UDP)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
* Called from {@link QosSocketFilter#matchesLocalAddress(InetAddress, int, int)}
* and {@link QosSocketFilter#matchesRemoteAddress(InetAddress, int, int)} with the
* filterSocketAddress coming from {@link QosSocketInfo#getLocalSocketAddress()}.
@@ -174,6 +230,7 @@
final int startPort, final int endPort) {
return startPort <= filterSocketAddress.getPort()
&& endPort >= filterSocketAddress.getPort()
- && filterSocketAddress.getAddress().equals(address);
+ && (address.isAnyLocalAddress()
+ || filterSocketAddress.getAddress().equals(address));
}
}
diff --git a/framework/src/android/net/QosSocketInfo.java b/framework/src/android/net/QosSocketInfo.java
index a45d507..49ac22b 100644
--- a/framework/src/android/net/QosSocketInfo.java
+++ b/framework/src/android/net/QosSocketInfo.java
@@ -16,6 +16,9 @@
package android.net;
+import static android.system.OsConstants.SOCK_DGRAM;
+import static android.system.OsConstants.SOCK_STREAM;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
@@ -24,6 +27,7 @@
import android.os.Parcelable;
import java.io.IOException;
+import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
@@ -53,6 +57,8 @@
@Nullable
private final InetSocketAddress mRemoteSocketAddress;
+ private final int mSocketType;
+
/**
* The {@link Network} the socket is on.
*
@@ -98,6 +104,16 @@
}
/**
+ * The socket type of the socket passed in when this QosSocketInfo object was constructed.
+ *
+ * @return the socket type of the socket.
+ * @hide
+ */
+ public int getSocketType() {
+ return mSocketType;
+ }
+
+ /**
* Creates a {@link QosSocketInfo} given a {@link Network} and bound {@link Socket}. The
* {@link Socket} must remain bound in order to receive {@link QosSession}s.
*
@@ -112,6 +128,32 @@
mParcelFileDescriptor = ParcelFileDescriptor.fromSocket(socket);
mLocalSocketAddress =
new InetSocketAddress(socket.getLocalAddress(), socket.getLocalPort());
+ mSocketType = SOCK_STREAM;
+
+ if (socket.isConnected()) {
+ mRemoteSocketAddress = (InetSocketAddress) socket.getRemoteSocketAddress();
+ } else {
+ mRemoteSocketAddress = null;
+ }
+ }
+
+ /**
+ * Creates a {@link QosSocketInfo} given a {@link Network} and bound {@link DatagramSocket}. The
+ * {@link DatagramSocket} must remain bound in order to receive {@link QosSession}s.
+ *
+ * @param network the network
+ * @param socket the bound {@link DatagramSocket}
+ * @hide
+ */
+ public QosSocketInfo(@NonNull final Network network, @NonNull final DatagramSocket socket)
+ throws IOException {
+ Objects.requireNonNull(socket, "socket cannot be null");
+
+ mNetwork = Objects.requireNonNull(network, "network cannot be null");
+ mParcelFileDescriptor = ParcelFileDescriptor.fromDatagramSocket(socket);
+ mLocalSocketAddress =
+ new InetSocketAddress(socket.getLocalAddress(), socket.getLocalPort());
+ mSocketType = SOCK_DGRAM;
if (socket.isConnected()) {
mRemoteSocketAddress = (InetSocketAddress) socket.getRemoteSocketAddress();
@@ -123,23 +165,28 @@
/* Parcelable methods */
private QosSocketInfo(final Parcel in) {
mNetwork = Objects.requireNonNull(Network.CREATOR.createFromParcel(in));
- mParcelFileDescriptor = ParcelFileDescriptor.CREATOR.createFromParcel(in);
+ final boolean withFd = in.readBoolean();
+ if (withFd) {
+ mParcelFileDescriptor = ParcelFileDescriptor.CREATOR.createFromParcel(in);
+ } else {
+ mParcelFileDescriptor = null;
+ }
- final int localAddressLength = in.readInt();
- mLocalSocketAddress = readSocketAddress(in, localAddressLength);
+ mLocalSocketAddress = readSocketAddress(in);
+ mRemoteSocketAddress = readSocketAddress(in);
- final int remoteAddressLength = in.readInt();
- mRemoteSocketAddress = remoteAddressLength == 0 ? null
- : readSocketAddress(in, remoteAddressLength);
+ mSocketType = in.readInt();
}
- private @NonNull InetSocketAddress readSocketAddress(final Parcel in, final int addressLength) {
- final byte[] address = new byte[addressLength];
- in.readByteArray(address);
+ private InetSocketAddress readSocketAddress(final Parcel in) {
+ final byte[] addrBytes = in.createByteArray();
+ if (addrBytes == null) {
+ return null;
+ }
final int port = in.readInt();
try {
- return new InetSocketAddress(InetAddress.getByAddress(address), port);
+ return new InetSocketAddress(InetAddress.getByAddress(addrBytes), port);
} catch (final UnknownHostException e) {
/* This can never happen. UnknownHostException will never be thrown
since the address provided is numeric and non-null. */
@@ -154,22 +201,38 @@
@Override
public void writeToParcel(@NonNull final Parcel dest, final int flags) {
- mNetwork.writeToParcel(dest, 0);
- mParcelFileDescriptor.writeToParcel(dest, 0);
+ writeToParcelInternal(dest, flags, /*includeFd=*/ true);
+ }
- final byte[] localAddress = mLocalSocketAddress.getAddress().getAddress();
- dest.writeInt(localAddress.length);
- dest.writeByteArray(localAddress);
+ /**
+ * Used when sending QosSocketInfo to telephony, which does not need access to the socket FD.
+ * @hide
+ */
+ public void writeToParcelWithoutFd(@NonNull final Parcel dest, final int flags) {
+ writeToParcelInternal(dest, flags, /*includeFd=*/ false);
+ }
+
+ private void writeToParcelInternal(
+ @NonNull final Parcel dest, final int flags, boolean includeFd) {
+ mNetwork.writeToParcel(dest, 0);
+
+ if (includeFd) {
+ dest.writeBoolean(true);
+ mParcelFileDescriptor.writeToParcel(dest, 0);
+ } else {
+ dest.writeBoolean(false);
+ }
+
+ dest.writeByteArray(mLocalSocketAddress.getAddress().getAddress());
dest.writeInt(mLocalSocketAddress.getPort());
if (mRemoteSocketAddress == null) {
- dest.writeInt(0);
+ dest.writeByteArray(null);
} else {
- final byte[] remoteAddress = mRemoteSocketAddress.getAddress().getAddress();
- dest.writeInt(remoteAddress.length);
- dest.writeByteArray(remoteAddress);
+ dest.writeByteArray(mRemoteSocketAddress.getAddress().getAddress());
dest.writeInt(mRemoteSocketAddress.getPort());
}
+ dest.writeInt(mSocketType);
}
@NonNull
diff --git a/framework/src/android/net/SocketNotConnectedException.java b/framework/src/android/net/SocketNotConnectedException.java
new file mode 100644
index 0000000..fa2a615
--- /dev/null
+++ b/framework/src/android/net/SocketNotConnectedException.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+/**
+ * Thrown when a previously bound socket becomes unbound.
+ *
+ * @hide
+ */
+public class SocketNotConnectedException extends Exception {
+ /** @hide */
+ public SocketNotConnectedException() {
+ super("The socket is not connected");
+ }
+}
diff --git a/framework/src/android/net/SocketRemoteAddressChangedException.java b/framework/src/android/net/SocketRemoteAddressChangedException.java
new file mode 100644
index 0000000..ecaeebc
--- /dev/null
+++ b/framework/src/android/net/SocketRemoteAddressChangedException.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+/**
+ * Thrown when the local address of the socket has changed.
+ *
+ * @hide
+ */
+public class SocketRemoteAddressChangedException extends Exception {
+ /** @hide */
+ public SocketRemoteAddressChangedException() {
+ super("The remote address of the socket changed");
+ }
+}
diff --git a/framework/src/android/net/TestNetworkManager.java b/framework/src/android/net/TestNetworkManager.java
index 280e497..4e78823 100644
--- a/framework/src/android/net/TestNetworkManager.java
+++ b/framework/src/android/net/TestNetworkManager.java
@@ -45,6 +45,12 @@
*/
public static final String TEST_TAP_PREFIX = "testtap";
+ /**
+ * Prefix for clat interfaces.
+ * @hide
+ */
+ public static final String CLAT_INTERFACE_PREFIX = "v4-";
+
@NonNull private static final String TAG = TestNetworkManager.class.getSimpleName();
@NonNull private final ITestNetworkManager mService;
@@ -160,7 +166,8 @@
public TestNetworkInterface createTunInterface(@NonNull Collection<LinkAddress> linkAddrs) {
try {
final LinkAddress[] arr = new LinkAddress[linkAddrs.size()];
- return mService.createInterface(TUN, BRING_UP, linkAddrs.toArray(arr));
+ return mService.createInterface(TUN, BRING_UP, linkAddrs.toArray(arr),
+ null /* iface */);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -178,7 +185,7 @@
@NonNull
public TestNetworkInterface createTapInterface() {
try {
- return mService.createInterface(TAP, BRING_UP, NO_ADDRS);
+ return mService.createInterface(TAP, BRING_UP, NO_ADDRS, null /* iface */);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -197,7 +204,29 @@
@NonNull
public TestNetworkInterface createTapInterface(boolean bringUp) {
try {
- return mService.createInterface(TAP, bringUp, NO_ADDRS);
+ return mService.createInterface(TAP, bringUp, NO_ADDRS, null /* iface */);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Create a tap interface with a given interface name for testing purposes
+ *
+ * @param bringUp whether to bring up the interface before returning it.
+ * @param iface interface name to be assigned, so far only interface name which starts with
+ * "v4-testtap" or "v4-testtun" is allowed to be created. If it's null, then use
+ * the default name(e.g. testtap or testtun).
+ *
+ * @return A ParcelFileDescriptor of the underlying TAP interface. Close this to tear down the
+ * TAP interface.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_TEST_NETWORKS)
+ @NonNull
+ public TestNetworkInterface createTapInterface(boolean bringUp, @NonNull String iface) {
+ try {
+ return mService.createInterface(TAP, bringUp, NO_ADDRS, iface);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/netd/Android.bp b/netd/Android.bp
index 5ac02d3..c731b8b 100644
--- a/netd/Android.bp
+++ b/netd/Android.bp
@@ -55,7 +55,8 @@
cc_test {
name: "netd_updatable_unit_test",
defaults: ["netd_defaults"],
- test_suites: ["general-tests"],
+ test_suites: ["general-tests", "mts-tethering"],
+ test_config_template: ":net_native_test_config_template",
require_root: true, // required by setrlimitForTest()
header_libs: [
"bpf_connectivity_headers",
@@ -72,6 +73,7 @@
"liblog",
"libnetdutils",
],
+ compile_multilib: "both",
multilib: {
lib32: {
suffix: "32",
diff --git a/netd/BpfHandler.cpp b/netd/BpfHandler.cpp
index f3dfb57..42d0de5 100644
--- a/netd/BpfHandler.cpp
+++ b/netd/BpfHandler.cpp
@@ -73,16 +73,7 @@
}
RETURN_IF_NOT_OK(attachProgramToCgroup(BPF_EGRESS_PROG_PATH, cg_fd, BPF_CGROUP_INET_EGRESS));
RETURN_IF_NOT_OK(attachProgramToCgroup(BPF_INGRESS_PROG_PATH, cg_fd, BPF_CGROUP_INET_INGRESS));
-
- // For the devices that support cgroup socket filter, the socket filter
- // should be loaded successfully by bpfloader. So we attach the filter to
- // cgroup if the program is pinned properly.
- // TODO: delete the if statement once all devices should support cgroup
- // socket filter (ie. the minimum kernel version required is 4.14).
- if (!access(CGROUP_SOCKET_PROG_PATH, F_OK)) {
- RETURN_IF_NOT_OK(
- attachProgramToCgroup(CGROUP_SOCKET_PROG_PATH, cg_fd, BPF_CGROUP_INET_SOCK_CREATE));
- }
+ RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_SOCKET_PROG_PATH, cg_fd, BPF_CGROUP_INET_SOCK_CREATE));
return netdutils::status::ok;
}
@@ -113,6 +104,7 @@
RETURN_IF_NOT_OK(mConfigurationMap.writeValue(CURRENT_STATS_MAP_CONFIGURATION_KEY, SELECT_MAP_A,
BPF_ANY));
RETURN_IF_NOT_OK(mUidPermissionMap.init(UID_PERMISSION_MAP_PATH));
+ ALOGI("%s successfully", __func__);
return netdutils::status::ok;
}
@@ -242,7 +234,7 @@
if (sock_cookie == NONEXISTENT_COOKIE) return -errno;
base::Result<void> res = mCookieTagMap.deleteValue(sock_cookie);
if (!res.ok()) {
- ALOGE("Failed to untag socket: %s\n", strerror(res.error().code()));
+ ALOGE("Failed to untag socket: %s", strerror(res.error().code()));
return -res.error().code();
}
return 0;
diff --git a/netd/BpfHandler.h b/netd/BpfHandler.h
index 2ede1c1..05b9ebc 100644
--- a/netd/BpfHandler.h
+++ b/netd/BpfHandler.h
@@ -62,7 +62,7 @@
BpfMap<uint64_t, UidTagValue> mCookieTagMap;
BpfMap<StatsKey, StatsValue> mStatsMapA;
BpfMap<StatsKey, StatsValue> mStatsMapB;
- BpfMap<uint32_t, uint8_t> mConfigurationMap;
+ BpfMap<uint32_t, uint32_t> mConfigurationMap;
BpfMap<uint32_t, uint8_t> mUidPermissionMap;
std::mutex mMutex;
diff --git a/netd/BpfHandlerTest.cpp b/netd/BpfHandlerTest.cpp
index cd6b565..1bd222d 100644
--- a/netd/BpfHandlerTest.cpp
+++ b/netd/BpfHandlerTest.cpp
@@ -21,6 +21,7 @@
#include <gtest/gtest.h>
+#define TEST_BPF_MAP
#include "BpfHandler.h"
using namespace android::bpf; // NOLINT(google-build-using-namespace): exempted
@@ -48,46 +49,38 @@
BpfHandler mBh;
BpfMap<uint64_t, UidTagValue> mFakeCookieTagMap;
BpfMap<StatsKey, StatsValue> mFakeStatsMapA;
- BpfMap<uint32_t, uint8_t> mFakeConfigurationMap;
+ BpfMap<uint32_t, uint32_t> mFakeConfigurationMap;
BpfMap<uint32_t, uint8_t> mFakeUidPermissionMap;
void SetUp() {
std::lock_guard guard(mBh.mMutex);
ASSERT_EQ(0, setrlimitForTest());
- mFakeCookieTagMap.reset(createMap(BPF_MAP_TYPE_HASH, sizeof(uint64_t), sizeof(UidTagValue),
- TEST_MAP_SIZE, 0));
+ mFakeCookieTagMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE);
ASSERT_VALID(mFakeCookieTagMap);
- mFakeStatsMapA.reset(createMap(BPF_MAP_TYPE_HASH, sizeof(StatsKey), sizeof(StatsValue),
- TEST_MAP_SIZE, 0));
+ mFakeStatsMapA.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE);
ASSERT_VALID(mFakeStatsMapA);
- mFakeConfigurationMap.reset(
- createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(uint8_t), 1, 0));
+ mFakeConfigurationMap.resetMap(BPF_MAP_TYPE_HASH, 1);
ASSERT_VALID(mFakeConfigurationMap);
- mFakeUidPermissionMap.reset(
- createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(uint8_t), TEST_MAP_SIZE, 0));
+ mFakeUidPermissionMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, 0);
ASSERT_VALID(mFakeUidPermissionMap);
- mBh.mCookieTagMap.reset(dupFd(mFakeCookieTagMap.getMap()));
+ mBh.mCookieTagMap = mFakeCookieTagMap;
ASSERT_VALID(mBh.mCookieTagMap);
- mBh.mStatsMapA.reset(dupFd(mFakeStatsMapA.getMap()));
+ mBh.mStatsMapA = mFakeStatsMapA;
ASSERT_VALID(mBh.mStatsMapA);
- mBh.mConfigurationMap.reset(dupFd(mFakeConfigurationMap.getMap()));
+ mBh.mConfigurationMap = mFakeConfigurationMap;
ASSERT_VALID(mBh.mConfigurationMap);
// Always write to stats map A by default.
ASSERT_RESULT_OK(mBh.mConfigurationMap.writeValue(CURRENT_STATS_MAP_CONFIGURATION_KEY,
SELECT_MAP_A, BPF_ANY));
- mBh.mUidPermissionMap.reset(dupFd(mFakeUidPermissionMap.getMap()));
+ mBh.mUidPermissionMap = mFakeUidPermissionMap;
ASSERT_VALID(mBh.mUidPermissionMap);
}
- int dupFd(const android::base::unique_fd& mapFd) {
- return fcntl(mapFd.get(), F_DUPFD_CLOEXEC, 0);
- }
-
int setUpSocketAndTag(int protocol, uint64_t* cookie, uint32_t tag, uid_t uid,
uid_t realUid) {
int sock = socket(protocol, SOCK_STREAM | SOCK_CLOEXEC, 0);
diff --git a/service-t/Android.bp b/service-t/Android.bp
index 8851554..1b9f2ec 100644
--- a/service-t/Android.bp
+++ b/service-t/Android.bp
@@ -19,6 +19,20 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
+// Include build rules from Sources.bp
+build = ["Sources.bp"]
+
+filegroup {
+ name: "service-connectivity-tiramisu-sources",
+ srcs: [
+ "src/**/*.java",
+ ],
+ visibility: ["//visibility:private"],
+}
+// The above filegroup can be used to specify different sources depending
+// on the branch, while minimizing merge conflicts in the rest of the
+// build rules.
+
// This builds T+ services depending on framework-connectivity-t
// hidden symbols separately from the S+ services, to ensure that S+
// services cannot accidentally depend on T+ hidden symbols from
@@ -29,9 +43,7 @@
// TODO(b/210962470): Bump this to at least S, and then T.
min_sdk_version: "30",
srcs: [
- "src/**/*.java",
- ":ethernet-service-updatable-sources",
- ":services.connectivity-tiramisu-updatable-sources",
+ ":service-connectivity-tiramisu-sources",
],
libs: [
"framework-annotations-lib",
diff --git a/service-t/Sources.bp b/service-t/Sources.bp
new file mode 100644
index 0000000..187eadf
--- /dev/null
+++ b/service-t/Sources.bp
@@ -0,0 +1,40 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+// For test code only.
+filegroup {
+ name: "lib_networkStatsFactory_native",
+ srcs: [
+ "jni/com_android_server_net_NetworkStatsFactory.cpp",
+ ],
+ path: "jni",
+ visibility: [
+ "//packages/modules/Connectivity:__subpackages__",
+ ],
+}
+
+filegroup {
+ name: "services.connectivity-netstats-jni-sources",
+ srcs: [
+ "jni/com_android_server_net_NetworkStatsFactory.cpp",
+ "jni/com_android_server_net_NetworkStatsService.cpp",
+ ],
+ path: "jni",
+ visibility: [
+ "//packages/modules/Connectivity:__subpackages__",
+ ],
+}
+
diff --git a/service-t/jni/com_android_server_net_NetworkStatsFactory.cpp b/service-t/jni/com_android_server_net_NetworkStatsFactory.cpp
new file mode 100644
index 0000000..8b6526f
--- /dev/null
+++ b/service-t/jni/com_android_server_net_NetworkStatsFactory.cpp
@@ -0,0 +1,362 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "NetworkStats"
+
+#include <errno.h>
+#include <inttypes.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <vector>
+
+#include <jni.h>
+
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedUtfChars.h>
+#include <nativehelper/ScopedLocalRef.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
+
+#include <utils/Log.h>
+#include <utils/misc.h>
+
+#include "android-base/unique_fd.h"
+#include "bpf/BpfUtils.h"
+#include "netdbpf/BpfNetworkStats.h"
+
+using android::bpf::parseBpfNetworkStatsDetail;
+using android::bpf::stats_line;
+
+namespace android {
+
+static jclass gStringClass;
+
+static struct {
+ jfieldID size;
+ jfieldID capacity;
+ jfieldID iface;
+ jfieldID uid;
+ jfieldID set;
+ jfieldID tag;
+ jfieldID metered;
+ jfieldID roaming;
+ jfieldID defaultNetwork;
+ jfieldID rxBytes;
+ jfieldID rxPackets;
+ jfieldID txBytes;
+ jfieldID txPackets;
+ jfieldID operations;
+} gNetworkStatsClassInfo;
+
+static jobjectArray get_string_array(JNIEnv* env, jobject obj, jfieldID field, int size, bool grow)
+{
+ if (!grow) {
+ jobjectArray array = (jobjectArray)env->GetObjectField(obj, field);
+ if (array != NULL) {
+ return array;
+ }
+ }
+ return env->NewObjectArray(size, gStringClass, NULL);
+}
+
+static jintArray get_int_array(JNIEnv* env, jobject obj, jfieldID field, int size, bool grow)
+{
+ if (!grow) {
+ jintArray array = (jintArray)env->GetObjectField(obj, field);
+ if (array != NULL) {
+ return array;
+ }
+ }
+ return env->NewIntArray(size);
+}
+
+static jlongArray get_long_array(JNIEnv* env, jobject obj, jfieldID field, int size, bool grow)
+{
+ if (!grow) {
+ jlongArray array = (jlongArray)env->GetObjectField(obj, field);
+ if (array != NULL) {
+ return array;
+ }
+ }
+ return env->NewLongArray(size);
+}
+
+static int legacyReadNetworkStatsDetail(std::vector<stats_line>* lines,
+ const std::vector<std::string>& limitIfaces,
+ int limitTag, int limitUid, const char* path) {
+ FILE* fp = fopen(path, "re");
+ if (fp == NULL) {
+ return -1;
+ }
+
+ int lastIdx = 1;
+ int idx;
+ char buffer[384];
+ while (fgets(buffer, sizeof(buffer), fp) != NULL) {
+ stats_line s;
+ int64_t rawTag;
+ char* pos = buffer;
+ char* endPos;
+ // First field is the index.
+ idx = (int)strtol(pos, &endPos, 10);
+ //ALOGI("Index #%d: %s", idx, buffer);
+ if (pos == endPos) {
+ // Skip lines that don't start with in index. In particular,
+ // this will skip the initial header line.
+ continue;
+ }
+ if (idx != lastIdx + 1) {
+ ALOGE("inconsistent idx=%d after lastIdx=%d: %s", idx, lastIdx, buffer);
+ fclose(fp);
+ return -1;
+ }
+ lastIdx = idx;
+ pos = endPos;
+ // Skip whitespace.
+ while (*pos == ' ') {
+ pos++;
+ }
+ // Next field is iface.
+ int ifaceIdx = 0;
+ while (*pos != ' ' && *pos != 0 && ifaceIdx < (int)(sizeof(s.iface)-1)) {
+ s.iface[ifaceIdx] = *pos;
+ ifaceIdx++;
+ pos++;
+ }
+ if (*pos != ' ') {
+ ALOGE("bad iface: %s", buffer);
+ fclose(fp);
+ return -1;
+ }
+ s.iface[ifaceIdx] = 0;
+ if (limitIfaces.size() > 0) {
+ // Is this an iface the caller is interested in?
+ int i = 0;
+ while (i < (int)limitIfaces.size()) {
+ if (limitIfaces[i] == s.iface) {
+ break;
+ }
+ i++;
+ }
+ if (i >= (int)limitIfaces.size()) {
+ // Nothing matched; skip this line.
+ //ALOGI("skipping due to iface: %s", buffer);
+ continue;
+ }
+ }
+
+ // Ignore whitespace
+ while (*pos == ' ') pos++;
+
+ // Find end of tag field
+ endPos = pos;
+ while (*endPos != ' ') endPos++;
+
+ // Three digit field is always 0x0, otherwise parse
+ if (endPos - pos == 3) {
+ rawTag = 0;
+ } else {
+ if (sscanf(pos, "%" PRIx64, &rawTag) != 1) {
+ ALOGE("bad tag: %s", pos);
+ fclose(fp);
+ return -1;
+ }
+ }
+ s.tag = rawTag >> 32;
+ if (limitTag != -1 && s.tag != static_cast<uint32_t>(limitTag)) {
+ //ALOGI("skipping due to tag: %s", buffer);
+ continue;
+ }
+ pos = endPos;
+
+ // Ignore whitespace
+ while (*pos == ' ') pos++;
+
+ // Parse remaining fields.
+ if (sscanf(pos, "%u %u %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64,
+ &s.uid, &s.set, &s.rxBytes, &s.rxPackets,
+ &s.txBytes, &s.txPackets) == 6) {
+ if (limitUid != -1 && static_cast<uint32_t>(limitUid) != s.uid) {
+ //ALOGI("skipping due to uid: %s", buffer);
+ continue;
+ }
+ lines->push_back(s);
+ } else {
+ //ALOGI("skipping due to bad remaining fields: %s", pos);
+ }
+ }
+
+ if (fclose(fp) != 0) {
+ ALOGE("Failed to close netstats file");
+ return -1;
+ }
+ return 0;
+}
+
+static int statsLinesToNetworkStats(JNIEnv* env, jclass clazz, jobject stats,
+ std::vector<stats_line>& lines) {
+ int size = lines.size();
+
+ bool grow = size > env->GetIntField(stats, gNetworkStatsClassInfo.capacity);
+
+ ScopedLocalRef<jobjectArray> iface(env, get_string_array(env, stats,
+ gNetworkStatsClassInfo.iface, size, grow));
+ if (iface.get() == NULL) return -1;
+ ScopedIntArrayRW uid(env, get_int_array(env, stats,
+ gNetworkStatsClassInfo.uid, size, grow));
+ if (uid.get() == NULL) return -1;
+ ScopedIntArrayRW set(env, get_int_array(env, stats,
+ gNetworkStatsClassInfo.set, size, grow));
+ if (set.get() == NULL) return -1;
+ ScopedIntArrayRW tag(env, get_int_array(env, stats,
+ gNetworkStatsClassInfo.tag, size, grow));
+ if (tag.get() == NULL) return -1;
+ ScopedIntArrayRW metered(env, get_int_array(env, stats,
+ gNetworkStatsClassInfo.metered, size, grow));
+ if (metered.get() == NULL) return -1;
+ ScopedIntArrayRW roaming(env, get_int_array(env, stats,
+ gNetworkStatsClassInfo.roaming, size, grow));
+ if (roaming.get() == NULL) return -1;
+ ScopedIntArrayRW defaultNetwork(env, get_int_array(env, stats,
+ gNetworkStatsClassInfo.defaultNetwork, size, grow));
+ if (defaultNetwork.get() == NULL) return -1;
+ ScopedLongArrayRW rxBytes(env, get_long_array(env, stats,
+ gNetworkStatsClassInfo.rxBytes, size, grow));
+ if (rxBytes.get() == NULL) return -1;
+ ScopedLongArrayRW rxPackets(env, get_long_array(env, stats,
+ gNetworkStatsClassInfo.rxPackets, size, grow));
+ if (rxPackets.get() == NULL) return -1;
+ ScopedLongArrayRW txBytes(env, get_long_array(env, stats,
+ gNetworkStatsClassInfo.txBytes, size, grow));
+ if (txBytes.get() == NULL) return -1;
+ ScopedLongArrayRW txPackets(env, get_long_array(env, stats,
+ gNetworkStatsClassInfo.txPackets, size, grow));
+ if (txPackets.get() == NULL) return -1;
+ ScopedLongArrayRW operations(env, get_long_array(env, stats,
+ gNetworkStatsClassInfo.operations, size, grow));
+ if (operations.get() == NULL) return -1;
+
+ for (int i = 0; i < size; i++) {
+ ScopedLocalRef<jstring> ifaceString(env, env->NewStringUTF(lines[i].iface));
+ env->SetObjectArrayElement(iface.get(), i, ifaceString.get());
+
+ uid[i] = lines[i].uid;
+ set[i] = lines[i].set;
+ tag[i] = lines[i].tag;
+ // Metered, roaming and defaultNetwork are populated in Java-land.
+ rxBytes[i] = lines[i].rxBytes;
+ rxPackets[i] = lines[i].rxPackets;
+ txBytes[i] = lines[i].txBytes;
+ txPackets[i] = lines[i].txPackets;
+ }
+
+ env->SetIntField(stats, gNetworkStatsClassInfo.size, size);
+ if (grow) {
+ env->SetIntField(stats, gNetworkStatsClassInfo.capacity, size);
+ env->SetObjectField(stats, gNetworkStatsClassInfo.iface, iface.get());
+ env->SetObjectField(stats, gNetworkStatsClassInfo.uid, uid.getJavaArray());
+ env->SetObjectField(stats, gNetworkStatsClassInfo.set, set.getJavaArray());
+ env->SetObjectField(stats, gNetworkStatsClassInfo.tag, tag.getJavaArray());
+ env->SetObjectField(stats, gNetworkStatsClassInfo.metered, metered.getJavaArray());
+ env->SetObjectField(stats, gNetworkStatsClassInfo.roaming, roaming.getJavaArray());
+ env->SetObjectField(stats, gNetworkStatsClassInfo.defaultNetwork,
+ defaultNetwork.getJavaArray());
+ env->SetObjectField(stats, gNetworkStatsClassInfo.rxBytes, rxBytes.getJavaArray());
+ env->SetObjectField(stats, gNetworkStatsClassInfo.rxPackets, rxPackets.getJavaArray());
+ env->SetObjectField(stats, gNetworkStatsClassInfo.txBytes, txBytes.getJavaArray());
+ env->SetObjectField(stats, gNetworkStatsClassInfo.txPackets, txPackets.getJavaArray());
+ env->SetObjectField(stats, gNetworkStatsClassInfo.operations, operations.getJavaArray());
+ }
+ return 0;
+}
+
+static int readNetworkStatsDetail(JNIEnv* env, jclass clazz, jobject stats, jstring path,
+ jint limitUid, jobjectArray limitIfacesObj, jint limitTag,
+ jboolean useBpfStats) {
+
+ std::vector<std::string> limitIfaces;
+ if (limitIfacesObj != NULL && env->GetArrayLength(limitIfacesObj) > 0) {
+ int num = env->GetArrayLength(limitIfacesObj);
+ for (int i = 0; i < num; i++) {
+ jstring string = (jstring)env->GetObjectArrayElement(limitIfacesObj, i);
+ ScopedUtfChars string8(env, string);
+ if (string8.c_str() != NULL) {
+ limitIfaces.push_back(std::string(string8.c_str()));
+ }
+ }
+ }
+ std::vector<stats_line> lines;
+
+
+ if (useBpfStats) {
+ if (parseBpfNetworkStatsDetail(&lines, limitIfaces, limitTag, limitUid) < 0)
+ return -1;
+ } else {
+ ScopedUtfChars path8(env, path);
+ if (path8.c_str() == NULL) {
+ ALOGE("the qtaguid legacy path is invalid: %s", path8.c_str());
+ return -1;
+ }
+ if (legacyReadNetworkStatsDetail(&lines, limitIfaces, limitTag,
+ limitUid, path8.c_str()) < 0)
+ return -1;
+ }
+
+ return statsLinesToNetworkStats(env, clazz, stats, lines);
+}
+
+static int readNetworkStatsDev(JNIEnv* env, jclass clazz, jobject stats) {
+ std::vector<stats_line> lines;
+
+ if (parseBpfNetworkStatsDev(&lines) < 0)
+ return -1;
+
+ return statsLinesToNetworkStats(env, clazz, stats, lines);
+}
+
+static const JNINativeMethod gMethods[] = {
+ { "nativeReadNetworkStatsDetail",
+ "(Landroid/net/NetworkStats;Ljava/lang/String;I[Ljava/lang/String;IZ)I",
+ (void*) readNetworkStatsDetail },
+ { "nativeReadNetworkStatsDev", "(Landroid/net/NetworkStats;)I",
+ (void*) readNetworkStatsDev },
+};
+
+int register_android_server_net_NetworkStatsFactory(JNIEnv* env) {
+ int err = jniRegisterNativeMethods(env, "com/android/server/net/NetworkStatsFactory", gMethods,
+ NELEM(gMethods));
+ gStringClass = env->FindClass("java/lang/String");
+ gStringClass = static_cast<jclass>(env->NewGlobalRef(gStringClass));
+
+ jclass clazz = env->FindClass("android/net/NetworkStats");
+ gNetworkStatsClassInfo.size = env->GetFieldID(clazz, "size", "I");
+ gNetworkStatsClassInfo.capacity = env->GetFieldID(clazz, "capacity", "I");
+ gNetworkStatsClassInfo.iface = env->GetFieldID(clazz, "iface", "[Ljava/lang/String;");
+ gNetworkStatsClassInfo.uid = env->GetFieldID(clazz, "uid", "[I");
+ gNetworkStatsClassInfo.set = env->GetFieldID(clazz, "set", "[I");
+ gNetworkStatsClassInfo.tag = env->GetFieldID(clazz, "tag", "[I");
+ gNetworkStatsClassInfo.metered = env->GetFieldID(clazz, "metered", "[I");
+ gNetworkStatsClassInfo.roaming = env->GetFieldID(clazz, "roaming", "[I");
+ gNetworkStatsClassInfo.defaultNetwork = env->GetFieldID(clazz, "defaultNetwork", "[I");
+ gNetworkStatsClassInfo.rxBytes = env->GetFieldID(clazz, "rxBytes", "[J");
+ gNetworkStatsClassInfo.rxPackets = env->GetFieldID(clazz, "rxPackets", "[J");
+ gNetworkStatsClassInfo.txBytes = env->GetFieldID(clazz, "txBytes", "[J");
+ gNetworkStatsClassInfo.txPackets = env->GetFieldID(clazz, "txPackets", "[J");
+ gNetworkStatsClassInfo.operations = env->GetFieldID(clazz, "operations", "[J");
+
+ return err;
+}
+
+}
diff --git a/service-t/jni/com_android_server_net_NetworkStatsService.cpp b/service-t/jni/com_android_server_net_NetworkStatsService.cpp
new file mode 100644
index 0000000..39cbaf7
--- /dev/null
+++ b/service-t/jni/com_android_server_net_NetworkStatsService.cpp
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "NetworkStatsNative"
+
+#include <cutils/qtaguid.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <jni.h>
+#include <nativehelper/ScopedUtfChars.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <utils/Log.h>
+#include <utils/misc.h>
+
+#include "bpf/BpfUtils.h"
+#include "netdbpf/BpfNetworkStats.h"
+
+using android::bpf::bpfGetUidStats;
+using android::bpf::bpfGetIfaceStats;
+
+namespace android {
+
+// NOTE: keep these in sync with TrafficStats.java
+static const uint64_t UNKNOWN = -1;
+
+enum StatsType {
+ RX_BYTES = 0,
+ RX_PACKETS = 1,
+ TX_BYTES = 2,
+ TX_PACKETS = 3,
+ TCP_RX_PACKETS = 4,
+ TCP_TX_PACKETS = 5
+};
+
+static uint64_t getStatsType(Stats* stats, StatsType type) {
+ switch (type) {
+ case RX_BYTES:
+ return stats->rxBytes;
+ case RX_PACKETS:
+ return stats->rxPackets;
+ case TX_BYTES:
+ return stats->txBytes;
+ case TX_PACKETS:
+ return stats->txPackets;
+ case TCP_RX_PACKETS:
+ return stats->tcpRxPackets;
+ case TCP_TX_PACKETS:
+ return stats->tcpTxPackets;
+ default:
+ return UNKNOWN;
+ }
+}
+
+static jlong getTotalStat(JNIEnv* env, jclass clazz, jint type) {
+ Stats stats = {};
+
+ if (bpfGetIfaceStats(NULL, &stats) == 0) {
+ return getStatsType(&stats, (StatsType) type);
+ } else {
+ return UNKNOWN;
+ }
+}
+
+static jlong getIfaceStat(JNIEnv* env, jclass clazz, jstring iface, jint type) {
+ ScopedUtfChars iface8(env, iface);
+ if (iface8.c_str() == NULL) {
+ return UNKNOWN;
+ }
+
+ Stats stats = {};
+
+ if (bpfGetIfaceStats(iface8.c_str(), &stats) == 0) {
+ return getStatsType(&stats, (StatsType) type);
+ } else {
+ return UNKNOWN;
+ }
+}
+
+static jlong getUidStat(JNIEnv* env, jclass clazz, jint uid, jint type) {
+ Stats stats = {};
+
+ if (bpfGetUidStats(uid, &stats) == 0) {
+ return getStatsType(&stats, (StatsType) type);
+ } else {
+ return UNKNOWN;
+ }
+}
+
+static const JNINativeMethod gMethods[] = {
+ {"nativeGetTotalStat", "(I)J", (void*)getTotalStat},
+ {"nativeGetIfaceStat", "(Ljava/lang/String;I)J", (void*)getIfaceStat},
+ {"nativeGetUidStat", "(II)J", (void*)getUidStat},
+};
+
+int register_android_server_net_NetworkStatsService(JNIEnv* env) {
+ return jniRegisterNativeMethods(env, "com/android/server/net/NetworkStatsService", gMethods,
+ NELEM(gMethods));
+}
+
+}
diff --git a/service-t/native/libs/libnetworkstats/Android.bp b/service-t/native/libs/libnetworkstats/Android.bp
index bf56fd5..5b3d314 100644
--- a/service-t/native/libs/libnetworkstats/Android.bp
+++ b/service-t/native/libs/libnetworkstats/Android.bp
@@ -48,7 +48,8 @@
cc_test {
name: "libnetworkstats_test",
- test_suites: ["general-tests"],
+ test_suites: ["general-tests", "mts-tethering"],
+ test_config_template: ":net_native_test_config_template",
require_root: true, // required by setrlimitForTest()
header_libs: ["bpf_connectivity_headers"],
srcs: [
@@ -68,4 +69,13 @@
"libbase",
"liblog",
],
+ compile_multilib: "both",
+ multilib: {
+ lib32: {
+ suffix: "32",
+ },
+ lib64: {
+ suffix: "64",
+ },
+ },
}
diff --git a/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp b/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
index 4d605ce..c67821f 100644
--- a/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
+++ b/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
@@ -193,7 +193,7 @@
return ret;
}
- BpfMapRO<uint32_t, uint8_t> configurationMap(CONFIGURATION_MAP_PATH);
+ BpfMapRO<uint32_t, uint32_t> configurationMap(CONFIGURATION_MAP_PATH);
if (!configurationMap.isValid()) {
int ret = -errno;
ALOGE("get configuration map fd failed: %s", strerror(errno));
@@ -205,6 +205,10 @@
configuration.error().message().c_str());
return -configuration.error().code();
}
+ if (configuration.value() != SELECT_MAP_A && configuration.value() != SELECT_MAP_B) {
+ ALOGE("%s unknown configuration value: %d", __func__, configuration.value());
+ return -EINVAL;
+ }
const char* statsMapPath = STATS_MAP_PATH[configuration.value()];
BpfMap<StatsKey, StatsValue> statsMap(statsMapPath);
if (!statsMap.isValid()) {
diff --git a/service-t/src/com/android/server/ConnectivityServiceInitializer.java b/service-t/src/com/android/server/ConnectivityServiceInitializer.java
index fa86f39..626c2eb 100644
--- a/service-t/src/com/android/server/ConnectivityServiceInitializer.java
+++ b/service-t/src/com/android/server/ConnectivityServiceInitializer.java
@@ -21,6 +21,7 @@
import com.android.modules.utils.build.SdkLevel;
import com.android.networkstack.apishim.ConstantsShim;
+import com.android.server.connectivity.ConnectivityNativeService;
import com.android.server.ethernet.EthernetService;
import com.android.server.ethernet.EthernetServiceImpl;
import com.android.server.nearby.NearbyService;
@@ -31,6 +32,7 @@
*/
public final class ConnectivityServiceInitializer extends SystemService {
private static final String TAG = ConnectivityServiceInitializer.class.getSimpleName();
+ private final ConnectivityNativeService mConnectivityNative;
private final ConnectivityService mConnectivity;
private final IpSecService mIpSecService;
private final NsdService mNsdService;
@@ -44,6 +46,7 @@
mEthernetServiceImpl = createEthernetService(context);
mConnectivity = new ConnectivityService(context);
mIpSecService = createIpSecService(context);
+ mConnectivityNative = createConnectivityNativeService(context);
mNsdService = createNsdService(context);
mNearbyService = createNearbyService(context);
}
@@ -65,6 +68,12 @@
publishBinderService(Context.IPSEC_SERVICE, mIpSecService, /* allowIsolated= */ false);
}
+ if (mConnectivityNative != null) {
+ Log.i(TAG, "Registering " + ConnectivityNativeService.SERVICE_NAME);
+ publishBinderService(ConnectivityNativeService.SERVICE_NAME, mConnectivityNative,
+ /* allowIsolated= */ false);
+ }
+
if (mNsdService != null) {
Log.i(TAG, "Registering " + Context.NSD_SERVICE);
publishBinderService(Context.NSD_SERVICE, mNsdService, /* allowIsolated= */ false);
@@ -98,15 +107,24 @@
return new IpSecService(context);
}
+ /**
+ * Return ConnectivityNativeService instance, or null if current SDK is lower than T.
+ */
+ private ConnectivityNativeService createConnectivityNativeService(final Context context) {
+ if (!SdkLevel.isAtLeastT()) return null;
+ try {
+ return new ConnectivityNativeService(context);
+ } catch (UnsupportedOperationException e) {
+ Log.d(TAG, "Unable to get ConnectivityNative service", e);
+ return null;
+ }
+ }
+
/** Return NsdService instance or null if current SDK is lower than T */
private NsdService createNsdService(final Context context) {
if (!SdkLevel.isAtLeastT()) return null;
- try {
- return NsdService.create(context);
- } catch (InterruptedException e) {
- Log.d(TAG, "Unable to get NSD service", e);
- return null;
- }
+
+ return NsdService.create(context);
}
/** Return Nearby service instance or null if current SDK is lower than T */
diff --git a/service-t/src/com/android/server/IpSecService.java b/service-t/src/com/android/server/IpSecService.java
new file mode 100644
index 0000000..16b9f1e
--- /dev/null
+++ b/service-t/src/com/android/server/IpSecService.java
@@ -0,0 +1,1883 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static android.Manifest.permission.DUMP;
+import static android.net.IpSecManager.INVALID_RESOURCE_ID;
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+import static android.system.OsConstants.AF_UNSPEC;
+import static android.system.OsConstants.EINVAL;
+import static android.system.OsConstants.IPPROTO_UDP;
+import static android.system.OsConstants.SOCK_DGRAM;
+
+import android.annotation.NonNull;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.IIpSecService;
+import android.net.INetd;
+import android.net.InetAddresses;
+import android.net.IpSecAlgorithm;
+import android.net.IpSecConfig;
+import android.net.IpSecManager;
+import android.net.IpSecSpiResponse;
+import android.net.IpSecTransform;
+import android.net.IpSecTransformResponse;
+import android.net.IpSecTunnelInterfaceResponse;
+import android.net.IpSecUdpEncapResponse;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.TrafficStats;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Range;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+import com.android.net.module.util.BinderUtils;
+import com.android.net.module.util.NetdUtils;
+import com.android.net.module.util.PermissionUtils;
+
+import libcore.io.IoUtils;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A service to manage multiple clients that want to access the IpSec API. The service is
+ * responsible for maintaining a list of clients and managing the resources (and related quotas)
+ * that each of them own.
+ *
+ * <p>Synchronization in IpSecService is done on all entrypoints due to potential race conditions at
+ * the kernel/xfrm level. Further, this allows the simplifying assumption to be made that only one
+ * thread is ever running at a time.
+ *
+ * @hide
+ */
+public class IpSecService extends IIpSecService.Stub {
+ private static final String TAG = "IpSecService";
+ private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final int[] ADDRESS_FAMILIES =
+ new int[] {OsConstants.AF_INET, OsConstants.AF_INET6};
+
+ private static final int NETD_FETCH_TIMEOUT_MS = 5000; // ms
+ private static final InetAddress INADDR_ANY;
+
+ @VisibleForTesting static final int MAX_PORT_BIND_ATTEMPTS = 10;
+
+ private final INetd mNetd;
+
+ static {
+ try {
+ INADDR_ANY = InetAddress.getByAddress(new byte[] {0, 0, 0, 0});
+ } catch (UnknownHostException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ static final int FREE_PORT_MIN = 1024; // ports 1-1023 are reserved
+ static final int PORT_MAX = 0xFFFF; // ports are an unsigned 16-bit integer
+
+ /* Binder context for this service */
+ private final Context mContext;
+ private final Dependencies mDeps;
+
+ /**
+ * The next non-repeating global ID for tracking resources between users, this service, and
+ * kernel data structures. Accessing this variable is not thread safe, so it is only read or
+ * modified within blocks synchronized on IpSecService.this. We want to avoid -1
+ * (INVALID_RESOURCE_ID) and 0 (we probably forgot to initialize it).
+ */
+ @GuardedBy("IpSecService.this")
+ private int mNextResourceId = 1;
+
+ /**
+ * Dependencies of IpSecService, for injection in tests.
+ */
+ @VisibleForTesting
+ public static class Dependencies {
+ /**
+ * Get a reference to INetd.
+ */
+ public INetd getNetdInstance(Context context) throws RemoteException {
+ final INetd netd = INetd.Stub.asInterface((IBinder)
+ context.getSystemService(Context.NETD_SERVICE));
+ if (netd == null) {
+ throw new RemoteException("Failed to Get Netd Instance");
+ }
+ return netd;
+ }
+ }
+
+ final UidFdTagger mUidFdTagger;
+
+ /**
+ * Interface for user-reference and kernel-resource cleanup.
+ *
+ * <p>This interface must be implemented for a resource to be reference counted.
+ */
+ @VisibleForTesting
+ public interface IResource {
+ /**
+ * Invalidates a IResource object, ensuring it is invalid for the purposes of allocating new
+ * objects dependent on it.
+ *
+ * <p>Implementations of this method are expected to remove references to the IResource
+ * object from the IpSecService's tracking arrays. The removal from the arrays ensures that
+ * the resource is considered invalid for user access or allocation or use in other
+ * resources.
+ *
+ * <p>References to the IResource object may be held by other RefcountedResource objects,
+ * and as such, the underlying resources and quota may not be cleaned up.
+ */
+ void invalidate() throws RemoteException;
+
+ /**
+ * Releases underlying resources and related quotas.
+ *
+ * <p>Implementations of this method are expected to remove all system resources that are
+ * tracked by the IResource object. Due to other RefcountedResource objects potentially
+ * having references to the IResource object, freeUnderlyingResources may not always be
+ * called from releaseIfUnreferencedRecursively().
+ */
+ void freeUnderlyingResources() throws RemoteException;
+ }
+
+ /**
+ * RefcountedResource manages references and dependencies in an exclusively acyclic graph.
+ *
+ * <p>RefcountedResource implements both explicit and implicit resource management. Creating a
+ * RefcountedResource object creates an explicit reference that must be freed by calling
+ * userRelease(). Additionally, adding this object as a child of another RefcountedResource
+ * object will add an implicit reference.
+ *
+ * <p>Resources are cleaned up when all references, both implicit and explicit, are released
+ * (ie, when userRelease() is called and when all parents have called releaseReference() on this
+ * object.)
+ */
+ @VisibleForTesting
+ public class RefcountedResource<T extends IResource> implements IBinder.DeathRecipient {
+ private final T mResource;
+ private final List<RefcountedResource> mChildren;
+ int mRefCount = 1; // starts at 1 for user's reference.
+ IBinder mBinder;
+
+ RefcountedResource(T resource, IBinder binder, RefcountedResource... children) {
+ synchronized (IpSecService.this) {
+ this.mResource = resource;
+ this.mChildren = new ArrayList<>(children.length);
+ this.mBinder = binder;
+
+ for (RefcountedResource child : children) {
+ mChildren.add(child);
+ child.mRefCount++;
+ }
+
+ try {
+ mBinder.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ binderDied();
+ e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * If the Binder object dies, this function is called to free the system resources that are
+ * being tracked by this record and to subsequently release this record for garbage
+ * collection
+ */
+ @Override
+ public void binderDied() {
+ synchronized (IpSecService.this) {
+ try {
+ userRelease();
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to release resource: " + e);
+ }
+ }
+ }
+
+ public T getResource() {
+ return mResource;
+ }
+
+ /**
+ * Unlinks from binder and performs IpSecService resource cleanup (removes from resource
+ * arrays)
+ *
+ * <p>If this method has been previously called, the RefcountedResource's binder field will
+ * be null, and the method will return without performing the cleanup a second time.
+ *
+ * <p>Note that calling this function does not imply that kernel resources will be freed at
+ * this time, or that the related quota will be returned. Such actions will only be
+ * performed upon the reference count reaching zero.
+ */
+ @GuardedBy("IpSecService.this")
+ public void userRelease() throws RemoteException {
+ // Prevent users from putting reference counts into a bad state by calling
+ // userRelease() multiple times.
+ if (mBinder == null) {
+ return;
+ }
+
+ mBinder.unlinkToDeath(this, 0);
+ mBinder = null;
+
+ mResource.invalidate();
+
+ releaseReference();
+ }
+
+ /**
+ * Removes a reference to this resource. If the resultant reference count is zero, the
+ * underlying resources are freed, and references to all child resources are also dropped
+ * recursively (resulting in them freeing their resources and children, etcetera)
+ *
+ * <p>This method also sets the reference count to an invalid value (-1) to signify that it
+ * has been fully released. Any subsequent calls to this method will result in an
+ * IllegalStateException being thrown due to resource already having been previously
+ * released
+ */
+ @VisibleForTesting
+ @GuardedBy("IpSecService.this")
+ public void releaseReference() throws RemoteException {
+ mRefCount--;
+
+ if (mRefCount > 0) {
+ return;
+ } else if (mRefCount < 0) {
+ throw new IllegalStateException(
+ "Invalid operation - resource has already been released.");
+ }
+
+ // Cleanup own resources
+ mResource.freeUnderlyingResources();
+
+ // Cleanup child resources as needed
+ for (RefcountedResource<? extends IResource> child : mChildren) {
+ child.releaseReference();
+ }
+
+ // Enforce that resource cleanup can only be called once
+ // By decrementing the refcount (from 0 to -1), the next call will throw an
+ // IllegalStateException - it has already been released fully.
+ mRefCount--;
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder()
+ .append("{mResource=")
+ .append(mResource)
+ .append(", mRefCount=")
+ .append(mRefCount)
+ .append(", mChildren=")
+ .append(mChildren)
+ .append("}")
+ .toString();
+ }
+ }
+
+ /**
+ * Very simple counting class that looks much like a counting semaphore
+ *
+ * <p>This class is not thread-safe, and expects that that users of this class will ensure
+ * synchronization and thread safety by holding the IpSecService.this instance lock.
+ */
+ @VisibleForTesting
+ static class ResourceTracker {
+ private final int mMax;
+ int mCurrent;
+
+ ResourceTracker(int max) {
+ mMax = max;
+ mCurrent = 0;
+ }
+
+ boolean isAvailable() {
+ return (mCurrent < mMax);
+ }
+
+ void take() {
+ if (!isAvailable()) {
+ Log.wtf(TAG, "Too many resources allocated!");
+ }
+ mCurrent++;
+ }
+
+ void give() {
+ if (mCurrent <= 0) {
+ Log.wtf(TAG, "We've released this resource too many times");
+ }
+ mCurrent--;
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder()
+ .append("{mCurrent=")
+ .append(mCurrent)
+ .append(", mMax=")
+ .append(mMax)
+ .append("}")
+ .toString();
+ }
+ }
+
+ @VisibleForTesting
+ static final class UserRecord {
+ /* Maximum number of each type of resource that a single UID may possess */
+
+ // Up to 4 active VPNs/IWLAN with potential soft handover.
+ public static final int MAX_NUM_TUNNEL_INTERFACES = 8;
+ public static final int MAX_NUM_ENCAP_SOCKETS = 16;
+
+ // SPIs and Transforms are both cheap, and are 1:1 correlated.
+ public static final int MAX_NUM_TRANSFORMS = 64;
+ public static final int MAX_NUM_SPIS = 64;
+
+ /**
+ * Store each of the OwnedResource types in an (thinly wrapped) sparse array for indexing
+ * and explicit (user) reference management.
+ *
+ * <p>These are stored in separate arrays to improve debuggability and dump output clarity.
+ *
+ * <p>Resources are removed from this array when the user releases their explicit reference
+ * by calling one of the releaseResource() methods.
+ */
+ final RefcountedResourceArray<SpiRecord> mSpiRecords =
+ new RefcountedResourceArray<>(SpiRecord.class.getSimpleName());
+ final RefcountedResourceArray<TransformRecord> mTransformRecords =
+ new RefcountedResourceArray<>(TransformRecord.class.getSimpleName());
+ final RefcountedResourceArray<EncapSocketRecord> mEncapSocketRecords =
+ new RefcountedResourceArray<>(EncapSocketRecord.class.getSimpleName());
+ final RefcountedResourceArray<TunnelInterfaceRecord> mTunnelInterfaceRecords =
+ new RefcountedResourceArray<>(TunnelInterfaceRecord.class.getSimpleName());
+
+ /**
+ * Trackers for quotas for each of the OwnedResource types.
+ *
+ * <p>These trackers are separate from the resource arrays, since they are incremented and
+ * decremented at different points in time. Specifically, quota is only returned upon final
+ * resource deallocation (after all explicit and implicit references are released). Note
+ * that it is possible that calls to releaseResource() will not return the used quota if
+ * there are other resources that depend on (are parents of) the resource being released.
+ */
+ final ResourceTracker mSpiQuotaTracker = new ResourceTracker(MAX_NUM_SPIS);
+ final ResourceTracker mTransformQuotaTracker = new ResourceTracker(MAX_NUM_TRANSFORMS);
+ final ResourceTracker mSocketQuotaTracker = new ResourceTracker(MAX_NUM_ENCAP_SOCKETS);
+ final ResourceTracker mTunnelQuotaTracker = new ResourceTracker(MAX_NUM_TUNNEL_INTERFACES);
+
+ void removeSpiRecord(int resourceId) {
+ mSpiRecords.remove(resourceId);
+ }
+
+ void removeTransformRecord(int resourceId) {
+ mTransformRecords.remove(resourceId);
+ }
+
+ void removeTunnelInterfaceRecord(int resourceId) {
+ mTunnelInterfaceRecords.remove(resourceId);
+ }
+
+ void removeEncapSocketRecord(int resourceId) {
+ mEncapSocketRecords.remove(resourceId);
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder()
+ .append("{mSpiQuotaTracker=")
+ .append(mSpiQuotaTracker)
+ .append(", mTransformQuotaTracker=")
+ .append(mTransformQuotaTracker)
+ .append(", mSocketQuotaTracker=")
+ .append(mSocketQuotaTracker)
+ .append(", mTunnelQuotaTracker=")
+ .append(mTunnelQuotaTracker)
+ .append(", mSpiRecords=")
+ .append(mSpiRecords)
+ .append(", mTransformRecords=")
+ .append(mTransformRecords)
+ .append(", mEncapSocketRecords=")
+ .append(mEncapSocketRecords)
+ .append(", mTunnelInterfaceRecords=")
+ .append(mTunnelInterfaceRecords)
+ .append("}")
+ .toString();
+ }
+ }
+
+ /**
+ * This class is not thread-safe, and expects that that users of this class will ensure
+ * synchronization and thread safety by holding the IpSecService.this instance lock.
+ */
+ @VisibleForTesting
+ static final class UserResourceTracker {
+ private final SparseArray<UserRecord> mUserRecords = new SparseArray<>();
+
+ /** Lazy-initialization/getter that populates or retrieves the UserRecord as needed */
+ public UserRecord getUserRecord(int uid) {
+ checkCallerUid(uid);
+
+ UserRecord r = mUserRecords.get(uid);
+ if (r == null) {
+ r = new UserRecord();
+ mUserRecords.put(uid, r);
+ }
+ return r;
+ }
+
+ /** Safety method; guards against access of other user's UserRecords */
+ private void checkCallerUid(int uid) {
+ if (uid != Binder.getCallingUid() && Process.SYSTEM_UID != Binder.getCallingUid()) {
+ throw new SecurityException("Attempted access of unowned resources");
+ }
+ }
+
+ @Override
+ public String toString() {
+ return mUserRecords.toString();
+ }
+ }
+
+ @VisibleForTesting final UserResourceTracker mUserResourceTracker = new UserResourceTracker();
+
+ /**
+ * The OwnedResourceRecord class provides a facility to cleanly and reliably track system
+ * resources. It relies on a provided resourceId that should uniquely identify the kernel
+ * resource. To use this class, the user should implement the invalidate() and
+ * freeUnderlyingResources() methods that are responsible for cleaning up IpSecService resource
+ * tracking arrays and kernel resources, respectively.
+ *
+ * <p>This class associates kernel resources with the UID that owns and controls them.
+ */
+ private abstract class OwnedResourceRecord implements IResource {
+ final int mPid;
+ final int mUid;
+ protected final int mResourceId;
+
+ OwnedResourceRecord(int resourceId) {
+ super();
+ if (resourceId == INVALID_RESOURCE_ID) {
+ throw new IllegalArgumentException("Resource ID must not be INVALID_RESOURCE_ID");
+ }
+ mResourceId = resourceId;
+ mPid = Binder.getCallingPid();
+ mUid = Binder.getCallingUid();
+
+ getResourceTracker().take();
+ }
+
+ @Override
+ public abstract void invalidate() throws RemoteException;
+
+ /** Convenience method; retrieves the user resource record for the stored UID. */
+ protected UserRecord getUserRecord() {
+ return mUserResourceTracker.getUserRecord(mUid);
+ }
+
+ @Override
+ public abstract void freeUnderlyingResources() throws RemoteException;
+
+ /** Get the resource tracker for this resource */
+ protected abstract ResourceTracker getResourceTracker();
+
+ @Override
+ public String toString() {
+ return new StringBuilder()
+ .append("{mResourceId=")
+ .append(mResourceId)
+ .append(", pid=")
+ .append(mPid)
+ .append(", uid=")
+ .append(mUid)
+ .append("}")
+ .toString();
+ }
+ };
+
+ /**
+ * Thin wrapper over SparseArray to ensure resources exist, and simplify generic typing.
+ *
+ * <p>RefcountedResourceArray prevents null insertions, and throws an IllegalArgumentException
+ * if a key is not found during a retrieval process.
+ */
+ static class RefcountedResourceArray<T extends IResource> {
+ SparseArray<RefcountedResource<T>> mArray = new SparseArray<>();
+ private final String mTypeName;
+
+ RefcountedResourceArray(String typeName) {
+ this.mTypeName = typeName;
+ }
+
+ /**
+ * Accessor method to get inner resource object.
+ *
+ * @throws IllegalArgumentException if no resource with provided key is found.
+ */
+ T getResourceOrThrow(int key) {
+ return getRefcountedResourceOrThrow(key).getResource();
+ }
+
+ /**
+ * Accessor method to get reference counting wrapper.
+ *
+ * @throws IllegalArgumentException if no resource with provided key is found.
+ */
+ RefcountedResource<T> getRefcountedResourceOrThrow(int key) {
+ RefcountedResource<T> resource = mArray.get(key);
+ if (resource == null) {
+ throw new IllegalArgumentException(
+ String.format("No such %s found for given id: %d", mTypeName, key));
+ }
+
+ return resource;
+ }
+
+ void put(int key, RefcountedResource<T> obj) {
+ Objects.requireNonNull(obj, "Null resources cannot be added");
+ mArray.put(key, obj);
+ }
+
+ void remove(int key) {
+ mArray.remove(key);
+ }
+
+ @Override
+ public String toString() {
+ return mArray.toString();
+ }
+ }
+
+ /**
+ * Tracks an SA in the kernel, and manages cleanup paths. Once a TransformRecord is
+ * created, the SpiRecord that originally tracked the SAs will reliquish the
+ * responsibility of freeing the underlying SA to this class via the mOwnedByTransform flag.
+ */
+ private final class TransformRecord extends OwnedResourceRecord {
+ private final IpSecConfig mConfig;
+ private final SpiRecord mSpi;
+ private final EncapSocketRecord mSocket;
+
+ TransformRecord(
+ int resourceId, IpSecConfig config, SpiRecord spi, EncapSocketRecord socket) {
+ super(resourceId);
+ mConfig = config;
+ mSpi = spi;
+ mSocket = socket;
+
+ spi.setOwnedByTransform();
+ }
+
+ public IpSecConfig getConfig() {
+ return mConfig;
+ }
+
+ public SpiRecord getSpiRecord() {
+ return mSpi;
+ }
+
+ public EncapSocketRecord getSocketRecord() {
+ return mSocket;
+ }
+
+ /** always guarded by IpSecService#this */
+ @Override
+ public void freeUnderlyingResources() {
+ int spi = mSpi.getSpi();
+ try {
+ mNetd.ipSecDeleteSecurityAssociation(
+ mUid,
+ mConfig.getSourceAddress(),
+ mConfig.getDestinationAddress(),
+ spi,
+ mConfig.getMarkValue(),
+ mConfig.getMarkMask(),
+ mConfig.getXfrmInterfaceId());
+ } catch (RemoteException | ServiceSpecificException e) {
+ Log.e(TAG, "Failed to delete SA with ID: " + mResourceId, e);
+ }
+
+ getResourceTracker().give();
+ }
+
+ @Override
+ public void invalidate() throws RemoteException {
+ getUserRecord().removeTransformRecord(mResourceId);
+ }
+
+ @Override
+ protected ResourceTracker getResourceTracker() {
+ return getUserRecord().mTransformQuotaTracker;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder strBuilder = new StringBuilder();
+ strBuilder
+ .append("{super=")
+ .append(super.toString())
+ .append(", mSocket=")
+ .append(mSocket)
+ .append(", mSpi.mResourceId=")
+ .append(mSpi.mResourceId)
+ .append(", mConfig=")
+ .append(mConfig)
+ .append("}");
+ return strBuilder.toString();
+ }
+ }
+
+ /**
+ * Tracks a single SA in the kernel, and manages cleanup paths. Once used in a Transform, the
+ * responsibility for cleaning up underlying resources will be passed to the TransformRecord
+ * object
+ */
+ private final class SpiRecord extends OwnedResourceRecord {
+ private final String mSourceAddress;
+ private final String mDestinationAddress;
+ private int mSpi;
+
+ private boolean mOwnedByTransform = false;
+
+ SpiRecord(int resourceId, String sourceAddress,
+ String destinationAddress, int spi) {
+ super(resourceId);
+ mSourceAddress = sourceAddress;
+ mDestinationAddress = destinationAddress;
+ mSpi = spi;
+ }
+
+ /** always guarded by IpSecService#this */
+ @Override
+ public void freeUnderlyingResources() {
+ try {
+ if (!mOwnedByTransform) {
+ mNetd.ipSecDeleteSecurityAssociation(
+ mUid, mSourceAddress, mDestinationAddress, mSpi, 0 /* mark */,
+ 0 /* mask */, 0 /* if_id */);
+ }
+ } catch (ServiceSpecificException | RemoteException e) {
+ Log.e(TAG, "Failed to delete SPI reservation with ID: " + mResourceId, e);
+ }
+
+ mSpi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
+
+ getResourceTracker().give();
+ }
+
+ public int getSpi() {
+ return mSpi;
+ }
+
+ public String getDestinationAddress() {
+ return mDestinationAddress;
+ }
+
+ public void setOwnedByTransform() {
+ if (mOwnedByTransform) {
+ // Programming error
+ throw new IllegalStateException("Cannot own an SPI twice!");
+ }
+
+ mOwnedByTransform = true;
+ }
+
+ public boolean getOwnedByTransform() {
+ return mOwnedByTransform;
+ }
+
+ @Override
+ public void invalidate() throws RemoteException {
+ getUserRecord().removeSpiRecord(mResourceId);
+ }
+
+ @Override
+ protected ResourceTracker getResourceTracker() {
+ return getUserRecord().mSpiQuotaTracker;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder strBuilder = new StringBuilder();
+ strBuilder
+ .append("{super=")
+ .append(super.toString())
+ .append(", mSpi=")
+ .append(mSpi)
+ .append(", mSourceAddress=")
+ .append(mSourceAddress)
+ .append(", mDestinationAddress=")
+ .append(mDestinationAddress)
+ .append(", mOwnedByTransform=")
+ .append(mOwnedByTransform)
+ .append("}");
+ return strBuilder.toString();
+ }
+ }
+
+ private final SparseBooleanArray mTunnelNetIds = new SparseBooleanArray();
+ final Range<Integer> mNetIdRange = ConnectivityManager.getIpSecNetIdRange();
+ private int mNextTunnelNetId = mNetIdRange.getLower();
+
+ /**
+ * Reserves a netId within the range of netIds allocated for IPsec tunnel interfaces
+ *
+ * <p>This method should only be called from Binder threads. Do not call this from within the
+ * system server as it will crash the system on failure.
+ *
+ * @return an integer key within the netId range, if successful
+ * @throws IllegalStateException if unsuccessful (all netId are currently reserved)
+ */
+ @VisibleForTesting
+ int reserveNetId() {
+ final int range = mNetIdRange.getUpper() - mNetIdRange.getLower() + 1;
+ synchronized (mTunnelNetIds) {
+ for (int i = 0; i < range; i++) {
+ final int netId = mNextTunnelNetId;
+ if (++mNextTunnelNetId > mNetIdRange.getUpper()) {
+ mNextTunnelNetId = mNetIdRange.getLower();
+ }
+ if (!mTunnelNetIds.get(netId)) {
+ mTunnelNetIds.put(netId, true);
+ return netId;
+ }
+ }
+ }
+ throw new IllegalStateException("No free netIds to allocate");
+ }
+
+ @VisibleForTesting
+ void releaseNetId(int netId) {
+ synchronized (mTunnelNetIds) {
+ mTunnelNetIds.delete(netId);
+ }
+ }
+
+ /**
+ * Tracks an tunnel interface, and manages cleanup paths.
+ *
+ * <p>This class is not thread-safe, and expects that that users of this class will ensure
+ * synchronization and thread safety by holding the IpSecService.this instance lock
+ */
+ @VisibleForTesting
+ final class TunnelInterfaceRecord extends OwnedResourceRecord {
+ private final String mInterfaceName;
+
+ // outer addresses
+ private final String mLocalAddress;
+ private final String mRemoteAddress;
+
+ private final int mIkey;
+ private final int mOkey;
+
+ private final int mIfId;
+
+ private Network mUnderlyingNetwork;
+
+ TunnelInterfaceRecord(
+ int resourceId,
+ String interfaceName,
+ Network underlyingNetwork,
+ String localAddr,
+ String remoteAddr,
+ int ikey,
+ int okey,
+ int intfId) {
+ super(resourceId);
+
+ mInterfaceName = interfaceName;
+ mUnderlyingNetwork = underlyingNetwork;
+ mLocalAddress = localAddr;
+ mRemoteAddress = remoteAddr;
+ mIkey = ikey;
+ mOkey = okey;
+ mIfId = intfId;
+ }
+
+ /** always guarded by IpSecService#this */
+ @Override
+ public void freeUnderlyingResources() {
+ // Calls to netd
+ // Teardown VTI
+ // Delete global policies
+ try {
+ mNetd.ipSecRemoveTunnelInterface(mInterfaceName);
+
+ for (int selAddrFamily : ADDRESS_FAMILIES) {
+ mNetd.ipSecDeleteSecurityPolicy(
+ mUid,
+ selAddrFamily,
+ IpSecManager.DIRECTION_OUT,
+ mOkey,
+ 0xffffffff,
+ mIfId);
+ mNetd.ipSecDeleteSecurityPolicy(
+ mUid,
+ selAddrFamily,
+ IpSecManager.DIRECTION_IN,
+ mIkey,
+ 0xffffffff,
+ mIfId);
+ }
+ } catch (ServiceSpecificException | RemoteException e) {
+ Log.e(
+ TAG,
+ "Failed to delete VTI with interface name: "
+ + mInterfaceName
+ + " and id: "
+ + mResourceId, e);
+ }
+
+ getResourceTracker().give();
+ releaseNetId(mIkey);
+ releaseNetId(mOkey);
+ }
+
+ @GuardedBy("IpSecService.this")
+ public void setUnderlyingNetwork(Network underlyingNetwork) {
+ // When #applyTunnelModeTransform is called, this new underlying network will be used to
+ // update the output mark of the input transform.
+ mUnderlyingNetwork = underlyingNetwork;
+ }
+
+ @GuardedBy("IpSecService.this")
+ public Network getUnderlyingNetwork() {
+ return mUnderlyingNetwork;
+ }
+
+ public String getInterfaceName() {
+ return mInterfaceName;
+ }
+
+ /** Returns the local, outer address for the tunnelInterface */
+ public String getLocalAddress() {
+ return mLocalAddress;
+ }
+
+ /** Returns the remote, outer address for the tunnelInterface */
+ public String getRemoteAddress() {
+ return mRemoteAddress;
+ }
+
+ public int getIkey() {
+ return mIkey;
+ }
+
+ public int getOkey() {
+ return mOkey;
+ }
+
+ public int getIfId() {
+ return mIfId;
+ }
+
+ @Override
+ protected ResourceTracker getResourceTracker() {
+ return getUserRecord().mTunnelQuotaTracker;
+ }
+
+ @Override
+ public void invalidate() {
+ getUserRecord().removeTunnelInterfaceRecord(mResourceId);
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder()
+ .append("{super=")
+ .append(super.toString())
+ .append(", mInterfaceName=")
+ .append(mInterfaceName)
+ .append(", mUnderlyingNetwork=")
+ .append(mUnderlyingNetwork)
+ .append(", mLocalAddress=")
+ .append(mLocalAddress)
+ .append(", mRemoteAddress=")
+ .append(mRemoteAddress)
+ .append(", mIkey=")
+ .append(mIkey)
+ .append(", mOkey=")
+ .append(mOkey)
+ .append("}")
+ .toString();
+ }
+ }
+
+ /**
+ * Tracks a UDP encap socket, and manages cleanup paths
+ *
+ * <p>While this class does not manage non-kernel resources, race conditions around socket
+ * binding require that the service creates the encap socket, binds it and applies the socket
+ * policy before handing it to a user.
+ */
+ private final class EncapSocketRecord extends OwnedResourceRecord {
+ private FileDescriptor mSocket;
+ private final int mPort;
+
+ EncapSocketRecord(int resourceId, FileDescriptor socket, int port) {
+ super(resourceId);
+ mSocket = socket;
+ mPort = port;
+ }
+
+ /** always guarded by IpSecService#this */
+ @Override
+ public void freeUnderlyingResources() {
+ Log.d(TAG, "Closing port " + mPort);
+ IoUtils.closeQuietly(mSocket);
+ mSocket = null;
+
+ getResourceTracker().give();
+ }
+
+ public int getPort() {
+ return mPort;
+ }
+
+ public FileDescriptor getFileDescriptor() {
+ return mSocket;
+ }
+
+ @Override
+ protected ResourceTracker getResourceTracker() {
+ return getUserRecord().mSocketQuotaTracker;
+ }
+
+ @Override
+ public void invalidate() {
+ getUserRecord().removeEncapSocketRecord(mResourceId);
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder()
+ .append("{super=")
+ .append(super.toString())
+ .append(", mSocket=")
+ .append(mSocket)
+ .append(", mPort=")
+ .append(mPort)
+ .append("}")
+ .toString();
+ }
+ }
+
+ /**
+ * Constructs a new IpSecService instance
+ *
+ * @param context Binder context for this service
+ */
+ public IpSecService(Context context) {
+ this(context, new Dependencies());
+ }
+
+ @NonNull
+ private AppOpsManager getAppOpsManager() {
+ AppOpsManager appOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
+ if (appOps == null) throw new RuntimeException("System Server couldn't get AppOps");
+ return appOps;
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public IpSecService(Context context, Dependencies deps) {
+ this(
+ context,
+ deps,
+ (fd, uid) -> {
+ try {
+ TrafficStats.setThreadStatsUid(uid);
+ TrafficStats.tagFileDescriptor(fd);
+ } finally {
+ TrafficStats.clearThreadStatsUid();
+ }
+ });
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public IpSecService(Context context, Dependencies deps, UidFdTagger uidFdTagger) {
+ mContext = context;
+ mDeps = Objects.requireNonNull(deps, "Missing dependencies.");
+ mUidFdTagger = uidFdTagger;
+ try {
+ mNetd = mDeps.getNetdInstance(mContext);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Checks that the provided InetAddress is valid for use in an IPsec SA. The address must not be
+ * a wildcard address and must be in a numeric form such as 1.2.3.4 or 2001::1.
+ */
+ private static void checkInetAddress(String inetAddress) {
+ if (TextUtils.isEmpty(inetAddress)) {
+ throw new IllegalArgumentException("Unspecified address");
+ }
+
+ InetAddress checkAddr = InetAddresses.parseNumericAddress(inetAddress);
+
+ if (checkAddr.isAnyLocalAddress()) {
+ throw new IllegalArgumentException("Inappropriate wildcard address: " + inetAddress);
+ }
+ }
+
+ /**
+ * Checks the user-provided direction field and throws an IllegalArgumentException if it is not
+ * DIRECTION_IN or DIRECTION_OUT
+ */
+ private void checkDirection(int direction) {
+ switch (direction) {
+ case IpSecManager.DIRECTION_OUT:
+ case IpSecManager.DIRECTION_IN:
+ return;
+ case IpSecManager.DIRECTION_FWD:
+ // Only NETWORK_STACK or MAINLINE_NETWORK_STACK allowed to use forward policies
+ PermissionUtils.enforceNetworkStackPermission(mContext);
+ return;
+ }
+ throw new IllegalArgumentException("Invalid Direction: " + direction);
+ }
+
+ /** Get a new SPI and maintain the reservation in the system server */
+ @Override
+ public synchronized IpSecSpiResponse allocateSecurityParameterIndex(
+ String destinationAddress, int requestedSpi, IBinder binder) throws RemoteException {
+ checkInetAddress(destinationAddress);
+ // RFC 4303 Section 2.1 - 0=local, 1-255=reserved.
+ if (requestedSpi > 0 && requestedSpi < 256) {
+ throw new IllegalArgumentException("ESP SPI must not be in the range of 0-255.");
+ }
+ Objects.requireNonNull(binder, "Null Binder passed to allocateSecurityParameterIndex");
+
+ int callingUid = Binder.getCallingUid();
+ UserRecord userRecord = mUserResourceTracker.getUserRecord(callingUid);
+ final int resourceId = mNextResourceId++;
+
+ int spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
+ try {
+ if (!userRecord.mSpiQuotaTracker.isAvailable()) {
+ return new IpSecSpiResponse(
+ IpSecManager.Status.RESOURCE_UNAVAILABLE, INVALID_RESOURCE_ID, spi);
+ }
+
+ spi = mNetd.ipSecAllocateSpi(callingUid, "", destinationAddress, requestedSpi);
+ Log.d(TAG, "Allocated SPI " + spi);
+ userRecord.mSpiRecords.put(
+ resourceId,
+ new RefcountedResource<SpiRecord>(
+ new SpiRecord(resourceId, "",
+ destinationAddress, spi), binder));
+ } catch (ServiceSpecificException e) {
+ if (e.errorCode == OsConstants.ENOENT) {
+ return new IpSecSpiResponse(
+ IpSecManager.Status.SPI_UNAVAILABLE, INVALID_RESOURCE_ID, spi);
+ }
+ throw e;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ return new IpSecSpiResponse(IpSecManager.Status.OK, resourceId, spi);
+ }
+
+ /* This method should only be called from Binder threads. Do not call this from
+ * within the system server as it will crash the system on failure.
+ */
+ private void releaseResource(RefcountedResourceArray resArray, int resourceId)
+ throws RemoteException {
+ resArray.getRefcountedResourceOrThrow(resourceId).userRelease();
+ }
+
+ /** Release a previously allocated SPI that has been registered with the system server */
+ @Override
+ public synchronized void releaseSecurityParameterIndex(int resourceId) throws RemoteException {
+ UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
+ releaseResource(userRecord.mSpiRecords, resourceId);
+ }
+
+ /**
+ * This function finds and forcibly binds to a random system port, ensuring that the port cannot
+ * be unbound.
+ *
+ * <p>A socket cannot be un-bound from a port if it was bound to that port by number. To select
+ * a random open port and then bind by number, this function creates a temp socket, binds to a
+ * random port (specifying 0), gets that port number, and then uses is to bind the user's UDP
+ * Encapsulation Socket forcibly, so that it cannot be un-bound by the user with the returned
+ * FileHandle.
+ *
+ * <p>The loop in this function handles the inherent race window between un-binding to a port
+ * and re-binding, during which the system could *technically* hand that port out to someone
+ * else.
+ */
+ private int bindToRandomPort(FileDescriptor sockFd) throws IOException {
+ for (int i = MAX_PORT_BIND_ATTEMPTS; i > 0; i--) {
+ try {
+ FileDescriptor probeSocket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ Os.bind(probeSocket, INADDR_ANY, 0);
+ int port = ((InetSocketAddress) Os.getsockname(probeSocket)).getPort();
+ Os.close(probeSocket);
+ Log.v(TAG, "Binding to port " + port);
+ Os.bind(sockFd, INADDR_ANY, port);
+ return port;
+ } catch (ErrnoException e) {
+ // Someone miraculously claimed the port just after we closed probeSocket.
+ if (e.errno == OsConstants.EADDRINUSE) {
+ continue;
+ }
+ throw e.rethrowAsIOException();
+ }
+ }
+ throw new IOException("Failed " + MAX_PORT_BIND_ATTEMPTS + " attempts to bind to a port");
+ }
+
+ /**
+ * Functional interface to do traffic tagging of given sockets to UIDs.
+ *
+ * <p>Specifically used by openUdpEncapsulationSocket to ensure data usage on the UDP encap
+ * sockets are billed to the UID that the UDP encap socket was created on behalf of.
+ *
+ * <p>Separate class so that the socket tagging logic can be mocked; TrafficStats uses static
+ * methods that cannot be easily mocked/tested.
+ */
+ @VisibleForTesting
+ public interface UidFdTagger {
+ /**
+ * Sets socket tag to assign all traffic to the provided UID.
+ *
+ * <p>Since the socket is created on behalf of an unprivileged application, all traffic
+ * should be accounted to the UID of the unprivileged application.
+ */
+ void tag(FileDescriptor fd, int uid) throws IOException;
+ }
+
+ /**
+ * Open a socket via the system server and bind it to the specified port (random if port=0).
+ * This will return a PFD to the user that represent a bound UDP socket. The system server will
+ * cache the socket and a record of its owner so that it can and must be freed when no longer
+ * needed.
+ */
+ @Override
+ public synchronized IpSecUdpEncapResponse openUdpEncapsulationSocket(int port, IBinder binder)
+ throws RemoteException {
+ if (port != 0 && (port < FREE_PORT_MIN || port > PORT_MAX)) {
+ throw new IllegalArgumentException(
+ "Specified port number must be a valid non-reserved UDP port");
+ }
+ Objects.requireNonNull(binder, "Null Binder passed to openUdpEncapsulationSocket");
+
+ int callingUid = Binder.getCallingUid();
+ UserRecord userRecord = mUserResourceTracker.getUserRecord(callingUid);
+ final int resourceId = mNextResourceId++;
+
+ ParcelFileDescriptor pFd = null;
+ try {
+ if (!userRecord.mSocketQuotaTracker.isAvailable()) {
+ return new IpSecUdpEncapResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
+ }
+
+ FileDescriptor sockFd = null;
+ try {
+ sockFd = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ pFd = ParcelFileDescriptor.dup(sockFd);
+ } finally {
+ IoUtils.closeQuietly(sockFd);
+ }
+
+ mUidFdTagger.tag(pFd.getFileDescriptor(), callingUid);
+ // This code is common to both the unspecified and specified port cases
+ Os.setsockoptInt(
+ pFd.getFileDescriptor(),
+ OsConstants.IPPROTO_UDP,
+ OsConstants.UDP_ENCAP,
+ OsConstants.UDP_ENCAP_ESPINUDP);
+
+ mNetd.ipSecSetEncapSocketOwner(pFd, callingUid);
+ if (port != 0) {
+ Log.v(TAG, "Binding to port " + port);
+ Os.bind(pFd.getFileDescriptor(), INADDR_ANY, port);
+ } else {
+ port = bindToRandomPort(pFd.getFileDescriptor());
+ }
+
+ userRecord.mEncapSocketRecords.put(
+ resourceId,
+ new RefcountedResource<EncapSocketRecord>(
+ new EncapSocketRecord(resourceId, pFd.getFileDescriptor(), port),
+ binder));
+ return new IpSecUdpEncapResponse(IpSecManager.Status.OK, resourceId, port,
+ pFd.getFileDescriptor());
+ } catch (IOException | ErrnoException e) {
+ try {
+ if (pFd != null) {
+ pFd.close();
+ }
+ } catch (IOException ex) {
+ // Nothing can be done at this point
+ Log.e(TAG, "Failed to close pFd.");
+ }
+ }
+ // If we make it to here, then something has gone wrong and we couldn't open a socket.
+ // The only reasonable condition that would cause that is resource unavailable.
+ return new IpSecUdpEncapResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
+ }
+
+ /** close a socket that has been been allocated by and registered with the system server */
+ @Override
+ public synchronized void closeUdpEncapsulationSocket(int resourceId) throws RemoteException {
+ UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
+ releaseResource(userRecord.mEncapSocketRecords, resourceId);
+ }
+
+ /**
+ * Create a tunnel interface for use in IPSec tunnel mode. The system server will cache the
+ * tunnel interface and a record of its owner so that it can and must be freed when no longer
+ * needed.
+ */
+ @Override
+ public synchronized IpSecTunnelInterfaceResponse createTunnelInterface(
+ String localAddr, String remoteAddr, Network underlyingNetwork, IBinder binder,
+ String callingPackage) {
+ enforceTunnelFeatureAndPermissions(callingPackage);
+ Objects.requireNonNull(binder, "Null Binder passed to createTunnelInterface");
+ Objects.requireNonNull(underlyingNetwork, "No underlying network was specified");
+ checkInetAddress(localAddr);
+ checkInetAddress(remoteAddr);
+
+ // TODO: Check that underlying network exists, and IP addresses not assigned to a different
+ // network (b/72316676).
+
+ int callerUid = Binder.getCallingUid();
+ UserRecord userRecord = mUserResourceTracker.getUserRecord(callerUid);
+ if (!userRecord.mTunnelQuotaTracker.isAvailable()) {
+ return new IpSecTunnelInterfaceResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
+ }
+
+ final int resourceId = mNextResourceId++;
+ final int ikey = reserveNetId();
+ final int okey = reserveNetId();
+ String intfName = String.format("%s%d", INetd.IPSEC_INTERFACE_PREFIX, resourceId);
+
+ try {
+ // Calls to netd:
+ // Create VTI
+ // Add inbound/outbound global policies
+ // (use reqid = 0)
+ mNetd.ipSecAddTunnelInterface(intfName, localAddr, remoteAddr, ikey, okey, resourceId);
+
+ BinderUtils.withCleanCallingIdentity(() -> {
+ NetdUtils.setInterfaceUp(mNetd, intfName);
+ });
+
+ for (int selAddrFamily : ADDRESS_FAMILIES) {
+ // Always send down correct local/remote addresses for template.
+ mNetd.ipSecAddSecurityPolicy(
+ callerUid,
+ selAddrFamily,
+ IpSecManager.DIRECTION_OUT,
+ localAddr,
+ remoteAddr,
+ 0,
+ okey,
+ 0xffffffff,
+ resourceId);
+ mNetd.ipSecAddSecurityPolicy(
+ callerUid,
+ selAddrFamily,
+ IpSecManager.DIRECTION_IN,
+ remoteAddr,
+ localAddr,
+ 0,
+ ikey,
+ 0xffffffff,
+ resourceId);
+
+ // Add a forwarding policy on the tunnel interface. In order to support forwarding
+ // the IpSecTunnelInterface must have a forwarding policy matching the incoming SA.
+ //
+ // Unless a IpSecTransform is also applied against this interface in DIRECTION_FWD,
+ // forwarding will be blocked by default (as would be the case if this policy was
+ // absent).
+ //
+ // This is necessary only on the tunnel interface, and not any the interface to
+ // which traffic will be forwarded to.
+ mNetd.ipSecAddSecurityPolicy(
+ callerUid,
+ selAddrFamily,
+ IpSecManager.DIRECTION_FWD,
+ remoteAddr,
+ localAddr,
+ 0,
+ ikey,
+ 0xffffffff,
+ resourceId);
+ }
+
+ userRecord.mTunnelInterfaceRecords.put(
+ resourceId,
+ new RefcountedResource<TunnelInterfaceRecord>(
+ new TunnelInterfaceRecord(
+ resourceId,
+ intfName,
+ underlyingNetwork,
+ localAddr,
+ remoteAddr,
+ ikey,
+ okey,
+ resourceId),
+ binder));
+ return new IpSecTunnelInterfaceResponse(IpSecManager.Status.OK, resourceId, intfName);
+ } catch (RemoteException e) {
+ // Release keys if we got an error.
+ releaseNetId(ikey);
+ releaseNetId(okey);
+ throw e.rethrowFromSystemServer();
+ } catch (Throwable t) {
+ // Release keys if we got an error.
+ releaseNetId(ikey);
+ releaseNetId(okey);
+ throw t;
+ }
+ }
+
+ /**
+ * Adds a new local address to the tunnel interface. This allows packets to be sent and received
+ * from multiple local IP addresses over the same tunnel.
+ */
+ @Override
+ public synchronized void addAddressToTunnelInterface(
+ int tunnelResourceId, LinkAddress localAddr, String callingPackage) {
+ enforceTunnelFeatureAndPermissions(callingPackage);
+ UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
+
+ // Get tunnelInterface record; if no such interface is found, will throw
+ // IllegalArgumentException
+ TunnelInterfaceRecord tunnelInterfaceInfo =
+ userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelResourceId);
+
+ try {
+ // We can assume general validity of the IP address, since we get them as a
+ // LinkAddress, which does some validation.
+ mNetd.interfaceAddAddress(
+ tunnelInterfaceInfo.mInterfaceName,
+ localAddr.getAddress().getHostAddress(),
+ localAddr.getPrefixLength());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Remove a new local address from the tunnel interface. After removal, the address will no
+ * longer be available to send from, or receive on.
+ */
+ @Override
+ public synchronized void removeAddressFromTunnelInterface(
+ int tunnelResourceId, LinkAddress localAddr, String callingPackage) {
+ enforceTunnelFeatureAndPermissions(callingPackage);
+
+ UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
+ // Get tunnelInterface record; if no such interface is found, will throw
+ // IllegalArgumentException
+ TunnelInterfaceRecord tunnelInterfaceInfo =
+ userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelResourceId);
+
+ try {
+ // We can assume general validity of the IP address, since we get them as a
+ // LinkAddress, which does some validation.
+ mNetd.interfaceDelAddress(
+ tunnelInterfaceInfo.mInterfaceName,
+ localAddr.getAddress().getHostAddress(),
+ localAddr.getPrefixLength());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** Set TunnelInterface to use a specific underlying network. */
+ @Override
+ public synchronized void setNetworkForTunnelInterface(
+ int tunnelResourceId, Network underlyingNetwork, String callingPackage) {
+ enforceTunnelFeatureAndPermissions(callingPackage);
+ Objects.requireNonNull(underlyingNetwork, "No underlying network was specified");
+
+ final UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
+
+ // Get tunnelInterface record; if no such interface is found, will throw
+ // IllegalArgumentException. userRecord.mTunnelInterfaceRecords is never null
+ final TunnelInterfaceRecord tunnelInterfaceInfo =
+ userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelResourceId);
+
+ final ConnectivityManager connectivityManager =
+ mContext.getSystemService(ConnectivityManager.class);
+ final LinkProperties lp = connectivityManager.getLinkProperties(underlyingNetwork);
+ if (lp == null) {
+ throw new IllegalArgumentException(
+ "LinkProperties is null. The underlyingNetwork may not be functional");
+ }
+
+ if (tunnelInterfaceInfo.getInterfaceName().equals(lp.getInterfaceName())) {
+ throw new IllegalArgumentException(
+ "Underlying network cannot be the network being exposed by this tunnel");
+ }
+
+ // It is meaningless to check if the network exists or is valid because the network might
+ // disconnect at any time after it passes the check.
+
+ tunnelInterfaceInfo.setUnderlyingNetwork(underlyingNetwork);
+ }
+
+ /**
+ * Delete a TunnelInterface that has been been allocated by and registered with the system
+ * server
+ */
+ @Override
+ public synchronized void deleteTunnelInterface(
+ int resourceId, String callingPackage) throws RemoteException {
+ enforceTunnelFeatureAndPermissions(callingPackage);
+ UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
+ releaseResource(userRecord.mTunnelInterfaceRecords, resourceId);
+ }
+
+ @VisibleForTesting
+ void validateAlgorithms(IpSecConfig config) throws IllegalArgumentException {
+ IpSecAlgorithm auth = config.getAuthentication();
+ IpSecAlgorithm crypt = config.getEncryption();
+ IpSecAlgorithm aead = config.getAuthenticatedEncryption();
+
+ // Validate the algorithm set
+ Preconditions.checkArgument(
+ aead != null || crypt != null || auth != null,
+ "No Encryption or Authentication algorithms specified");
+ Preconditions.checkArgument(
+ auth == null || auth.isAuthentication(),
+ "Unsupported algorithm for Authentication");
+ Preconditions.checkArgument(
+ crypt == null || crypt.isEncryption(), "Unsupported algorithm for Encryption");
+ Preconditions.checkArgument(
+ aead == null || aead.isAead(),
+ "Unsupported algorithm for Authenticated Encryption");
+ Preconditions.checkArgument(
+ aead == null || (auth == null && crypt == null),
+ "Authenticated Encryption is mutually exclusive with other Authentication "
+ + "or Encryption algorithms");
+ }
+
+ private int getFamily(String inetAddress) {
+ int family = AF_UNSPEC;
+ InetAddress checkAddress = InetAddresses.parseNumericAddress(inetAddress);
+ if (checkAddress instanceof Inet4Address) {
+ family = AF_INET;
+ } else if (checkAddress instanceof Inet6Address) {
+ family = AF_INET6;
+ }
+ return family;
+ }
+
+ /**
+ * Checks an IpSecConfig parcel to ensure that the contents are valid and throws an
+ * IllegalArgumentException if they are not.
+ */
+ private void checkIpSecConfig(IpSecConfig config) {
+ UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
+
+ switch (config.getEncapType()) {
+ case IpSecTransform.ENCAP_NONE:
+ break;
+ case IpSecTransform.ENCAP_ESPINUDP:
+ case IpSecTransform.ENCAP_ESPINUDP_NON_IKE:
+ // Retrieve encap socket record; will throw IllegalArgumentException if not found
+ userRecord.mEncapSocketRecords.getResourceOrThrow(
+ config.getEncapSocketResourceId());
+
+ int port = config.getEncapRemotePort();
+ if (port <= 0 || port > 0xFFFF) {
+ throw new IllegalArgumentException("Invalid remote UDP port: " + port);
+ }
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid Encap Type: " + config.getEncapType());
+ }
+
+ validateAlgorithms(config);
+
+ // Retrieve SPI record; will throw IllegalArgumentException if not found
+ SpiRecord s = userRecord.mSpiRecords.getResourceOrThrow(config.getSpiResourceId());
+
+ // Check to ensure that SPI has not already been used.
+ if (s.getOwnedByTransform()) {
+ throw new IllegalStateException("SPI already in use; cannot be used in new Transforms");
+ }
+
+ // If no remote address is supplied, then use one from the SPI.
+ if (TextUtils.isEmpty(config.getDestinationAddress())) {
+ config.setDestinationAddress(s.getDestinationAddress());
+ }
+
+ // All remote addresses must match
+ if (!config.getDestinationAddress().equals(s.getDestinationAddress())) {
+ throw new IllegalArgumentException("Mismatched remote addresseses.");
+ }
+
+ // This check is technically redundant due to the chain of custody between the SPI and
+ // the IpSecConfig, but in the future if the dest is allowed to be set explicitly in
+ // the transform, this will prevent us from messing up.
+ checkInetAddress(config.getDestinationAddress());
+
+ // Require a valid source address for all transforms.
+ checkInetAddress(config.getSourceAddress());
+
+ // Check to ensure source and destination have the same address family.
+ String sourceAddress = config.getSourceAddress();
+ String destinationAddress = config.getDestinationAddress();
+ int sourceFamily = getFamily(sourceAddress);
+ int destinationFamily = getFamily(destinationAddress);
+ if (sourceFamily != destinationFamily) {
+ throw new IllegalArgumentException(
+ "Source address ("
+ + sourceAddress
+ + ") and destination address ("
+ + destinationAddress
+ + ") have different address families.");
+ }
+
+ // Throw an error if UDP Encapsulation is not used in IPv4.
+ if (config.getEncapType() != IpSecTransform.ENCAP_NONE && sourceFamily != AF_INET) {
+ throw new IllegalArgumentException(
+ "UDP Encapsulation is not supported for this address family");
+ }
+
+ switch (config.getMode()) {
+ case IpSecTransform.MODE_TRANSPORT:
+ break;
+ case IpSecTransform.MODE_TUNNEL:
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Invalid IpSecTransform.mode: " + config.getMode());
+ }
+
+ config.setMarkValue(0);
+ config.setMarkMask(0);
+ }
+
+ private static final String TUNNEL_OP = AppOpsManager.OPSTR_MANAGE_IPSEC_TUNNELS;
+
+ private void enforceTunnelFeatureAndPermissions(String callingPackage) {
+ if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS)) {
+ throw new UnsupportedOperationException(
+ "IPsec Tunnel Mode requires PackageManager.FEATURE_IPSEC_TUNNELS");
+ }
+
+ Objects.requireNonNull(callingPackage, "Null calling package cannot create IpSec tunnels");
+
+ // OP_MANAGE_IPSEC_TUNNELS will return MODE_ERRORED by default, including for the system
+ // server. If the appop is not granted, require that the caller has the MANAGE_IPSEC_TUNNELS
+ // permission or is the System Server.
+ if (AppOpsManager.MODE_ALLOWED == getAppOpsManager().noteOpNoThrow(
+ TUNNEL_OP, Binder.getCallingUid(), callingPackage)) {
+ return;
+ }
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.MANAGE_IPSEC_TUNNELS, "IpSecService");
+ }
+
+ private void createOrUpdateTransform(
+ IpSecConfig c, int resourceId, SpiRecord spiRecord, EncapSocketRecord socketRecord)
+ throws RemoteException {
+
+ int encapType = c.getEncapType(), encapLocalPort = 0, encapRemotePort = 0;
+ if (encapType != IpSecTransform.ENCAP_NONE) {
+ encapLocalPort = socketRecord.getPort();
+ encapRemotePort = c.getEncapRemotePort();
+ }
+
+ IpSecAlgorithm auth = c.getAuthentication();
+ IpSecAlgorithm crypt = c.getEncryption();
+ IpSecAlgorithm authCrypt = c.getAuthenticatedEncryption();
+
+ String cryptName;
+ if (crypt == null) {
+ cryptName = (authCrypt == null) ? IpSecAlgorithm.CRYPT_NULL : "";
+ } else {
+ cryptName = crypt.getName();
+ }
+
+ mNetd.ipSecAddSecurityAssociation(
+ Binder.getCallingUid(),
+ c.getMode(),
+ c.getSourceAddress(),
+ c.getDestinationAddress(),
+ (c.getNetwork() != null) ? c.getNetwork().getNetId() : 0,
+ spiRecord.getSpi(),
+ c.getMarkValue(),
+ c.getMarkMask(),
+ (auth != null) ? auth.getName() : "",
+ (auth != null) ? auth.getKey() : new byte[] {},
+ (auth != null) ? auth.getTruncationLengthBits() : 0,
+ cryptName,
+ (crypt != null) ? crypt.getKey() : new byte[] {},
+ (crypt != null) ? crypt.getTruncationLengthBits() : 0,
+ (authCrypt != null) ? authCrypt.getName() : "",
+ (authCrypt != null) ? authCrypt.getKey() : new byte[] {},
+ (authCrypt != null) ? authCrypt.getTruncationLengthBits() : 0,
+ encapType,
+ encapLocalPort,
+ encapRemotePort,
+ c.getXfrmInterfaceId());
+ }
+
+ /**
+ * Create a IPsec transform, which represents a single security association in the kernel. The
+ * transform will be cached by the system server and must be freed when no longer needed. It is
+ * possible to free one, deleting the SA from underneath sockets that are using it, which will
+ * result in all of those sockets becoming unable to send or receive data.
+ */
+ @Override
+ public synchronized IpSecTransformResponse createTransform(
+ IpSecConfig c, IBinder binder, String callingPackage) throws RemoteException {
+ Objects.requireNonNull(c);
+ if (c.getMode() == IpSecTransform.MODE_TUNNEL) {
+ enforceTunnelFeatureAndPermissions(callingPackage);
+ }
+ checkIpSecConfig(c);
+ Objects.requireNonNull(binder, "Null Binder passed to createTransform");
+ final int resourceId = mNextResourceId++;
+
+ UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
+ List<RefcountedResource> dependencies = new ArrayList<>();
+
+ if (!userRecord.mTransformQuotaTracker.isAvailable()) {
+ return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
+ }
+
+ EncapSocketRecord socketRecord = null;
+ if (c.getEncapType() != IpSecTransform.ENCAP_NONE) {
+ RefcountedResource<EncapSocketRecord> refcountedSocketRecord =
+ userRecord.mEncapSocketRecords.getRefcountedResourceOrThrow(
+ c.getEncapSocketResourceId());
+ dependencies.add(refcountedSocketRecord);
+ socketRecord = refcountedSocketRecord.getResource();
+ }
+
+ RefcountedResource<SpiRecord> refcountedSpiRecord =
+ userRecord.mSpiRecords.getRefcountedResourceOrThrow(c.getSpiResourceId());
+ dependencies.add(refcountedSpiRecord);
+ SpiRecord spiRecord = refcountedSpiRecord.getResource();
+
+ createOrUpdateTransform(c, resourceId, spiRecord, socketRecord);
+
+ // SA was created successfully, time to construct a record and lock it away
+ userRecord.mTransformRecords.put(
+ resourceId,
+ new RefcountedResource<TransformRecord>(
+ new TransformRecord(resourceId, c, spiRecord, socketRecord),
+ binder,
+ dependencies.toArray(new RefcountedResource[dependencies.size()])));
+ return new IpSecTransformResponse(IpSecManager.Status.OK, resourceId);
+ }
+
+ /**
+ * Delete a transport mode transform that was previously allocated by + registered with the
+ * system server. If this is called on an inactive (or non-existent) transform, it will not
+ * return an error. It's safe to de-allocate transforms that may have already been deleted for
+ * other reasons.
+ */
+ @Override
+ public synchronized void deleteTransform(int resourceId) throws RemoteException {
+ UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
+ releaseResource(userRecord.mTransformRecords, resourceId);
+ }
+
+ /**
+ * Apply an active transport mode transform to a socket, which will apply the IPsec security
+ * association as a correspondent policy to the provided socket
+ */
+ @Override
+ public synchronized void applyTransportModeTransform(
+ ParcelFileDescriptor socket, int direction, int resourceId) throws RemoteException {
+ int callingUid = Binder.getCallingUid();
+ UserRecord userRecord = mUserResourceTracker.getUserRecord(callingUid);
+ checkDirection(direction);
+ // Get transform record; if no transform is found, will throw IllegalArgumentException
+ TransformRecord info = userRecord.mTransformRecords.getResourceOrThrow(resourceId);
+
+ // TODO: make this a function.
+ if (info.mPid != getCallingPid() || info.mUid != callingUid) {
+ throw new SecurityException("Only the owner of an IpSec Transform may apply it!");
+ }
+
+ // Get config and check that to-be-applied transform has the correct mode
+ IpSecConfig c = info.getConfig();
+ Preconditions.checkArgument(
+ c.getMode() == IpSecTransform.MODE_TRANSPORT,
+ "Transform mode was not Transport mode; cannot be applied to a socket");
+
+ mNetd.ipSecApplyTransportModeTransform(
+ socket,
+ callingUid,
+ direction,
+ c.getSourceAddress(),
+ c.getDestinationAddress(),
+ info.getSpiRecord().getSpi());
+ }
+
+ /**
+ * Remove transport mode transforms from a socket, applying the default (empty) policy. This
+ * ensures that NO IPsec policy is applied to the socket (would be the equivalent of applying a
+ * policy that performs no IPsec). Today the resourceId parameter is passed but not used:
+ * reserved for future improved input validation.
+ */
+ @Override
+ public synchronized void removeTransportModeTransforms(ParcelFileDescriptor socket)
+ throws RemoteException {
+ mNetd.ipSecRemoveTransportModeTransform(socket);
+ }
+
+ /**
+ * Apply an active tunnel mode transform to a TunnelInterface, which will apply the IPsec
+ * security association as a correspondent policy to the provided interface
+ */
+ @Override
+ public synchronized void applyTunnelModeTransform(
+ int tunnelResourceId, int direction,
+ int transformResourceId, String callingPackage) throws RemoteException {
+ enforceTunnelFeatureAndPermissions(callingPackage);
+ checkDirection(direction);
+
+ int callingUid = Binder.getCallingUid();
+ UserRecord userRecord = mUserResourceTracker.getUserRecord(callingUid);
+
+ // Get transform record; if no transform is found, will throw IllegalArgumentException
+ TransformRecord transformInfo =
+ userRecord.mTransformRecords.getResourceOrThrow(transformResourceId);
+
+ // Get tunnelInterface record; if no such interface is found, will throw
+ // IllegalArgumentException
+ TunnelInterfaceRecord tunnelInterfaceInfo =
+ userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelResourceId);
+
+ // Get config and check that to-be-applied transform has the correct mode
+ IpSecConfig c = transformInfo.getConfig();
+ Preconditions.checkArgument(
+ c.getMode() == IpSecTransform.MODE_TUNNEL,
+ "Transform mode was not Tunnel mode; cannot be applied to a tunnel interface");
+
+ EncapSocketRecord socketRecord = null;
+ if (c.getEncapType() != IpSecTransform.ENCAP_NONE) {
+ socketRecord =
+ userRecord.mEncapSocketRecords.getResourceOrThrow(c.getEncapSocketResourceId());
+ }
+ SpiRecord spiRecord = transformInfo.getSpiRecord();
+
+ int mark =
+ (direction == IpSecManager.DIRECTION_OUT)
+ ? tunnelInterfaceInfo.getOkey()
+ : tunnelInterfaceInfo.getIkey(); // Ikey also used for FWD policies
+
+ try {
+ // Default to using the invalid SPI of 0 for inbound SAs. This allows policies to skip
+ // SPI matching as part of the template resolution.
+ int spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
+ c.setXfrmInterfaceId(tunnelInterfaceInfo.getIfId());
+
+ // TODO: enable this when UPDSA supports updating marks. Adding kernel support upstream
+ // (and backporting) would allow us to narrow the mark space, and ensure that the SA
+ // and SPs have matching marks (as VTI are meant to be built).
+ // Currently update does nothing with marks. Leave empty (defaulting to 0) to ensure the
+ // config matches the actual allocated resources in the kernel.
+ // All SAs will have zero marks (from creation time), and any policy that matches the
+ // same src/dst could match these SAs. Non-IpSecService governed processes that
+ // establish floating policies with the same src/dst may result in undefined
+ // behavior. This is generally limited to vendor code due to the permissions
+ // (CAP_NET_ADMIN) required.
+ //
+ // c.setMarkValue(mark);
+ // c.setMarkMask(0xffffffff);
+
+ if (direction == IpSecManager.DIRECTION_OUT) {
+ // Set output mark via underlying network (output only)
+ c.setNetwork(tunnelInterfaceInfo.getUnderlyingNetwork());
+
+ // Set outbound SPI only. We want inbound to use any valid SA (old, new) on rekeys,
+ // but want to guarantee outbound packets are sent over the new SA.
+ spi = spiRecord.getSpi();
+ }
+
+ // Always update the policy with the relevant XFRM_IF_ID
+ for (int selAddrFamily : ADDRESS_FAMILIES) {
+ mNetd.ipSecUpdateSecurityPolicy(
+ callingUid,
+ selAddrFamily,
+ direction,
+ transformInfo.getConfig().getSourceAddress(),
+ transformInfo.getConfig().getDestinationAddress(),
+ spi, // If outbound, also add SPI to the policy.
+ mark, // Must always set policy mark; ikey/okey for VTIs
+ 0xffffffff,
+ c.getXfrmInterfaceId());
+ }
+
+ // Update SA with tunnel mark (ikey or okey based on direction)
+ createOrUpdateTransform(c, transformResourceId, spiRecord, socketRecord);
+ } catch (ServiceSpecificException e) {
+ if (e.errorCode == EINVAL) {
+ throw new IllegalArgumentException(e.toString());
+ } else {
+ throw e;
+ }
+ }
+ }
+
+ @Override
+ protected synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ mContext.enforceCallingOrSelfPermission(DUMP, TAG);
+
+ pw.println("IpSecService dump:");
+ pw.println();
+
+ pw.println("mUserResourceTracker:");
+ pw.println(mUserResourceTracker);
+ }
+}
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
new file mode 100644
index 0000000..7115720
--- /dev/null
+++ b/service-t/src/com/android/server/NsdService.java
@@ -0,0 +1,1060 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static android.net.ConnectivityManager.NETID_UNSET;
+import static android.net.nsd.NsdManager.MDNS_SERVICE_EVENT;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.INetd;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.mdns.aidl.DiscoveryInfo;
+import android.net.mdns.aidl.GetAddressInfo;
+import android.net.mdns.aidl.IMDnsEventListener;
+import android.net.mdns.aidl.RegistrationInfo;
+import android.net.mdns.aidl.ResolutionInfo;
+import android.net.nsd.INsdManager;
+import android.net.nsd.INsdManagerCallback;
+import android.net.nsd.INsdServiceConnector;
+import android.net.nsd.MDnsManager;
+import android.net.nsd.NsdManager;
+import android.net.nsd.NsdServiceInfo;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.Pair;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.HashMap;
+
+/**
+ * Network Service Discovery Service handles remote service discovery operation requests by
+ * implementing the INsdManager interface.
+ *
+ * @hide
+ */
+public class NsdService extends INsdManager.Stub {
+ private static final String TAG = "NsdService";
+ private static final String MDNS_TAG = "mDnsConnector";
+
+ private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final long CLEANUP_DELAY_MS = 10000;
+ private static final int IFACE_IDX_ANY = 0;
+
+ private final Context mContext;
+ private final NsdStateMachine mNsdStateMachine;
+ private final MDnsManager mMDnsManager;
+ private final MDnsEventCallback mMDnsEventCallback;
+ // WARNING : Accessing this value in any thread is not safe, it must only be changed in the
+ // state machine thread. If change this outside state machine, it will need to introduce
+ // synchronization.
+ private boolean mIsDaemonStarted = false;
+
+ /**
+ * Clients receiving asynchronous messages
+ */
+ private final HashMap<NsdServiceConnector, ClientInfo> mClients = new HashMap<>();
+
+ /* A map from unique id to client info */
+ private final SparseArray<ClientInfo> mIdToClientInfoMap= new SparseArray<>();
+
+ private final long mCleanupDelayMs;
+
+ private static final int INVALID_ID = 0;
+ private int mUniqueId = 1;
+ // The count of the connected legacy clients.
+ private int mLegacyClientCount = 0;
+
+ private class NsdStateMachine extends StateMachine {
+
+ private final DefaultState mDefaultState = new DefaultState();
+ private final EnabledState mEnabledState = new EnabledState();
+
+ @Override
+ protected String getWhatToString(int what) {
+ return NsdManager.nameOf(what);
+ }
+
+ private void maybeStartDaemon() {
+ if (mIsDaemonStarted) {
+ if (DBG) Log.d(TAG, "Daemon is already started.");
+ return;
+ }
+ mMDnsManager.registerEventListener(mMDnsEventCallback);
+ mMDnsManager.startDaemon();
+ mIsDaemonStarted = true;
+ maybeScheduleStop();
+ }
+
+ private void maybeStopDaemon() {
+ if (!mIsDaemonStarted) {
+ if (DBG) Log.d(TAG, "Daemon has not been started.");
+ return;
+ }
+ mMDnsManager.unregisterEventListener(mMDnsEventCallback);
+ mMDnsManager.stopDaemon();
+ mIsDaemonStarted = false;
+ }
+
+ private boolean isAnyRequestActive() {
+ return mIdToClientInfoMap.size() != 0;
+ }
+
+ private void scheduleStop() {
+ sendMessageDelayed(NsdManager.DAEMON_CLEANUP, mCleanupDelayMs);
+ }
+ private void maybeScheduleStop() {
+ // The native daemon should stay alive and can't be cleanup
+ // if any legacy client connected.
+ if (!isAnyRequestActive() && mLegacyClientCount == 0) {
+ scheduleStop();
+ }
+ }
+
+ private void cancelStop() {
+ this.removeMessages(NsdManager.DAEMON_CLEANUP);
+ }
+
+ NsdStateMachine(String name, Handler handler) {
+ super(name, handler);
+ addState(mDefaultState);
+ addState(mEnabledState, mDefaultState);
+ State initialState = mEnabledState;
+ setInitialState(initialState);
+ setLogRecSize(25);
+ }
+
+ class DefaultState extends State {
+ @Override
+ public boolean processMessage(Message msg) {
+ final ClientInfo cInfo;
+ final int clientId = msg.arg2;
+ switch (msg.what) {
+ case NsdManager.REGISTER_CLIENT:
+ final Pair<NsdServiceConnector, INsdManagerCallback> arg =
+ (Pair<NsdServiceConnector, INsdManagerCallback>) msg.obj;
+ final INsdManagerCallback cb = arg.second;
+ try {
+ cb.asBinder().linkToDeath(arg.first, 0);
+ cInfo = new ClientInfo(cb);
+ mClients.put(arg.first, cInfo);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Client " + clientId + " has already died");
+ }
+ break;
+ case NsdManager.UNREGISTER_CLIENT:
+ final NsdServiceConnector connector = (NsdServiceConnector) msg.obj;
+ cInfo = mClients.remove(connector);
+ if (cInfo != null) {
+ cInfo.expungeAllRequests();
+ if (cInfo.isLegacy()) {
+ mLegacyClientCount -= 1;
+ }
+ }
+ maybeScheduleStop();
+ break;
+ case NsdManager.DISCOVER_SERVICES:
+ cInfo = getClientInfoForReply(msg);
+ if (cInfo != null) {
+ cInfo.onDiscoverServicesFailed(
+ clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+ }
+ break;
+ case NsdManager.STOP_DISCOVERY:
+ cInfo = getClientInfoForReply(msg);
+ if (cInfo != null) {
+ cInfo.onStopDiscoveryFailed(
+ clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+ }
+ break;
+ case NsdManager.REGISTER_SERVICE:
+ cInfo = getClientInfoForReply(msg);
+ if (cInfo != null) {
+ cInfo.onRegisterServiceFailed(
+ clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+ }
+ break;
+ case NsdManager.UNREGISTER_SERVICE:
+ cInfo = getClientInfoForReply(msg);
+ if (cInfo != null) {
+ cInfo.onUnregisterServiceFailed(
+ clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+ }
+ break;
+ case NsdManager.RESOLVE_SERVICE:
+ cInfo = getClientInfoForReply(msg);
+ if (cInfo != null) {
+ cInfo.onResolveServiceFailed(
+ clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+ }
+ break;
+ case NsdManager.DAEMON_CLEANUP:
+ maybeStopDaemon();
+ break;
+ // This event should be only sent by the legacy (target SDK < S) clients.
+ // Mark the sending client as legacy.
+ case NsdManager.DAEMON_STARTUP:
+ cInfo = getClientInfoForReply(msg);
+ if (cInfo != null) {
+ cancelStop();
+ cInfo.setLegacy();
+ mLegacyClientCount += 1;
+ maybeStartDaemon();
+ }
+ break;
+ default:
+ Log.e(TAG, "Unhandled " + msg);
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+
+ private ClientInfo getClientInfoForReply(Message msg) {
+ final ListenerArgs args = (ListenerArgs) msg.obj;
+ return mClients.get(args.connector);
+ }
+ }
+
+ class EnabledState extends State {
+ @Override
+ public void enter() {
+ sendNsdStateChangeBroadcast(true);
+ }
+
+ @Override
+ public void exit() {
+ // TODO: it is incorrect to stop the daemon without expunging all requests
+ // and sending error callbacks to clients.
+ scheduleStop();
+ }
+
+ private boolean requestLimitReached(ClientInfo clientInfo) {
+ if (clientInfo.mClientIds.size() >= ClientInfo.MAX_LIMIT) {
+ if (DBG) Log.d(TAG, "Exceeded max outstanding requests " + clientInfo);
+ return true;
+ }
+ return false;
+ }
+
+ private void storeRequestMap(int clientId, int globalId, ClientInfo clientInfo, int what) {
+ clientInfo.mClientIds.put(clientId, globalId);
+ clientInfo.mClientRequests.put(clientId, what);
+ mIdToClientInfoMap.put(globalId, clientInfo);
+ // Remove the cleanup event because here comes a new request.
+ cancelStop();
+ }
+
+ private void removeRequestMap(int clientId, int globalId, ClientInfo clientInfo) {
+ clientInfo.mClientIds.delete(clientId);
+ clientInfo.mClientRequests.delete(clientId);
+ mIdToClientInfoMap.remove(globalId);
+ maybeScheduleStop();
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ final ClientInfo clientInfo;
+ final int id;
+ final int clientId = msg.arg2;
+ final ListenerArgs args;
+ switch (msg.what) {
+ case NsdManager.DISCOVER_SERVICES:
+ if (DBG) Log.d(TAG, "Discover services");
+ args = (ListenerArgs) msg.obj;
+ clientInfo = mClients.get(args.connector);
+
+ if (requestLimitReached(clientInfo)) {
+ clientInfo.onDiscoverServicesFailed(
+ clientId, NsdManager.FAILURE_MAX_LIMIT);
+ break;
+ }
+
+ maybeStartDaemon();
+ id = getUniqueId();
+ if (discoverServices(id, args.serviceInfo)) {
+ if (DBG) {
+ Log.d(TAG, "Discover " + msg.arg2 + " " + id
+ + args.serviceInfo.getServiceType());
+ }
+ storeRequestMap(clientId, id, clientInfo, msg.what);
+ clientInfo.onDiscoverServicesStarted(clientId, args.serviceInfo);
+ } else {
+ stopServiceDiscovery(id);
+ clientInfo.onDiscoverServicesFailed(clientId,
+ NsdManager.FAILURE_INTERNAL_ERROR);
+ }
+ break;
+ case NsdManager.STOP_DISCOVERY:
+ if (DBG) Log.d(TAG, "Stop service discovery");
+ args = (ListenerArgs) msg.obj;
+ clientInfo = mClients.get(args.connector);
+
+ try {
+ id = clientInfo.mClientIds.get(clientId);
+ } catch (NullPointerException e) {
+ clientInfo.onStopDiscoveryFailed(
+ clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+ break;
+ }
+ removeRequestMap(clientId, id, clientInfo);
+ if (stopServiceDiscovery(id)) {
+ clientInfo.onStopDiscoverySucceeded(clientId);
+ } else {
+ clientInfo.onStopDiscoveryFailed(
+ clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+ }
+ break;
+ case NsdManager.REGISTER_SERVICE:
+ if (DBG) Log.d(TAG, "Register service");
+ args = (ListenerArgs) msg.obj;
+ clientInfo = mClients.get(args.connector);
+ if (requestLimitReached(clientInfo)) {
+ clientInfo.onRegisterServiceFailed(
+ clientId, NsdManager.FAILURE_MAX_LIMIT);
+ break;
+ }
+
+ maybeStartDaemon();
+ id = getUniqueId();
+ if (registerService(id, args.serviceInfo)) {
+ if (DBG) Log.d(TAG, "Register " + clientId + " " + id);
+ storeRequestMap(clientId, id, clientInfo, msg.what);
+ // Return success after mDns reports success
+ } else {
+ unregisterService(id);
+ clientInfo.onRegisterServiceFailed(
+ clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+ }
+ break;
+ case NsdManager.UNREGISTER_SERVICE:
+ if (DBG) Log.d(TAG, "unregister service");
+ args = (ListenerArgs) msg.obj;
+ clientInfo = mClients.get(args.connector);
+ if (clientInfo == null) {
+ Log.e(TAG, "Unknown connector in unregistration");
+ break;
+ }
+ id = clientInfo.mClientIds.get(clientId);
+ removeRequestMap(clientId, id, clientInfo);
+ if (unregisterService(id)) {
+ clientInfo.onUnregisterServiceSucceeded(clientId);
+ } else {
+ clientInfo.onUnregisterServiceFailed(
+ clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+ }
+ break;
+ case NsdManager.RESOLVE_SERVICE:
+ if (DBG) Log.d(TAG, "Resolve service");
+ args = (ListenerArgs) msg.obj;
+ clientInfo = mClients.get(args.connector);
+
+ if (clientInfo.mResolvedService != null) {
+ clientInfo.onResolveServiceFailed(
+ clientId, NsdManager.FAILURE_ALREADY_ACTIVE);
+ break;
+ }
+
+ maybeStartDaemon();
+ id = getUniqueId();
+ if (resolveService(id, args.serviceInfo)) {
+ clientInfo.mResolvedService = new NsdServiceInfo();
+ storeRequestMap(clientId, id, clientInfo, msg.what);
+ } else {
+ clientInfo.onResolveServiceFailed(
+ clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+ }
+ break;
+ case MDNS_SERVICE_EVENT:
+ if (!handleMDnsServiceEvent(msg.arg1, msg.arg2, msg.obj)) {
+ return NOT_HANDLED;
+ }
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+
+ private boolean handleMDnsServiceEvent(int code, int id, Object obj) {
+ NsdServiceInfo servInfo;
+ ClientInfo clientInfo = mIdToClientInfoMap.get(id);
+ if (clientInfo == null) {
+ Log.e(TAG, String.format("id %d for %d has no client mapping", id, code));
+ return false;
+ }
+
+ /* This goes in response as msg.arg2 */
+ int clientId = clientInfo.getClientId(id);
+ if (clientId < 0) {
+ // This can happen because of race conditions. For example,
+ // SERVICE_FOUND may race with STOP_SERVICE_DISCOVERY,
+ // and we may get in this situation.
+ Log.d(TAG, String.format("%d for listener id %d that is no longer active",
+ code, id));
+ return false;
+ }
+ if (DBG) {
+ Log.d(TAG, String.format("MDns service event code:%d id=%d", code, id));
+ }
+ switch (code) {
+ case IMDnsEventListener.SERVICE_FOUND: {
+ final DiscoveryInfo info = (DiscoveryInfo) obj;
+ final String name = info.serviceName;
+ final String type = info.registrationType;
+ servInfo = new NsdServiceInfo(name, type);
+ final int foundNetId = info.netId;
+ if (foundNetId == 0L) {
+ // Ignore services that do not have a Network: they are not usable
+ // by apps, as they would need privileged permissions to use
+ // interfaces that do not have an associated Network.
+ break;
+ }
+ setServiceNetworkForCallback(servInfo, info.netId, info.interfaceIdx);
+ clientInfo.onServiceFound(clientId, servInfo);
+ break;
+ }
+ case IMDnsEventListener.SERVICE_LOST: {
+ final DiscoveryInfo info = (DiscoveryInfo) obj;
+ final String name = info.serviceName;
+ final String type = info.registrationType;
+ final int lostNetId = info.netId;
+ servInfo = new NsdServiceInfo(name, type);
+ // The network could be set to null (netId 0) if it was torn down when the
+ // service is lost
+ // TODO: avoid returning null in that case, possibly by remembering
+ // found services on the same interface index and their network at the time
+ setServiceNetworkForCallback(servInfo, lostNetId, info.interfaceIdx);
+ clientInfo.onServiceLost(clientId, servInfo);
+ break;
+ }
+ case IMDnsEventListener.SERVICE_DISCOVERY_FAILED:
+ clientInfo.onDiscoverServicesFailed(
+ clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+ break;
+ case IMDnsEventListener.SERVICE_REGISTERED: {
+ final RegistrationInfo info = (RegistrationInfo) obj;
+ final String name = info.serviceName;
+ servInfo = new NsdServiceInfo(name, null /* serviceType */);
+ clientInfo.onRegisterServiceSucceeded(clientId, servInfo);
+ break;
+ }
+ case IMDnsEventListener.SERVICE_REGISTRATION_FAILED:
+ clientInfo.onRegisterServiceFailed(
+ clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+ break;
+ case IMDnsEventListener.SERVICE_RESOLVED: {
+ final ResolutionInfo info = (ResolutionInfo) obj;
+ int index = 0;
+ final String fullName = info.serviceFullName;
+ while (index < fullName.length() && fullName.charAt(index) != '.') {
+ if (fullName.charAt(index) == '\\') {
+ ++index;
+ }
+ ++index;
+ }
+ if (index >= fullName.length()) {
+ Log.e(TAG, "Invalid service found " + fullName);
+ break;
+ }
+
+ String name = unescape(fullName.substring(0, index));
+ String rest = fullName.substring(index);
+ String type = rest.replace(".local.", "");
+
+ clientInfo.mResolvedService.setServiceName(name);
+ clientInfo.mResolvedService.setServiceType(type);
+ clientInfo.mResolvedService.setPort(info.port);
+ clientInfo.mResolvedService.setTxtRecords(info.txtRecord);
+ // Network will be added after SERVICE_GET_ADDR_SUCCESS
+
+ stopResolveService(id);
+ removeRequestMap(clientId, id, clientInfo);
+
+ final int id2 = getUniqueId();
+ if (getAddrInfo(id2, info.hostname, info.interfaceIdx)) {
+ storeRequestMap(clientId, id2, clientInfo, NsdManager.RESOLVE_SERVICE);
+ } else {
+ clientInfo.onResolveServiceFailed(
+ clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+ clientInfo.mResolvedService = null;
+ }
+ break;
+ }
+ case IMDnsEventListener.SERVICE_RESOLUTION_FAILED:
+ /* NNN resolveId errorCode */
+ stopResolveService(id);
+ removeRequestMap(clientId, id, clientInfo);
+ clientInfo.mResolvedService = null;
+ clientInfo.onResolveServiceFailed(
+ clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+ break;
+ case IMDnsEventListener.SERVICE_GET_ADDR_FAILED:
+ /* NNN resolveId errorCode */
+ stopGetAddrInfo(id);
+ removeRequestMap(clientId, id, clientInfo);
+ clientInfo.mResolvedService = null;
+ clientInfo.onResolveServiceFailed(
+ clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+ break;
+ case IMDnsEventListener.SERVICE_GET_ADDR_SUCCESS: {
+ /* NNN resolveId hostname ttl addr interfaceIdx netId */
+ final GetAddressInfo info = (GetAddressInfo) obj;
+ final String address = info.address;
+ final int netId = info.netId;
+ InetAddress serviceHost = null;
+ try {
+ serviceHost = InetAddress.getByName(address);
+ } catch (UnknownHostException e) {
+ Log.wtf(TAG, "Invalid host in GET_ADDR_SUCCESS", e);
+ }
+
+ // If the resolved service is on an interface without a network, consider it
+ // as a failure: it would not be usable by apps as they would need
+ // privileged permissions.
+ if (netId != NETID_UNSET && serviceHost != null) {
+ clientInfo.mResolvedService.setHost(serviceHost);
+ setServiceNetworkForCallback(clientInfo.mResolvedService,
+ netId, info.interfaceIdx);
+ clientInfo.onResolveServiceSucceeded(
+ clientId, clientInfo.mResolvedService);
+ } else {
+ clientInfo.onResolveServiceFailed(
+ clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+ }
+ stopGetAddrInfo(id);
+ removeRequestMap(clientId, id, clientInfo);
+ clientInfo.mResolvedService = null;
+ break;
+ }
+ default:
+ return false;
+ }
+ return true;
+ }
+ }
+ }
+
+ private static void setServiceNetworkForCallback(NsdServiceInfo info, int netId, int ifaceIdx) {
+ switch (netId) {
+ case NETID_UNSET:
+ info.setNetwork(null);
+ break;
+ case INetd.LOCAL_NET_ID:
+ // Special case for LOCAL_NET_ID: Networks on netId 99 are not generally
+ // visible / usable for apps, so do not return it. Store the interface
+ // index instead, so at least if the client tries to resolve the service
+ // with that NsdServiceInfo, it will be done on the same interface.
+ // If they recreate the NsdServiceInfo themselves, resolution would be
+ // done on all interfaces as before T, which should also work.
+ info.setNetwork(null);
+ info.setInterfaceIndex(ifaceIdx);
+ break;
+ default:
+ info.setNetwork(new Network(netId));
+ }
+ }
+
+ // The full service name is escaped from standard DNS rules on mdnsresponder, making it suitable
+ // for passing to standard system DNS APIs such as res_query() . Thus, make the service name
+ // unescape for getting right service address. See "Notes on DNS Name Escaping" on
+ // external/mdnsresponder/mDNSShared/dns_sd.h for more details.
+ private String unescape(String s) {
+ StringBuilder sb = new StringBuilder(s.length());
+ for (int i = 0; i < s.length(); ++i) {
+ char c = s.charAt(i);
+ if (c == '\\') {
+ if (++i >= s.length()) {
+ Log.e(TAG, "Unexpected end of escape sequence in: " + s);
+ break;
+ }
+ c = s.charAt(i);
+ if (c != '.' && c != '\\') {
+ if (i + 2 >= s.length()) {
+ Log.e(TAG, "Unexpected end of escape sequence in: " + s);
+ break;
+ }
+ c = (char) ((c - '0') * 100 + (s.charAt(i + 1) - '0') * 10
+ + (s.charAt(i + 2) - '0'));
+ i += 2;
+ }
+ }
+ sb.append(c);
+ }
+ return sb.toString();
+ }
+
+ @VisibleForTesting
+ NsdService(Context ctx, Handler handler, long cleanupDelayMs) {
+ mCleanupDelayMs = cleanupDelayMs;
+ mContext = ctx;
+ mNsdStateMachine = new NsdStateMachine(TAG, handler);
+ mNsdStateMachine.start();
+ mMDnsManager = ctx.getSystemService(MDnsManager.class);
+ mMDnsEventCallback = new MDnsEventCallback(mNsdStateMachine);
+ }
+
+ public static NsdService create(Context context) {
+ HandlerThread thread = new HandlerThread(TAG);
+ thread.start();
+ Handler handler = new Handler(thread.getLooper());
+ NsdService service = new NsdService(context, handler, CLEANUP_DELAY_MS);
+ return service;
+ }
+
+ private static class MDnsEventCallback extends IMDnsEventListener.Stub {
+ private final StateMachine mStateMachine;
+
+ MDnsEventCallback(StateMachine sm) {
+ mStateMachine = sm;
+ }
+
+ @Override
+ public void onServiceRegistrationStatus(final RegistrationInfo status) {
+ mStateMachine.sendMessage(
+ MDNS_SERVICE_EVENT, status.result, status.id, status);
+ }
+
+ @Override
+ public void onServiceDiscoveryStatus(final DiscoveryInfo status) {
+ mStateMachine.sendMessage(
+ MDNS_SERVICE_EVENT, status.result, status.id, status);
+ }
+
+ @Override
+ public void onServiceResolutionStatus(final ResolutionInfo status) {
+ mStateMachine.sendMessage(
+ MDNS_SERVICE_EVENT, status.result, status.id, status);
+ }
+
+ @Override
+ public void onGettingServiceAddressStatus(final GetAddressInfo status) {
+ mStateMachine.sendMessage(
+ MDNS_SERVICE_EVENT, status.result, status.id, status);
+ }
+
+ @Override
+ public int getInterfaceVersion() throws RemoteException {
+ return this.VERSION;
+ }
+
+ @Override
+ public String getInterfaceHash() throws RemoteException {
+ return this.HASH;
+ }
+ }
+
+ @Override
+ public INsdServiceConnector connect(INsdManagerCallback cb) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INTERNET, "NsdService");
+ final INsdServiceConnector connector = new NsdServiceConnector();
+ mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
+ NsdManager.REGISTER_CLIENT, new Pair<>(connector, cb)));
+ return connector;
+ }
+
+ private static class ListenerArgs {
+ public final NsdServiceConnector connector;
+ public final NsdServiceInfo serviceInfo;
+ ListenerArgs(NsdServiceConnector connector, NsdServiceInfo serviceInfo) {
+ this.connector = connector;
+ this.serviceInfo = serviceInfo;
+ }
+ }
+
+ private class NsdServiceConnector extends INsdServiceConnector.Stub
+ implements IBinder.DeathRecipient {
+ @Override
+ public void registerService(int listenerKey, NsdServiceInfo serviceInfo) {
+ mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
+ NsdManager.REGISTER_SERVICE, 0, listenerKey,
+ new ListenerArgs(this, serviceInfo)));
+ }
+
+ @Override
+ public void unregisterService(int listenerKey) {
+ mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
+ NsdManager.UNREGISTER_SERVICE, 0, listenerKey,
+ new ListenerArgs(this, null)));
+ }
+
+ @Override
+ public void discoverServices(int listenerKey, NsdServiceInfo serviceInfo) {
+ mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
+ NsdManager.DISCOVER_SERVICES, 0, listenerKey,
+ new ListenerArgs(this, serviceInfo)));
+ }
+
+ @Override
+ public void stopDiscovery(int listenerKey) {
+ mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
+ NsdManager.STOP_DISCOVERY, 0, listenerKey, new ListenerArgs(this, null)));
+ }
+
+ @Override
+ public void resolveService(int listenerKey, NsdServiceInfo serviceInfo) {
+ mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
+ NsdManager.RESOLVE_SERVICE, 0, listenerKey,
+ new ListenerArgs(this, serviceInfo)));
+ }
+
+ @Override
+ public void startDaemon() {
+ mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
+ NsdManager.DAEMON_STARTUP, new ListenerArgs(this, null)));
+ }
+
+ @Override
+ public void binderDied() {
+ mNsdStateMachine.sendMessage(
+ mNsdStateMachine.obtainMessage(NsdManager.UNREGISTER_CLIENT, this));
+ }
+ }
+
+ private void sendNsdStateChangeBroadcast(boolean isEnabled) {
+ final Intent intent = new Intent(NsdManager.ACTION_NSD_STATE_CHANGED);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ int nsdState = isEnabled ? NsdManager.NSD_STATE_ENABLED : NsdManager.NSD_STATE_DISABLED;
+ intent.putExtra(NsdManager.EXTRA_NSD_STATE, nsdState);
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ private int getUniqueId() {
+ if (++mUniqueId == INVALID_ID) return ++mUniqueId;
+ return mUniqueId;
+ }
+
+ private boolean registerService(int regId, NsdServiceInfo service) {
+ if (DBG) {
+ Log.d(TAG, "registerService: " + regId + " " + service);
+ }
+ String name = service.getServiceName();
+ String type = service.getServiceType();
+ int port = service.getPort();
+ byte[] textRecord = service.getTxtRecord();
+ final int registerInterface = getNetworkInterfaceIndex(service);
+ if (service.getNetwork() != null && registerInterface == IFACE_IDX_ANY) {
+ Log.e(TAG, "Interface to register service on not found");
+ return false;
+ }
+ return mMDnsManager.registerService(regId, name, type, port, textRecord, registerInterface);
+ }
+
+ private boolean unregisterService(int regId) {
+ return mMDnsManager.stopOperation(regId);
+ }
+
+ private boolean discoverServices(int discoveryId, NsdServiceInfo serviceInfo) {
+ final String type = serviceInfo.getServiceType();
+ final int discoverInterface = getNetworkInterfaceIndex(serviceInfo);
+ if (serviceInfo.getNetwork() != null && discoverInterface == IFACE_IDX_ANY) {
+ Log.e(TAG, "Interface to discover service on not found");
+ return false;
+ }
+ return mMDnsManager.discover(discoveryId, type, discoverInterface);
+ }
+
+ private boolean stopServiceDiscovery(int discoveryId) {
+ return mMDnsManager.stopOperation(discoveryId);
+ }
+
+ private boolean resolveService(int resolveId, NsdServiceInfo service) {
+ final String name = service.getServiceName();
+ final String type = service.getServiceType();
+ final int resolveInterface = getNetworkInterfaceIndex(service);
+ if (service.getNetwork() != null && resolveInterface == IFACE_IDX_ANY) {
+ Log.e(TAG, "Interface to resolve service on not found");
+ return false;
+ }
+ return mMDnsManager.resolve(resolveId, name, type, "local.", resolveInterface);
+ }
+
+ /**
+ * Guess the interface to use to resolve or discover a service on a specific network.
+ *
+ * This is an imperfect guess, as for example the network may be gone or not yet fully
+ * registered. This is fine as failing is correct if the network is gone, and a client
+ * attempting to resolve/discover on a network not yet setup would have a bad time anyway; also
+ * this is to support the legacy mdnsresponder implementation, which historically resolved
+ * services on an unspecified network.
+ */
+ private int getNetworkInterfaceIndex(NsdServiceInfo serviceInfo) {
+ final Network network = serviceInfo.getNetwork();
+ if (network == null) {
+ // Fallback to getInterfaceIndex if present (typically if the NsdServiceInfo was
+ // provided by NsdService from discovery results, and the service was found on an
+ // interface that has no app-usable Network).
+ if (serviceInfo.getInterfaceIndex() != 0) {
+ return serviceInfo.getInterfaceIndex();
+ }
+ return IFACE_IDX_ANY;
+ }
+
+ final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
+ if (cm == null) {
+ Log.wtf(TAG, "No ConnectivityManager for resolveService");
+ return IFACE_IDX_ANY;
+ }
+ final LinkProperties lp = cm.getLinkProperties(network);
+ if (lp == null) return IFACE_IDX_ANY;
+
+ // Only resolve on non-stacked interfaces
+ final NetworkInterface iface;
+ try {
+ iface = NetworkInterface.getByName(lp.getInterfaceName());
+ } catch (SocketException e) {
+ Log.e(TAG, "Error querying interface", e);
+ return IFACE_IDX_ANY;
+ }
+
+ if (iface == null) {
+ Log.e(TAG, "Interface not found: " + lp.getInterfaceName());
+ return IFACE_IDX_ANY;
+ }
+
+ return iface.getIndex();
+ }
+
+ private boolean stopResolveService(int resolveId) {
+ return mMDnsManager.stopOperation(resolveId);
+ }
+
+ private boolean getAddrInfo(int resolveId, String hostname, int interfaceIdx) {
+ return mMDnsManager.getServiceAddress(resolveId, hostname, interfaceIdx);
+ }
+
+ private boolean stopGetAddrInfo(int resolveId) {
+ return mMDnsManager.stopOperation(resolveId);
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+ pw.println("Permission Denial: can't dump " + TAG
+ + " due to missing android.permission.DUMP permission");
+ return;
+ }
+
+ for (ClientInfo client : mClients.values()) {
+ pw.println("Client Info");
+ pw.println(client);
+ }
+
+ mNsdStateMachine.dump(fd, pw, args);
+ }
+
+ /* Information tracked per client */
+ private class ClientInfo {
+
+ private static final int MAX_LIMIT = 10;
+ private final INsdManagerCallback mCb;
+ /* Remembers a resolved service until getaddrinfo completes */
+ private NsdServiceInfo mResolvedService;
+
+ /* A map from client id to unique id sent to mDns */
+ private final SparseIntArray mClientIds = new SparseIntArray();
+
+ /* A map from client id to the type of the request we had received */
+ private final SparseIntArray mClientRequests = new SparseIntArray();
+
+ // The target SDK of this client < Build.VERSION_CODES.S
+ private boolean mIsLegacy = false;
+
+ private ClientInfo(INsdManagerCallback cb) {
+ mCb = cb;
+ if (DBG) Log.d(TAG, "New client");
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("mResolvedService ").append(mResolvedService).append("\n");
+ sb.append("mIsLegacy ").append(mIsLegacy).append("\n");
+ for(int i = 0; i< mClientIds.size(); i++) {
+ int clientID = mClientIds.keyAt(i);
+ sb.append("clientId ").append(clientID).
+ append(" mDnsId ").append(mClientIds.valueAt(i)).
+ append(" type ").append(mClientRequests.get(clientID)).append("\n");
+ }
+ return sb.toString();
+ }
+
+ private boolean isLegacy() {
+ return mIsLegacy;
+ }
+
+ private void setLegacy() {
+ mIsLegacy = true;
+ }
+
+ // Remove any pending requests from the global map when we get rid of a client,
+ // and send cancellations to the daemon.
+ private void expungeAllRequests() {
+ int globalId, clientId, i;
+ // TODO: to keep handler responsive, do not clean all requests for that client at once.
+ for (i = 0; i < mClientIds.size(); i++) {
+ clientId = mClientIds.keyAt(i);
+ globalId = mClientIds.valueAt(i);
+ mIdToClientInfoMap.remove(globalId);
+ if (DBG) {
+ Log.d(TAG, "Terminating client-ID " + clientId
+ + " global-ID " + globalId + " type " + mClientRequests.get(clientId));
+ }
+ switch (mClientRequests.get(clientId)) {
+ case NsdManager.DISCOVER_SERVICES:
+ stopServiceDiscovery(globalId);
+ break;
+ case NsdManager.RESOLVE_SERVICE:
+ stopResolveService(globalId);
+ break;
+ case NsdManager.REGISTER_SERVICE:
+ unregisterService(globalId);
+ break;
+ default:
+ break;
+ }
+ }
+ mClientIds.clear();
+ mClientRequests.clear();
+ }
+
+ // mClientIds is a sparse array of listener id -> mDnsClient id. For a given mDnsClient id,
+ // return the corresponding listener id. mDnsClient id is also called a global id.
+ private int getClientId(final int globalId) {
+ int idx = mClientIds.indexOfValue(globalId);
+ if (idx < 0) {
+ return idx;
+ }
+ return mClientIds.keyAt(idx);
+ }
+
+ void onDiscoverServicesStarted(int listenerKey, NsdServiceInfo info) {
+ try {
+ mCb.onDiscoverServicesStarted(listenerKey, info);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling onDiscoverServicesStarted", e);
+ }
+ }
+
+ void onDiscoverServicesFailed(int listenerKey, int error) {
+ try {
+ mCb.onDiscoverServicesFailed(listenerKey, error);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling onDiscoverServicesFailed", e);
+ }
+ }
+
+ void onServiceFound(int listenerKey, NsdServiceInfo info) {
+ try {
+ mCb.onServiceFound(listenerKey, info);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling onServiceFound(", e);
+ }
+ }
+
+ void onServiceLost(int listenerKey, NsdServiceInfo info) {
+ try {
+ mCb.onServiceLost(listenerKey, info);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling onServiceLost(", e);
+ }
+ }
+
+ void onStopDiscoveryFailed(int listenerKey, int error) {
+ try {
+ mCb.onStopDiscoveryFailed(listenerKey, error);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling onStopDiscoveryFailed", e);
+ }
+ }
+
+ void onStopDiscoverySucceeded(int listenerKey) {
+ try {
+ mCb.onStopDiscoverySucceeded(listenerKey);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling onStopDiscoverySucceeded", e);
+ }
+ }
+
+ void onRegisterServiceFailed(int listenerKey, int error) {
+ try {
+ mCb.onRegisterServiceFailed(listenerKey, error);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling onRegisterServiceFailed", e);
+ }
+ }
+
+ void onRegisterServiceSucceeded(int listenerKey, NsdServiceInfo info) {
+ try {
+ mCb.onRegisterServiceSucceeded(listenerKey, info);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling onRegisterServiceSucceeded", e);
+ }
+ }
+
+ void onUnregisterServiceFailed(int listenerKey, int error) {
+ try {
+ mCb.onUnregisterServiceFailed(listenerKey, error);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling onUnregisterServiceFailed", e);
+ }
+ }
+
+ void onUnregisterServiceSucceeded(int listenerKey) {
+ try {
+ mCb.onUnregisterServiceSucceeded(listenerKey);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling onUnregisterServiceSucceeded", e);
+ }
+ }
+
+ void onResolveServiceFailed(int listenerKey, int error) {
+ try {
+ mCb.onResolveServiceFailed(listenerKey, error);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling onResolveServiceFailed", e);
+ }
+ }
+
+ void onResolveServiceSucceeded(int listenerKey, NsdServiceInfo info) {
+ try {
+ mCb.onResolveServiceSucceeded(listenerKey, info);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling onResolveServiceSucceeded", e);
+ }
+ }
+ }
+}
diff --git a/service-t/src/com/android/server/ethernet/EthernetConfigStore.java b/service-t/src/com/android/server/ethernet/EthernetConfigStore.java
new file mode 100644
index 0000000..6006539
--- /dev/null
+++ b/service-t/src/com/android/server/ethernet/EthernetConfigStore.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.ethernet;
+
+import static com.android.net.module.util.DeviceConfigUtils.TETHERING_MODULE_NAME;
+
+import android.annotation.Nullable;
+import android.content.ApexEnvironment;
+import android.net.IpConfiguration;
+import android.os.Environment;
+import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.net.IpConfigStore;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * This class provides an API to store and manage Ethernet network configuration.
+ */
+public class EthernetConfigStore {
+ private static final String TAG = EthernetConfigStore.class.getSimpleName();
+ private static final String CONFIG_FILE = "ipconfig.txt";
+ private static final String FILE_PATH = "/misc/ethernet/";
+ private static final String LEGACY_IP_CONFIG_FILE_PATH = Environment.getDataDirectory()
+ + FILE_PATH;
+ private static final String APEX_IP_CONFIG_FILE_PATH = ApexEnvironment.getApexEnvironment(
+ TETHERING_MODULE_NAME).getDeviceProtectedDataDir() + FILE_PATH;
+
+ private IpConfigStore mStore = new IpConfigStore();
+ private final ArrayMap<String, IpConfiguration> mIpConfigurations;
+ private IpConfiguration mIpConfigurationForDefaultInterface;
+ private final Object mSync = new Object();
+
+ public EthernetConfigStore() {
+ mIpConfigurations = new ArrayMap<>(0);
+ }
+
+ private static boolean doesConfigFileExist(final String filepath) {
+ return new File(filepath).exists();
+ }
+
+ private void writeLegacyIpConfigToApexPath(final String newFilePath, final String oldFilePath,
+ final String filename) {
+ final File directory = new File(newFilePath);
+ if (!directory.exists()) {
+ directory.mkdirs();
+ }
+
+ // Write the legacy IP config to the apex file path.
+ FileOutputStream fos = null;
+ final AtomicFile dst = new AtomicFile(new File(newFilePath + filename));
+ final AtomicFile src = new AtomicFile(new File(oldFilePath + filename));
+ try {
+ final byte[] raw = src.readFully();
+ if (raw.length > 0) {
+ fos = dst.startWrite();
+ fos.write(raw);
+ fos.flush();
+ dst.finishWrite(fos);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Fail to sync the legacy IP config to the apex file path.");
+ dst.failWrite(fos);
+ }
+ }
+
+ public void read() {
+ read(APEX_IP_CONFIG_FILE_PATH, LEGACY_IP_CONFIG_FILE_PATH, CONFIG_FILE);
+ }
+
+ @VisibleForTesting
+ void read(final String newFilePath, final String oldFilePath, final String filename) {
+ synchronized (mSync) {
+ // Attempt to read the IP configuration from apex file path first.
+ if (doesConfigFileExist(newFilePath + filename)) {
+ loadConfigFileLocked(newFilePath + filename);
+ return;
+ }
+
+ // If the config file doesn't exist in the apex file path, attempt to read it from
+ // the legacy file path, if config file exists, write the legacy IP configuration to
+ // apex config file path, this should just happen on the first boot. New or updated
+ // config entries are only written to the apex config file later.
+ if (!doesConfigFileExist(oldFilePath + filename)) return;
+ loadConfigFileLocked(oldFilePath + filename);
+ writeLegacyIpConfigToApexPath(newFilePath, oldFilePath, filename);
+ }
+ }
+
+ private void loadConfigFileLocked(final String filepath) {
+ final ArrayMap<String, IpConfiguration> configs =
+ IpConfigStore.readIpConfigurations(filepath);
+ mIpConfigurations.putAll(configs);
+ }
+
+ public void write(String iface, IpConfiguration config) {
+ write(iface, config, APEX_IP_CONFIG_FILE_PATH + CONFIG_FILE);
+ }
+
+ @VisibleForTesting
+ void write(String iface, IpConfiguration config, String filepath) {
+ boolean modified;
+
+ synchronized (mSync) {
+ if (config == null) {
+ modified = mIpConfigurations.remove(iface) != null;
+ } else {
+ IpConfiguration oldConfig = mIpConfigurations.put(iface, config);
+ modified = !config.equals(oldConfig);
+ }
+
+ if (modified) {
+ mStore.writeIpConfigurations(filepath, mIpConfigurations);
+ }
+ }
+ }
+
+ public ArrayMap<String, IpConfiguration> getIpConfigurations() {
+ synchronized (mSync) {
+ return new ArrayMap<>(mIpConfigurations);
+ }
+ }
+
+ @Nullable
+ public IpConfiguration getIpConfigurationForDefaultInterface() {
+ return null;
+ }
+}
diff --git a/service-t/src/com/android/server/ethernet/EthernetNetworkAgent.java b/service-t/src/com/android/server/ethernet/EthernetNetworkAgent.java
new file mode 100644
index 0000000..57fbce7
--- /dev/null
+++ b/service-t/src/com/android/server/ethernet/EthernetNetworkAgent.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.ethernet;
+
+import android.content.Context;
+import android.net.LinkProperties;
+import android.net.NetworkAgent;
+import android.net.NetworkAgentConfig;
+import android.net.NetworkCapabilities;
+import android.net.NetworkProvider;
+import android.net.NetworkScore;
+import android.os.Looper;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+public class EthernetNetworkAgent extends NetworkAgent {
+
+ private static final String TAG = "EthernetNetworkAgent";
+
+ public interface Callbacks {
+ void onNetworkUnwanted();
+ }
+
+ private final Callbacks mCallbacks;
+
+ EthernetNetworkAgent(
+ @NonNull Context context,
+ @NonNull Looper looper,
+ @NonNull NetworkCapabilities nc,
+ @NonNull LinkProperties lp,
+ @NonNull NetworkAgentConfig config,
+ @Nullable NetworkProvider provider,
+ @NonNull Callbacks cb) {
+ super(context, looper, TAG, nc, lp, new NetworkScore.Builder().build(), config, provider);
+ mCallbacks = cb;
+ }
+
+ @Override
+ public void onNetworkUnwanted() {
+ mCallbacks.onNetworkUnwanted();
+ }
+
+ // sendLinkProperties is final in NetworkAgent, so it cannot be mocked.
+ public void sendLinkPropertiesImpl(LinkProperties lp) {
+ sendLinkProperties(lp);
+ }
+
+ public Callbacks getCallbacks() {
+ return mCallbacks;
+ }
+}
diff --git a/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java b/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
new file mode 100644
index 0000000..79802fb
--- /dev/null
+++ b/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
@@ -0,0 +1,735 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.ethernet;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityResources;
+import android.net.EthernetManager;
+import android.net.EthernetNetworkManagementException;
+import android.net.EthernetNetworkSpecifier;
+import android.net.INetworkInterfaceOutcomeReceiver;
+import android.net.IpConfiguration;
+import android.net.IpConfiguration.IpAssignment;
+import android.net.IpConfiguration.ProxySettings;
+import android.net.LinkProperties;
+import android.net.NetworkAgentConfig;
+import android.net.NetworkCapabilities;
+import android.net.NetworkProvider;
+import android.net.NetworkRequest;
+import android.net.NetworkScore;
+import android.net.ip.IIpClient;
+import android.net.ip.IpClientCallbacks;
+import android.net.ip.IpClientManager;
+import android.net.ip.IpClientUtil;
+import android.net.shared.ProvisioningConfiguration;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.AndroidRuntimeException;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.connectivity.resources.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.net.module.util.InterfaceParams;
+
+import java.io.FileDescriptor;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * {@link NetworkProvider} that manages NetworkOffers for Ethernet networks.
+ */
+public class EthernetNetworkFactory {
+ private final static String TAG = EthernetNetworkFactory.class.getSimpleName();
+ final static boolean DBG = true;
+
+ private static final String NETWORK_TYPE = "Ethernet";
+
+ private final ConcurrentHashMap<String, NetworkInterfaceState> mTrackingInterfaces =
+ new ConcurrentHashMap<>();
+ private final Handler mHandler;
+ private final Context mContext;
+ private final NetworkProvider mProvider;
+ final Dependencies mDeps;
+
+ public static class Dependencies {
+ public void makeIpClient(Context context, String iface, IpClientCallbacks callbacks) {
+ IpClientUtil.makeIpClient(context, iface, callbacks);
+ }
+
+ public IpClientManager makeIpClientManager(@NonNull final IIpClient ipClient) {
+ return new IpClientManager(ipClient, TAG);
+ }
+
+ public EthernetNetworkAgent makeEthernetNetworkAgent(Context context, Looper looper,
+ NetworkCapabilities nc, LinkProperties lp, NetworkAgentConfig config,
+ NetworkProvider provider, EthernetNetworkAgent.Callbacks cb) {
+ return new EthernetNetworkAgent(context, looper, nc, lp, config, provider, cb);
+ }
+
+ public InterfaceParams getNetworkInterfaceByName(String name) {
+ return InterfaceParams.getByName(name);
+ }
+
+ public String getTcpBufferSizesFromResource(Context context) {
+ final ConnectivityResources resources = new ConnectivityResources(context);
+ return resources.get().getString(R.string.config_ethernet_tcp_buffers);
+ }
+ }
+
+ public static class ConfigurationException extends AndroidRuntimeException {
+ public ConfigurationException(String msg) {
+ super(msg);
+ }
+ }
+
+ public EthernetNetworkFactory(Handler handler, Context context) {
+ this(handler, context, new NetworkProvider(context, handler.getLooper(), TAG),
+ new Dependencies());
+ }
+
+ @VisibleForTesting
+ EthernetNetworkFactory(Handler handler, Context context, NetworkProvider provider,
+ Dependencies deps) {
+ mHandler = handler;
+ mContext = context;
+ mProvider = provider;
+ mDeps = deps;
+ }
+
+ /**
+ * Registers the network provider with the system.
+ */
+ public void register() {
+ mContext.getSystemService(ConnectivityManager.class).registerNetworkProvider(mProvider);
+ }
+
+ /**
+ * Returns an array of available interface names. The array is sorted: unrestricted interfaces
+ * goes first, then sorted by name.
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ protected String[] getAvailableInterfaces(boolean includeRestricted) {
+ return mTrackingInterfaces.values()
+ .stream()
+ .filter(iface -> !iface.isRestricted() || includeRestricted)
+ .sorted((iface1, iface2) -> {
+ int r = Boolean.compare(iface1.isRestricted(), iface2.isRestricted());
+ return r == 0 ? iface1.name.compareTo(iface2.name) : r;
+ })
+ .map(iface -> iface.name)
+ .toArray(String[]::new);
+ }
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ protected void addInterface(@NonNull final String ifaceName, @NonNull final String hwAddress,
+ @NonNull final IpConfiguration ipConfig,
+ @NonNull final NetworkCapabilities capabilities) {
+ if (mTrackingInterfaces.containsKey(ifaceName)) {
+ Log.e(TAG, "Interface with name " + ifaceName + " already exists.");
+ return;
+ }
+
+ final NetworkCapabilities nc = new NetworkCapabilities.Builder(capabilities)
+ .setNetworkSpecifier(new EthernetNetworkSpecifier(ifaceName))
+ .build();
+
+ if (DBG) {
+ Log.d(TAG, "addInterface, iface: " + ifaceName + ", capabilities: " + nc);
+ }
+
+ final NetworkInterfaceState iface = new NetworkInterfaceState(
+ ifaceName, hwAddress, mHandler, mContext, ipConfig, nc, mProvider, mDeps);
+ mTrackingInterfaces.put(ifaceName, iface);
+ }
+
+ @VisibleForTesting
+ protected int getInterfaceState(@NonNull String iface) {
+ final NetworkInterfaceState interfaceState = mTrackingInterfaces.get(iface);
+ if (interfaceState == null) {
+ return EthernetManager.STATE_ABSENT;
+ } else if (!interfaceState.mLinkUp) {
+ return EthernetManager.STATE_LINK_DOWN;
+ } else {
+ return EthernetManager.STATE_LINK_UP;
+ }
+ }
+
+ /**
+ * Update a network's configuration and restart it if necessary.
+ *
+ * @param ifaceName the interface name of the network to be updated.
+ * @param ipConfig the desired {@link IpConfiguration} for the given network or null. If
+ * {@code null} is passed, the existing IpConfiguration is not updated.
+ * @param capabilities the desired {@link NetworkCapabilities} for the given network. If
+ * {@code null} is passed, then the network's current
+ * {@link NetworkCapabilities} will be used in support of existing APIs as
+ * the public API does not allow this.
+ * @param listener an optional {@link INetworkInterfaceOutcomeReceiver} to notify callers of
+ * completion.
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ protected void updateInterface(@NonNull final String ifaceName,
+ @Nullable final IpConfiguration ipConfig,
+ @Nullable final NetworkCapabilities capabilities,
+ @Nullable final INetworkInterfaceOutcomeReceiver listener) {
+ if (!hasInterface(ifaceName)) {
+ maybeSendNetworkManagementCallbackForUntracked(ifaceName, listener);
+ return;
+ }
+
+ final NetworkInterfaceState iface = mTrackingInterfaces.get(ifaceName);
+ iface.updateInterface(ipConfig, capabilities, listener);
+ mTrackingInterfaces.put(ifaceName, iface);
+ }
+
+ private static NetworkCapabilities mixInCapabilities(NetworkCapabilities nc,
+ NetworkCapabilities addedNc) {
+ final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder(nc);
+ for (int transport : addedNc.getTransportTypes()) builder.addTransportType(transport);
+ for (int capability : addedNc.getCapabilities()) builder.addCapability(capability);
+ return builder.build();
+ }
+
+ private static NetworkCapabilities createDefaultNetworkCapabilities() {
+ return NetworkCapabilities.Builder
+ .withoutDefaultCapabilities()
+ .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET).build();
+ }
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ protected void removeInterface(String interfaceName) {
+ NetworkInterfaceState iface = mTrackingInterfaces.remove(interfaceName);
+ if (iface != null) {
+ iface.destroy();
+ }
+ }
+
+ /** Returns true if state has been modified */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ protected boolean updateInterfaceLinkState(@NonNull final String ifaceName, final boolean up,
+ @Nullable final INetworkInterfaceOutcomeReceiver listener) {
+ if (!hasInterface(ifaceName)) {
+ maybeSendNetworkManagementCallbackForUntracked(ifaceName, listener);
+ return false;
+ }
+
+ if (DBG) {
+ Log.d(TAG, "updateInterfaceLinkState, iface: " + ifaceName + ", up: " + up);
+ }
+
+ NetworkInterfaceState iface = mTrackingInterfaces.get(ifaceName);
+ return iface.updateLinkState(up, listener);
+ }
+
+ private void maybeSendNetworkManagementCallbackForUntracked(
+ String ifaceName, INetworkInterfaceOutcomeReceiver listener) {
+ maybeSendNetworkManagementCallback(listener, null,
+ new EthernetNetworkManagementException(
+ ifaceName + " can't be updated as it is not available."));
+ }
+
+ @VisibleForTesting
+ protected boolean hasInterface(String ifaceName) {
+ return mTrackingInterfaces.containsKey(ifaceName);
+ }
+
+ private static void maybeSendNetworkManagementCallback(
+ @Nullable final INetworkInterfaceOutcomeReceiver listener,
+ @Nullable final String iface,
+ @Nullable final EthernetNetworkManagementException e) {
+ if (null == listener) {
+ return;
+ }
+
+ try {
+ if (iface != null) {
+ listener.onResult(iface);
+ } else {
+ listener.onError(e);
+ }
+ } catch (RemoteException re) {
+ Log.e(TAG, "Can't send onComplete for network management callback", re);
+ }
+ }
+
+ @VisibleForTesting
+ static class NetworkInterfaceState {
+ final String name;
+
+ private final String mHwAddress;
+ private final Handler mHandler;
+ private final Context mContext;
+ private final NetworkProvider mNetworkProvider;
+ private final Dependencies mDeps;
+ private final NetworkProvider.NetworkOfferCallback mNetworkOfferCallback;
+
+ private static String sTcpBufferSizes = null; // Lazy initialized.
+
+ private boolean mLinkUp;
+ private int mLegacyType;
+ private LinkProperties mLinkProperties = new LinkProperties();
+ private Set<NetworkRequest> mRequests = new ArraySet<>();
+
+ private volatile @Nullable IpClientManager mIpClient;
+ private @NonNull NetworkCapabilities mCapabilities;
+ private @Nullable EthernetIpClientCallback mIpClientCallback;
+ private @Nullable EthernetNetworkAgent mNetworkAgent;
+ private @Nullable IpConfiguration mIpConfig;
+
+ /**
+ * A map of TRANSPORT_* types to legacy transport types available for each type an ethernet
+ * interface could propagate.
+ *
+ * There are no legacy type equivalents to LOWPAN or WIFI_AWARE. These types are set to
+ * TYPE_NONE to match the behavior of their own network factories.
+ */
+ private static final SparseArray<Integer> sTransports = new SparseArray();
+ static {
+ sTransports.put(NetworkCapabilities.TRANSPORT_ETHERNET,
+ ConnectivityManager.TYPE_ETHERNET);
+ sTransports.put(NetworkCapabilities.TRANSPORT_BLUETOOTH,
+ ConnectivityManager.TYPE_BLUETOOTH);
+ sTransports.put(NetworkCapabilities.TRANSPORT_WIFI, ConnectivityManager.TYPE_WIFI);
+ sTransports.put(NetworkCapabilities.TRANSPORT_CELLULAR,
+ ConnectivityManager.TYPE_MOBILE);
+ sTransports.put(NetworkCapabilities.TRANSPORT_LOWPAN, ConnectivityManager.TYPE_NONE);
+ sTransports.put(NetworkCapabilities.TRANSPORT_WIFI_AWARE,
+ ConnectivityManager.TYPE_NONE);
+ }
+
+ private class EthernetIpClientCallback extends IpClientCallbacks {
+ private final ConditionVariable mIpClientStartCv = new ConditionVariable(false);
+ private final ConditionVariable mIpClientShutdownCv = new ConditionVariable(false);
+ @Nullable INetworkInterfaceOutcomeReceiver mNetworkManagementListener;
+
+ EthernetIpClientCallback(@Nullable final INetworkInterfaceOutcomeReceiver listener) {
+ mNetworkManagementListener = listener;
+ }
+
+ @Override
+ public void onIpClientCreated(IIpClient ipClient) {
+ mIpClient = mDeps.makeIpClientManager(ipClient);
+ mIpClientStartCv.open();
+ }
+
+ private void awaitIpClientStart() {
+ mIpClientStartCv.block();
+ }
+
+ private void awaitIpClientShutdown() {
+ mIpClientShutdownCv.block();
+ }
+
+ // At the time IpClient is stopped, an IpClient event may have already been posted on
+ // the back of the handler and is awaiting execution. Once that event is executed, the
+ // associated callback object may not be valid anymore
+ // (NetworkInterfaceState#mIpClientCallback points to a different object / null).
+ private boolean isCurrentCallback() {
+ return this == mIpClientCallback;
+ }
+
+ private void handleIpEvent(final @NonNull Runnable r) {
+ mHandler.post(() -> {
+ if (!isCurrentCallback()) {
+ Log.i(TAG, "Ignoring stale IpClientCallbacks " + this);
+ return;
+ }
+ r.run();
+ });
+ }
+
+ @Override
+ public void onProvisioningSuccess(LinkProperties newLp) {
+ handleIpEvent(() -> onIpLayerStarted(newLp, mNetworkManagementListener));
+ }
+
+ @Override
+ public void onProvisioningFailure(LinkProperties newLp) {
+ // This cannot happen due to provisioning timeout, because our timeout is 0. It can
+ // happen due to errors while provisioning or on provisioning loss.
+ handleIpEvent(() -> onIpLayerStopped(mNetworkManagementListener));
+ }
+
+ @Override
+ public void onLinkPropertiesChange(LinkProperties newLp) {
+ handleIpEvent(() -> updateLinkProperties(newLp));
+ }
+
+ @Override
+ public void onReachabilityLost(String logMsg) {
+ handleIpEvent(() -> updateNeighborLostEvent(logMsg));
+ }
+
+ @Override
+ public void onQuit() {
+ mIpClient = null;
+ mIpClientShutdownCv.open();
+ }
+ }
+
+ private class EthernetNetworkOfferCallback implements NetworkProvider.NetworkOfferCallback {
+ @Override
+ public void onNetworkNeeded(@NonNull NetworkRequest request) {
+ if (DBG) {
+ Log.d(TAG, String.format("%s: onNetworkNeeded for request: %s", name, request));
+ }
+ // When the network offer is first registered, onNetworkNeeded is called with all
+ // existing requests.
+ // ConnectivityService filters requests for us based on the NetworkCapabilities
+ // passed in the registerNetworkOffer() call.
+ mRequests.add(request);
+ // if the network is already started, this is a no-op.
+ start();
+ }
+
+ @Override
+ public void onNetworkUnneeded(@NonNull NetworkRequest request) {
+ if (DBG) {
+ Log.d(TAG,
+ String.format("%s: onNetworkUnneeded for request: %s", name, request));
+ }
+ mRequests.remove(request);
+ if (mRequests.isEmpty()) {
+ // not currently serving any requests, stop the network.
+ stop();
+ }
+ }
+ }
+
+ NetworkInterfaceState(String ifaceName, String hwAddress, Handler handler, Context context,
+ @NonNull IpConfiguration ipConfig, @NonNull NetworkCapabilities capabilities,
+ NetworkProvider networkProvider, Dependencies deps) {
+ name = ifaceName;
+ mIpConfig = Objects.requireNonNull(ipConfig);
+ mCapabilities = Objects.requireNonNull(capabilities);
+ mLegacyType = getLegacyType(mCapabilities);
+ mHandler = handler;
+ mContext = context;
+ mNetworkProvider = networkProvider;
+ mDeps = deps;
+ mNetworkOfferCallback = new EthernetNetworkOfferCallback();
+ mHwAddress = hwAddress;
+ }
+
+ /**
+ * Determines the legacy transport type from a NetworkCapabilities transport type. Defaults
+ * to legacy TYPE_NONE if there is no known conversion
+ */
+ private static int getLegacyType(int transport) {
+ return sTransports.get(transport, ConnectivityManager.TYPE_NONE);
+ }
+
+ private static int getLegacyType(@NonNull final NetworkCapabilities capabilities) {
+ final int[] transportTypes = capabilities.getTransportTypes();
+ if (transportTypes.length > 0) {
+ return getLegacyType(transportTypes[0]);
+ }
+
+ // Should never happen as transport is always one of ETHERNET or a valid override
+ throw new ConfigurationException("Network Capabilities do not have an associated "
+ + "transport type.");
+ }
+
+ private static NetworkScore getBestNetworkScore() {
+ return new NetworkScore.Builder().build();
+ }
+
+ private void setCapabilities(@NonNull final NetworkCapabilities capabilities) {
+ mCapabilities = new NetworkCapabilities(capabilities);
+ mLegacyType = getLegacyType(mCapabilities);
+
+ if (mLinkUp) {
+ // registering a new network offer will update the existing one, not install a
+ // new one.
+ mNetworkProvider.registerNetworkOffer(getBestNetworkScore(),
+ new NetworkCapabilities(capabilities), cmd -> mHandler.post(cmd),
+ mNetworkOfferCallback);
+ }
+ }
+
+ void updateInterface(@Nullable final IpConfiguration ipConfig,
+ @Nullable final NetworkCapabilities capabilities,
+ @Nullable final INetworkInterfaceOutcomeReceiver listener) {
+ if (DBG) {
+ Log.d(TAG, "updateInterface, iface: " + name
+ + ", ipConfig: " + ipConfig + ", old ipConfig: " + mIpConfig
+ + ", capabilities: " + capabilities + ", old capabilities: " + mCapabilities
+ + ", listener: " + listener
+ );
+ }
+
+ if (null != ipConfig){
+ mIpConfig = ipConfig;
+ }
+ if (null != capabilities) {
+ setCapabilities(capabilities);
+ }
+ // Send an abort callback if a request is filed before the previous one has completed.
+ maybeSendNetworkManagementCallbackForAbort();
+ // TODO: Update this logic to only do a restart if required. Although a restart may
+ // be required due to the capabilities or ipConfiguration values, not all
+ // capabilities changes require a restart.
+ restart(listener);
+ }
+
+ boolean isRestricted() {
+ return !mCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+ }
+
+ private void start() {
+ start(null);
+ }
+
+ private void start(@Nullable final INetworkInterfaceOutcomeReceiver listener) {
+ if (mIpClient != null) {
+ if (DBG) Log.d(TAG, "IpClient already started");
+ return;
+ }
+ if (DBG) {
+ Log.d(TAG, String.format("Starting Ethernet IpClient(%s)", name));
+ }
+
+ mIpClientCallback = new EthernetIpClientCallback(listener);
+ mDeps.makeIpClient(mContext, name, mIpClientCallback);
+ mIpClientCallback.awaitIpClientStart();
+
+ if (sTcpBufferSizes == null) {
+ sTcpBufferSizes = mDeps.getTcpBufferSizesFromResource(mContext);
+ }
+ provisionIpClient(mIpClient, mIpConfig, sTcpBufferSizes);
+ }
+
+ void onIpLayerStarted(@NonNull final LinkProperties linkProperties,
+ @Nullable final INetworkInterfaceOutcomeReceiver listener) {
+ if (mNetworkAgent != null) {
+ Log.e(TAG, "Already have a NetworkAgent - aborting new request");
+ stop();
+ return;
+ }
+ mLinkProperties = linkProperties;
+
+ // Create our NetworkAgent.
+ final NetworkAgentConfig config = new NetworkAgentConfig.Builder()
+ .setLegacyType(mLegacyType)
+ .setLegacyTypeName(NETWORK_TYPE)
+ .setLegacyExtraInfo(mHwAddress)
+ .build();
+ mNetworkAgent = mDeps.makeEthernetNetworkAgent(mContext, mHandler.getLooper(),
+ mCapabilities, mLinkProperties, config, mNetworkProvider,
+ new EthernetNetworkAgent.Callbacks() {
+ @Override
+ public void onNetworkUnwanted() {
+ // if mNetworkAgent is null, we have already called stop.
+ if (mNetworkAgent == null) return;
+
+ if (this == mNetworkAgent.getCallbacks()) {
+ stop();
+ } else {
+ Log.d(TAG, "Ignoring unwanted as we have a more modern " +
+ "instance");
+ }
+ }
+ });
+ mNetworkAgent.register();
+ mNetworkAgent.markConnected();
+ realizeNetworkManagementCallback(name, null);
+ }
+
+ void onIpLayerStopped(@Nullable final INetworkInterfaceOutcomeReceiver listener) {
+ // There is no point in continuing if the interface is gone as stop() will be triggered
+ // by removeInterface() when processed on the handler thread and start() won't
+ // work for a non-existent interface.
+ if (null == mDeps.getNetworkInterfaceByName(name)) {
+ if (DBG) Log.d(TAG, name + " is no longer available.");
+ // Send a callback in case a provisioning request was in progress.
+ maybeSendNetworkManagementCallbackForAbort();
+ return;
+ }
+ restart(listener);
+ }
+
+ private void maybeSendNetworkManagementCallbackForAbort() {
+ realizeNetworkManagementCallback(null,
+ new EthernetNetworkManagementException(
+ "The IP provisioning request has been aborted."));
+ }
+
+ // Must be called on the handler thread
+ private void realizeNetworkManagementCallback(@Nullable final String iface,
+ @Nullable final EthernetNetworkManagementException e) {
+ ensureRunningOnEthernetHandlerThread();
+ if (null == mIpClientCallback) {
+ return;
+ }
+
+ EthernetNetworkFactory.maybeSendNetworkManagementCallback(
+ mIpClientCallback.mNetworkManagementListener, iface, e);
+ // Only send a single callback per listener.
+ mIpClientCallback.mNetworkManagementListener = null;
+ }
+
+ private void ensureRunningOnEthernetHandlerThread() {
+ if (mHandler.getLooper().getThread() != Thread.currentThread()) {
+ throw new IllegalStateException(
+ "Not running on the Ethernet thread: "
+ + Thread.currentThread().getName());
+ }
+ }
+
+ void updateLinkProperties(LinkProperties linkProperties) {
+ mLinkProperties = linkProperties;
+ if (mNetworkAgent != null) {
+ mNetworkAgent.sendLinkPropertiesImpl(linkProperties);
+ }
+ }
+
+ void updateNeighborLostEvent(String logMsg) {
+ Log.i(TAG, "updateNeighborLostEvent " + logMsg);
+ // Reachability lost will be seen only if the gateway is not reachable.
+ // Since ethernet FW doesn't have the mechanism to scan for new networks
+ // like WiFi, simply restart.
+ // If there is a better network, that will become default and apps
+ // will be able to use internet. If ethernet gets connected again,
+ // and has backhaul connectivity, it will become default.
+ restart();
+ }
+
+ /** Returns true if state has been modified */
+ boolean updateLinkState(final boolean up,
+ @Nullable final INetworkInterfaceOutcomeReceiver listener) {
+ if (mLinkUp == up) {
+ EthernetNetworkFactory.maybeSendNetworkManagementCallback(listener, null,
+ new EthernetNetworkManagementException(
+ "No changes with requested link state " + up + " for " + name));
+ return false;
+ }
+ mLinkUp = up;
+
+ if (!up) { // was up, goes down
+ // retract network offer and stop IpClient.
+ destroy();
+ // If only setting the interface down, send a callback to signal completion.
+ EthernetNetworkFactory.maybeSendNetworkManagementCallback(listener, name, null);
+ } else { // was down, goes up
+ // register network offer
+ mNetworkProvider.registerNetworkOffer(getBestNetworkScore(),
+ new NetworkCapabilities(mCapabilities), (cmd) -> mHandler.post(cmd),
+ mNetworkOfferCallback);
+ }
+
+ return true;
+ }
+
+ private void stop() {
+ // Invalidate all previous start requests
+ if (mIpClient != null) {
+ mIpClient.shutdown();
+ mIpClientCallback.awaitIpClientShutdown();
+ mIpClient = null;
+ }
+ mIpClientCallback = null;
+
+ if (mNetworkAgent != null) {
+ mNetworkAgent.unregister();
+ mNetworkAgent = null;
+ }
+ mLinkProperties.clear();
+ }
+
+ public void destroy() {
+ mNetworkProvider.unregisterNetworkOffer(mNetworkOfferCallback);
+ maybeSendNetworkManagementCallbackForAbort();
+ stop();
+ mRequests.clear();
+ }
+
+ private static void provisionIpClient(@NonNull final IpClientManager ipClient,
+ @NonNull final IpConfiguration config, @NonNull final String tcpBufferSizes) {
+ if (config.getProxySettings() == ProxySettings.STATIC ||
+ config.getProxySettings() == ProxySettings.PAC) {
+ ipClient.setHttpProxy(config.getHttpProxy());
+ }
+
+ if (!TextUtils.isEmpty(tcpBufferSizes)) {
+ ipClient.setTcpBufferSizes(tcpBufferSizes);
+ }
+
+ ipClient.startProvisioning(createProvisioningConfiguration(config));
+ }
+
+ private static ProvisioningConfiguration createProvisioningConfiguration(
+ @NonNull final IpConfiguration config) {
+ if (config.getIpAssignment() == IpAssignment.STATIC) {
+ return new ProvisioningConfiguration.Builder()
+ .withStaticConfiguration(config.getStaticIpConfiguration())
+ .build();
+ }
+ return new ProvisioningConfiguration.Builder()
+ .withProvisioningTimeoutMs(0)
+ .build();
+ }
+
+ void restart() {
+ restart(null);
+ }
+
+ void restart(@Nullable final INetworkInterfaceOutcomeReceiver listener) {
+ if (DBG) Log.d(TAG, "reconnecting Ethernet");
+ stop();
+ start(listener);
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + "{ "
+ + "iface: " + name + ", "
+ + "up: " + mLinkUp + ", "
+ + "hwAddress: " + mHwAddress + ", "
+ + "networkCapabilities: " + mCapabilities + ", "
+ + "networkAgent: " + mNetworkAgent + ", "
+ + "ipClient: " + mIpClient + ","
+ + "linkProperties: " + mLinkProperties
+ + "}";
+ }
+ }
+
+ void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
+ pw.println(getClass().getSimpleName());
+ pw.println("Tracking interfaces:");
+ pw.increaseIndent();
+ for (String iface: mTrackingInterfaces.keySet()) {
+ NetworkInterfaceState ifaceState = mTrackingInterfaces.get(iface);
+ pw.println(iface + ":" + ifaceState);
+ pw.increaseIndent();
+ if (null == ifaceState.mIpClient) {
+ pw.println("IpClient is null");
+ }
+ pw.decreaseIndent();
+ }
+ pw.decreaseIndent();
+ }
+}
diff --git a/service-t/src/com/android/server/ethernet/EthernetService.java b/service-t/src/com/android/server/ethernet/EthernetService.java
new file mode 100644
index 0000000..d405fd5
--- /dev/null
+++ b/service-t/src/com/android/server/ethernet/EthernetService.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.ethernet;
+
+import android.content.Context;
+import android.net.INetd;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+
+import java.util.Objects;
+
+// TODO: consider renaming EthernetServiceImpl to EthernetService and deleting this file.
+public final class EthernetService {
+ private static final String TAG = "EthernetService";
+ private static final String THREAD_NAME = "EthernetServiceThread";
+
+ private static INetd getNetd(Context context) {
+ final INetd netd =
+ INetd.Stub.asInterface((IBinder) context.getSystemService(Context.NETD_SERVICE));
+ Objects.requireNonNull(netd, "could not get netd instance");
+ return netd;
+ }
+
+ public static EthernetServiceImpl create(Context context) {
+ final HandlerThread handlerThread = new HandlerThread(THREAD_NAME);
+ handlerThread.start();
+ final Handler handler = new Handler(handlerThread.getLooper());
+ final EthernetNetworkFactory factory = new EthernetNetworkFactory(handler, context);
+ return new EthernetServiceImpl(context, handler,
+ new EthernetTracker(context, handler, factory, getNetd(context)));
+ }
+}
diff --git a/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java b/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
new file mode 100644
index 0000000..71d3e4f
--- /dev/null
+++ b/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.ethernet;
+
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.EthernetNetworkUpdateRequest;
+import android.net.IEthernetManager;
+import android.net.IEthernetServiceListener;
+import android.net.INetworkInterfaceOutcomeReceiver;
+import android.net.ITetheredInterfaceCallback;
+import android.net.IpConfiguration;
+import android.net.NetworkCapabilities;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.PrintWriterPrinter;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.net.module.util.PermissionUtils;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * EthernetServiceImpl handles remote Ethernet operation requests by implementing
+ * the IEthernetManager interface.
+ */
+public class EthernetServiceImpl extends IEthernetManager.Stub {
+ private static final String TAG = "EthernetServiceImpl";
+
+ @VisibleForTesting
+ final AtomicBoolean mStarted = new AtomicBoolean(false);
+ private final Context mContext;
+ private final Handler mHandler;
+ private final EthernetTracker mTracker;
+
+ EthernetServiceImpl(@NonNull final Context context, @NonNull final Handler handler,
+ @NonNull final EthernetTracker tracker) {
+ mContext = context;
+ mHandler = handler;
+ mTracker = tracker;
+ }
+
+ private void enforceAutomotiveDevice(final @NonNull String methodName) {
+ PermissionUtils.enforceSystemFeature(mContext, PackageManager.FEATURE_AUTOMOTIVE,
+ methodName + " is only available on automotive devices.");
+ }
+
+ private boolean checkUseRestrictedNetworksPermission() {
+ return PermissionUtils.checkAnyPermissionOf(mContext,
+ android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS);
+ }
+
+ public void start() {
+ Log.i(TAG, "Starting Ethernet service");
+ mTracker.start();
+ mStarted.set(true);
+ }
+
+ private void throwIfEthernetNotStarted() {
+ if (!mStarted.get()) {
+ throw new IllegalStateException("System isn't ready to change ethernet configurations");
+ }
+ }
+
+ @Override
+ public String[] getAvailableInterfaces() throws RemoteException {
+ PermissionUtils.enforceAccessNetworkStatePermission(mContext, TAG);
+ return mTracker.getInterfaces(checkUseRestrictedNetworksPermission());
+ }
+
+ /**
+ * Get Ethernet configuration
+ * @return the Ethernet Configuration, contained in {@link IpConfiguration}.
+ */
+ @Override
+ public IpConfiguration getConfiguration(String iface) {
+ PermissionUtils.enforceAccessNetworkStatePermission(mContext, TAG);
+ if (mTracker.isRestrictedInterface(iface)) {
+ PermissionUtils.enforceRestrictedNetworkPermission(mContext, TAG);
+ }
+
+ return new IpConfiguration(mTracker.getIpConfiguration(iface));
+ }
+
+ /**
+ * Set Ethernet configuration
+ */
+ @Override
+ public void setConfiguration(String iface, IpConfiguration config) {
+ throwIfEthernetNotStarted();
+
+ PermissionUtils.enforceNetworkStackPermission(mContext);
+ if (mTracker.isRestrictedInterface(iface)) {
+ PermissionUtils.enforceRestrictedNetworkPermission(mContext, TAG);
+ }
+
+ // TODO: this does not check proxy settings, gateways, etc.
+ // Fix this by making IpConfiguration a complete representation of static configuration.
+ mTracker.updateIpConfiguration(iface, new IpConfiguration(config));
+ }
+
+ /**
+ * Indicates whether given interface is available.
+ */
+ @Override
+ public boolean isAvailable(String iface) {
+ PermissionUtils.enforceAccessNetworkStatePermission(mContext, TAG);
+ if (mTracker.isRestrictedInterface(iface)) {
+ PermissionUtils.enforceRestrictedNetworkPermission(mContext, TAG);
+ }
+
+ return mTracker.isTrackingInterface(iface);
+ }
+
+ /**
+ * Adds a listener.
+ * @param listener A {@link IEthernetServiceListener} to add.
+ */
+ public void addListener(IEthernetServiceListener listener) throws RemoteException {
+ Objects.requireNonNull(listener, "listener must not be null");
+ PermissionUtils.enforceAccessNetworkStatePermission(mContext, TAG);
+ mTracker.addListener(listener, checkUseRestrictedNetworksPermission());
+ }
+
+ /**
+ * Removes a listener.
+ * @param listener A {@link IEthernetServiceListener} to remove.
+ */
+ public void removeListener(IEthernetServiceListener listener) {
+ if (listener == null) {
+ throw new IllegalArgumentException("listener must not be null");
+ }
+ PermissionUtils.enforceAccessNetworkStatePermission(mContext, TAG);
+ mTracker.removeListener(listener);
+ }
+
+ @Override
+ public void setIncludeTestInterfaces(boolean include) {
+ PermissionUtils.enforceNetworkStackPermissionOr(mContext,
+ android.Manifest.permission.NETWORK_SETTINGS);
+ mTracker.setIncludeTestInterfaces(include);
+ }
+
+ @Override
+ public void requestTetheredInterface(ITetheredInterfaceCallback callback) {
+ Objects.requireNonNull(callback, "callback must not be null");
+ PermissionUtils.enforceNetworkStackPermissionOr(mContext,
+ android.Manifest.permission.NETWORK_SETTINGS);
+ mTracker.requestTetheredInterface(callback);
+ }
+
+ @Override
+ public void releaseTetheredInterface(ITetheredInterfaceCallback callback) {
+ Objects.requireNonNull(callback, "callback must not be null");
+ PermissionUtils.enforceNetworkStackPermissionOr(mContext,
+ android.Manifest.permission.NETWORK_SETTINGS);
+ mTracker.releaseTetheredInterface(callback);
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+ pw.println("Permission Denial: can't dump EthernetService from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid());
+ return;
+ }
+
+ pw.println("Current Ethernet state: ");
+ pw.increaseIndent();
+ mTracker.dump(fd, pw, args);
+ pw.decreaseIndent();
+
+ pw.println("Handler:");
+ pw.increaseIndent();
+ mHandler.dump(new PrintWriterPrinter(pw), "EthernetServiceImpl");
+ pw.decreaseIndent();
+ }
+
+ private void enforceNetworkManagementPermission() {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.MANAGE_ETHERNET_NETWORKS,
+ "EthernetServiceImpl");
+ }
+
+ private void enforceManageTestNetworksPermission() {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.MANAGE_TEST_NETWORKS,
+ "EthernetServiceImpl");
+ }
+
+ private void maybeValidateTestCapabilities(final String iface,
+ @Nullable final NetworkCapabilities nc) {
+ if (!mTracker.isValidTestInterface(iface)) {
+ return;
+ }
+ // For test interfaces, only null or capabilities that include TRANSPORT_TEST are
+ // allowed.
+ if (nc != null && !nc.hasTransport(TRANSPORT_TEST)) {
+ throw new IllegalArgumentException(
+ "Updates to test interfaces must have NetworkCapabilities.TRANSPORT_TEST.");
+ }
+ }
+
+ private void enforceAdminPermission(final String iface, boolean enforceAutomotive,
+ final String logMessage) {
+ if (mTracker.isValidTestInterface(iface)) {
+ enforceManageTestNetworksPermission();
+ } else {
+ enforceNetworkManagementPermission();
+ if (enforceAutomotive) {
+ enforceAutomotiveDevice(logMessage);
+ }
+ }
+ }
+
+ @Override
+ public void updateConfiguration(@NonNull final String iface,
+ @NonNull final EthernetNetworkUpdateRequest request,
+ @Nullable final INetworkInterfaceOutcomeReceiver listener) {
+ Objects.requireNonNull(iface);
+ Objects.requireNonNull(request);
+ throwIfEthernetNotStarted();
+
+ // TODO: validate that iface is listed in overlay config_ethernet_interfaces
+ // only automotive devices are allowed to set the NetworkCapabilities using this API
+ enforceAdminPermission(iface, request.getNetworkCapabilities() != null,
+ "updateConfiguration() with non-null capabilities");
+ maybeValidateTestCapabilities(iface, request.getNetworkCapabilities());
+
+ mTracker.updateConfiguration(
+ iface, request.getIpConfiguration(), request.getNetworkCapabilities(), listener);
+ }
+
+ @Override
+ public void enableInterface(@NonNull final String iface,
+ @Nullable final INetworkInterfaceOutcomeReceiver listener) {
+ Log.i(TAG, "enableInterface called with: iface=" + iface + ", listener=" + listener);
+ Objects.requireNonNull(iface);
+ throwIfEthernetNotStarted();
+
+ enforceAdminPermission(iface, false, "enableInterface()");
+
+ mTracker.enableInterface(iface, listener);
+ }
+
+ @Override
+ public void disableInterface(@NonNull final String iface,
+ @Nullable final INetworkInterfaceOutcomeReceiver listener) {
+ Log.i(TAG, "disableInterface called with: iface=" + iface + ", listener=" + listener);
+ Objects.requireNonNull(iface);
+ throwIfEthernetNotStarted();
+
+ enforceAdminPermission(iface, false, "disableInterface()");
+
+ mTracker.disableInterface(iface, listener);
+ }
+
+ @Override
+ public void setEthernetEnabled(boolean enabled) {
+ PermissionUtils.enforceNetworkStackPermissionOr(mContext,
+ android.Manifest.permission.NETWORK_SETTINGS);
+
+ mTracker.setEthernetEnabled(enabled);
+ }
+
+ @Override
+ public List<String> getInterfaceList() {
+ PermissionUtils.enforceAccessNetworkStatePermission(mContext, TAG);
+ return mTracker.getInterfaceList();
+ }
+}
diff --git a/service-t/src/com/android/server/ethernet/EthernetTracker.java b/service-t/src/com/android/server/ethernet/EthernetTracker.java
new file mode 100644
index 0000000..6405795
--- /dev/null
+++ b/service-t/src/com/android/server/ethernet/EthernetTracker.java
@@ -0,0 +1,963 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.ethernet;
+
+import static android.net.EthernetManager.ETHERNET_STATE_DISABLED;
+import static android.net.EthernetManager.ETHERNET_STATE_ENABLED;
+import static android.net.TestNetworkManager.TEST_TAP_PREFIX;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.net.ConnectivityResources;
+import android.net.EthernetManager;
+import android.net.IEthernetServiceListener;
+import android.net.INetd;
+import android.net.INetworkInterfaceOutcomeReceiver;
+import android.net.ITetheredInterfaceCallback;
+import android.net.InterfaceConfigurationParcel;
+import android.net.IpConfiguration;
+import android.net.IpConfiguration.IpAssignment;
+import android.net.IpConfiguration.ProxySettings;
+import android.net.LinkAddress;
+import android.net.NetworkCapabilities;
+import android.net.StaticIpConfiguration;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
+import com.android.net.module.util.NetdUtils;
+import com.android.net.module.util.PermissionUtils;
+
+import java.io.FileDescriptor;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Tracks Ethernet interfaces and manages interface configurations.
+ *
+ * <p>Interfaces may have different {@link android.net.NetworkCapabilities}. This mapping is defined
+ * in {@code config_ethernet_interfaces}. Notably, some interfaces could be marked as restricted by
+ * not specifying {@link android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED} flag.
+ * Interfaces could have associated {@link android.net.IpConfiguration}.
+ * Ethernet Interfaces may be present at boot time or appear after boot (e.g., for Ethernet adapters
+ * connected over USB). This class supports multiple interfaces. When an interface appears on the
+ * system (or is present at boot time) this class will start tracking it and bring it up. Only
+ * interfaces whose names match the {@code config_ethernet_iface_regex} regular expression are
+ * tracked.
+ *
+ * <p>All public or package private methods must be thread-safe unless stated otherwise.
+ */
+@VisibleForTesting(visibility = PACKAGE)
+public class EthernetTracker {
+ private static final int INTERFACE_MODE_CLIENT = 1;
+ private static final int INTERFACE_MODE_SERVER = 2;
+
+ private static final String TAG = EthernetTracker.class.getSimpleName();
+ private static final boolean DBG = EthernetNetworkFactory.DBG;
+
+ private static final String TEST_IFACE_REGEXP = TEST_TAP_PREFIX + "\\d+";
+
+ /**
+ * Interface names we track. This is a product-dependent regular expression, plus,
+ * if setIncludeTestInterfaces is true, any test interfaces.
+ */
+ private volatile String mIfaceMatch;
+ /**
+ * Track test interfaces if true, don't track otherwise.
+ */
+ private boolean mIncludeTestInterfaces = false;
+
+ /** Mapping between {iface name | mac address} -> {NetworkCapabilities} */
+ private final ConcurrentHashMap<String, NetworkCapabilities> mNetworkCapabilities =
+ new ConcurrentHashMap<>();
+ private final ConcurrentHashMap<String, IpConfiguration> mIpConfigurations =
+ new ConcurrentHashMap<>();
+
+ private final Context mContext;
+ private final INetd mNetd;
+ private final Handler mHandler;
+ private final EthernetNetworkFactory mFactory;
+ private final EthernetConfigStore mConfigStore;
+ private final Dependencies mDeps;
+
+ private final RemoteCallbackList<IEthernetServiceListener> mListeners =
+ new RemoteCallbackList<>();
+ private final TetheredInterfaceRequestList mTetheredInterfaceRequests =
+ new TetheredInterfaceRequestList();
+
+ // Used only on the handler thread
+ private String mDefaultInterface;
+ private int mDefaultInterfaceMode = INTERFACE_MODE_CLIENT;
+ // Tracks whether clients were notified that the tethered interface is available
+ private boolean mTetheredInterfaceWasAvailable = false;
+ private volatile IpConfiguration mIpConfigForDefaultInterface;
+
+ private int mEthernetState = ETHERNET_STATE_ENABLED;
+
+ private class TetheredInterfaceRequestList extends
+ RemoteCallbackList<ITetheredInterfaceCallback> {
+ @Override
+ public void onCallbackDied(ITetheredInterfaceCallback cb, Object cookie) {
+ mHandler.post(EthernetTracker.this::maybeUntetherDefaultInterface);
+ }
+ }
+
+ public static class Dependencies {
+ public String getInterfaceRegexFromResource(Context context) {
+ final ConnectivityResources resources = new ConnectivityResources(context);
+ return resources.get().getString(
+ com.android.connectivity.resources.R.string.config_ethernet_iface_regex);
+ }
+
+ public String[] getInterfaceConfigFromResource(Context context) {
+ final ConnectivityResources resources = new ConnectivityResources(context);
+ return resources.get().getStringArray(
+ com.android.connectivity.resources.R.array.config_ethernet_interfaces);
+ }
+ }
+
+ EthernetTracker(@NonNull final Context context, @NonNull final Handler handler,
+ @NonNull final EthernetNetworkFactory factory, @NonNull final INetd netd) {
+ this(context, handler, factory, netd, new Dependencies());
+ }
+
+ @VisibleForTesting
+ EthernetTracker(@NonNull final Context context, @NonNull final Handler handler,
+ @NonNull final EthernetNetworkFactory factory, @NonNull final INetd netd,
+ @NonNull final Dependencies deps) {
+ mContext = context;
+ mHandler = handler;
+ mFactory = factory;
+ mNetd = netd;
+ mDeps = deps;
+
+ // Interface match regex.
+ updateIfaceMatchRegexp();
+
+ // Read default Ethernet interface configuration from resources
+ final String[] interfaceConfigs = mDeps.getInterfaceConfigFromResource(context);
+ for (String strConfig : interfaceConfigs) {
+ parseEthernetConfig(strConfig);
+ }
+
+ mConfigStore = new EthernetConfigStore();
+ }
+
+ void start() {
+ mFactory.register();
+ mConfigStore.read();
+
+ // Default interface is just the first one we want to track.
+ mIpConfigForDefaultInterface = mConfigStore.getIpConfigurationForDefaultInterface();
+ final ArrayMap<String, IpConfiguration> configs = mConfigStore.getIpConfigurations();
+ for (int i = 0; i < configs.size(); i++) {
+ mIpConfigurations.put(configs.keyAt(i), configs.valueAt(i));
+ }
+
+ try {
+ PermissionUtils.enforceNetworkStackPermission(mContext);
+ mNetd.registerUnsolicitedEventListener(new InterfaceObserver());
+ } catch (RemoteException | ServiceSpecificException e) {
+ Log.e(TAG, "Could not register InterfaceObserver " + e);
+ }
+
+ mHandler.post(this::trackAvailableInterfaces);
+ }
+
+ void updateIpConfiguration(String iface, IpConfiguration ipConfiguration) {
+ if (DBG) {
+ Log.i(TAG, "updateIpConfiguration, iface: " + iface + ", cfg: " + ipConfiguration);
+ }
+ writeIpConfiguration(iface, ipConfiguration);
+ mHandler.post(() -> {
+ mFactory.updateInterface(iface, ipConfiguration, null, null);
+ broadcastInterfaceStateChange(iface);
+ });
+ }
+
+ private void writeIpConfiguration(@NonNull final String iface,
+ @NonNull final IpConfiguration ipConfig) {
+ mConfigStore.write(iface, ipConfig);
+ mIpConfigurations.put(iface, ipConfig);
+ }
+
+ private IpConfiguration getIpConfigurationForCallback(String iface, int state) {
+ return (state == EthernetManager.STATE_ABSENT) ? null : getOrCreateIpConfiguration(iface);
+ }
+
+ private void ensureRunningOnEthernetServiceThread() {
+ if (mHandler.getLooper().getThread() != Thread.currentThread()) {
+ throw new IllegalStateException(
+ "Not running on EthernetService thread: "
+ + Thread.currentThread().getName());
+ }
+ }
+
+ /**
+ * Broadcast the link state or IpConfiguration change of existing Ethernet interfaces to all
+ * listeners.
+ */
+ protected void broadcastInterfaceStateChange(@NonNull String iface) {
+ ensureRunningOnEthernetServiceThread();
+ final int state = getInterfaceState(iface);
+ final int role = getInterfaceRole(iface);
+ final IpConfiguration config = getIpConfigurationForCallback(iface, state);
+ final int n = mListeners.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ try {
+ mListeners.getBroadcastItem(i).onInterfaceStateChanged(iface, state, role, config);
+ } catch (RemoteException e) {
+ // Do nothing here.
+ }
+ }
+ mListeners.finishBroadcast();
+ }
+
+ /**
+ * Unicast the interface state or IpConfiguration change of existing Ethernet interfaces to a
+ * specific listener.
+ */
+ protected void unicastInterfaceStateChange(@NonNull IEthernetServiceListener listener,
+ @NonNull String iface) {
+ ensureRunningOnEthernetServiceThread();
+ final int state = mFactory.getInterfaceState(iface);
+ final int role = getInterfaceRole(iface);
+ final IpConfiguration config = getIpConfigurationForCallback(iface, state);
+ try {
+ listener.onInterfaceStateChanged(iface, state, role, config);
+ } catch (RemoteException e) {
+ // Do nothing here.
+ }
+ }
+
+ @VisibleForTesting(visibility = PACKAGE)
+ protected void updateConfiguration(@NonNull final String iface,
+ @Nullable final IpConfiguration ipConfig,
+ @Nullable final NetworkCapabilities capabilities,
+ @Nullable final INetworkInterfaceOutcomeReceiver listener) {
+ if (DBG) {
+ Log.i(TAG, "updateConfiguration, iface: " + iface + ", capabilities: " + capabilities
+ + ", ipConfig: " + ipConfig);
+ }
+
+ final IpConfiguration localIpConfig = ipConfig == null
+ ? null : new IpConfiguration(ipConfig);
+ if (ipConfig != null) {
+ writeIpConfiguration(iface, localIpConfig);
+ }
+
+ if (null != capabilities) {
+ mNetworkCapabilities.put(iface, capabilities);
+ }
+ mHandler.post(() -> {
+ mFactory.updateInterface(iface, localIpConfig, capabilities, listener);
+ broadcastInterfaceStateChange(iface);
+ });
+ }
+
+ @VisibleForTesting(visibility = PACKAGE)
+ protected void enableInterface(@NonNull final String iface,
+ @Nullable final INetworkInterfaceOutcomeReceiver listener) {
+ mHandler.post(() -> updateInterfaceState(iface, true, listener));
+ }
+
+ @VisibleForTesting(visibility = PACKAGE)
+ protected void disableInterface(@NonNull final String iface,
+ @Nullable final INetworkInterfaceOutcomeReceiver listener) {
+ mHandler.post(() -> updateInterfaceState(iface, false, listener));
+ }
+
+ IpConfiguration getIpConfiguration(String iface) {
+ return mIpConfigurations.get(iface);
+ }
+
+ @VisibleForTesting(visibility = PACKAGE)
+ protected boolean isTrackingInterface(String iface) {
+ return mFactory.hasInterface(iface);
+ }
+
+ String[] getInterfaces(boolean includeRestricted) {
+ return mFactory.getAvailableInterfaces(includeRestricted);
+ }
+
+ List<String> getInterfaceList() {
+ final List<String> interfaceList = new ArrayList<String>();
+ final String[] ifaces;
+ try {
+ ifaces = mNetd.interfaceGetList();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not get list of interfaces " + e);
+ return interfaceList;
+ }
+ final String ifaceMatch = mIfaceMatch;
+ for (String iface : ifaces) {
+ if (iface.matches(ifaceMatch)) interfaceList.add(iface);
+ }
+ return interfaceList;
+ }
+
+ /**
+ * Returns true if given interface was configured as restricted (doesn't have
+ * NET_CAPABILITY_NOT_RESTRICTED) capability. Otherwise, returns false.
+ */
+ boolean isRestrictedInterface(String iface) {
+ final NetworkCapabilities nc = mNetworkCapabilities.get(iface);
+ return nc != null && !nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+ }
+
+ void addListener(IEthernetServiceListener listener, boolean canUseRestrictedNetworks) {
+ mHandler.post(() -> {
+ if (!mListeners.register(listener, new ListenerInfo(canUseRestrictedNetworks))) {
+ // Remote process has already died
+ return;
+ }
+ for (String iface : getInterfaces(canUseRestrictedNetworks)) {
+ unicastInterfaceStateChange(listener, iface);
+ }
+
+ unicastEthernetStateChange(listener, mEthernetState);
+ });
+ }
+
+ void removeListener(IEthernetServiceListener listener) {
+ mHandler.post(() -> mListeners.unregister(listener));
+ }
+
+ public void setIncludeTestInterfaces(boolean include) {
+ mHandler.post(() -> {
+ mIncludeTestInterfaces = include;
+ updateIfaceMatchRegexp();
+ if (!include) {
+ removeTestData();
+ }
+ mHandler.post(() -> trackAvailableInterfaces());
+ });
+ }
+
+ private void removeTestData() {
+ removeTestIpData();
+ removeTestCapabilityData();
+ }
+
+ private void removeTestIpData() {
+ final Iterator<String> iterator = mIpConfigurations.keySet().iterator();
+ while (iterator.hasNext()) {
+ final String iface = iterator.next();
+ if (iface.matches(TEST_IFACE_REGEXP)) {
+ mConfigStore.write(iface, null);
+ iterator.remove();
+ }
+ }
+ }
+
+ private void removeTestCapabilityData() {
+ mNetworkCapabilities.keySet().removeIf(iface -> iface.matches(TEST_IFACE_REGEXP));
+ }
+
+ public void requestTetheredInterface(ITetheredInterfaceCallback callback) {
+ mHandler.post(() -> {
+ if (!mTetheredInterfaceRequests.register(callback)) {
+ // Remote process has already died
+ return;
+ }
+ if (mDefaultInterfaceMode == INTERFACE_MODE_SERVER) {
+ if (mTetheredInterfaceWasAvailable) {
+ notifyTetheredInterfaceAvailable(callback, mDefaultInterface);
+ }
+ return;
+ }
+
+ setDefaultInterfaceMode(INTERFACE_MODE_SERVER);
+ });
+ }
+
+ public void releaseTetheredInterface(ITetheredInterfaceCallback callback) {
+ mHandler.post(() -> {
+ mTetheredInterfaceRequests.unregister(callback);
+ maybeUntetherDefaultInterface();
+ });
+ }
+
+ private void notifyTetheredInterfaceAvailable(ITetheredInterfaceCallback cb, String iface) {
+ try {
+ cb.onAvailable(iface);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error sending tethered interface available callback", e);
+ }
+ }
+
+ private void notifyTetheredInterfaceUnavailable(ITetheredInterfaceCallback cb) {
+ try {
+ cb.onUnavailable();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error sending tethered interface available callback", e);
+ }
+ }
+
+ private void maybeUntetherDefaultInterface() {
+ if (mTetheredInterfaceRequests.getRegisteredCallbackCount() > 0) return;
+ if (mDefaultInterfaceMode == INTERFACE_MODE_CLIENT) return;
+ setDefaultInterfaceMode(INTERFACE_MODE_CLIENT);
+ }
+
+ private void setDefaultInterfaceMode(int mode) {
+ Log.d(TAG, "Setting default interface mode to " + mode);
+ mDefaultInterfaceMode = mode;
+ if (mDefaultInterface != null) {
+ removeInterface(mDefaultInterface);
+ addInterface(mDefaultInterface);
+ // when this broadcast is sent, any calls to notifyTetheredInterfaceAvailable or
+ // notifyTetheredInterfaceUnavailable have already happened
+ broadcastInterfaceStateChange(mDefaultInterface);
+ }
+ }
+
+ private int getInterfaceState(final String iface) {
+ if (mFactory.hasInterface(iface)) {
+ return mFactory.getInterfaceState(iface);
+ }
+ if (getInterfaceMode(iface) == INTERFACE_MODE_SERVER) {
+ // server mode interfaces are not tracked by the factory.
+ // TODO(b/234743836): interface state for server mode interfaces is not tracked
+ // properly; just return link up.
+ return EthernetManager.STATE_LINK_UP;
+ }
+ return EthernetManager.STATE_ABSENT;
+ }
+
+ private int getInterfaceRole(final String iface) {
+ if (mFactory.hasInterface(iface)) {
+ // only client mode interfaces are tracked by the factory.
+ return EthernetManager.ROLE_CLIENT;
+ }
+ if (getInterfaceMode(iface) == INTERFACE_MODE_SERVER) {
+ return EthernetManager.ROLE_SERVER;
+ }
+ return EthernetManager.ROLE_NONE;
+ }
+
+ private int getInterfaceMode(final String iface) {
+ if (iface.equals(mDefaultInterface)) {
+ return mDefaultInterfaceMode;
+ }
+ return INTERFACE_MODE_CLIENT;
+ }
+
+ private void removeInterface(String iface) {
+ mFactory.removeInterface(iface);
+ maybeUpdateServerModeInterfaceState(iface, false);
+ }
+
+ private void stopTrackingInterface(String iface) {
+ removeInterface(iface);
+ if (iface.equals(mDefaultInterface)) {
+ mDefaultInterface = null;
+ }
+ broadcastInterfaceStateChange(iface);
+ }
+
+ private void addInterface(String iface) {
+ InterfaceConfigurationParcel config = null;
+ // Bring up the interface so we get link status indications.
+ try {
+ PermissionUtils.enforceNetworkStackPermission(mContext);
+ NetdUtils.setInterfaceUp(mNetd, iface);
+ config = NetdUtils.getInterfaceConfigParcel(mNetd, iface);
+ } catch (IllegalStateException e) {
+ // Either the system is crashing or the interface has disappeared. Just ignore the
+ // error; we haven't modified any state because we only do that if our calls succeed.
+ Log.e(TAG, "Error upping interface " + iface, e);
+ }
+
+ if (config == null) {
+ Log.e(TAG, "Null interface config parcelable for " + iface + ". Bailing out.");
+ return;
+ }
+
+ final String hwAddress = config.hwAddr;
+
+ NetworkCapabilities nc = mNetworkCapabilities.get(iface);
+ if (nc == null) {
+ // Try to resolve using mac address
+ nc = mNetworkCapabilities.get(hwAddress);
+ if (nc == null) {
+ final boolean isTestIface = iface.matches(TEST_IFACE_REGEXP);
+ nc = createDefaultNetworkCapabilities(isTestIface);
+ }
+ }
+
+ final int mode = getInterfaceMode(iface);
+ if (mode == INTERFACE_MODE_CLIENT) {
+ IpConfiguration ipConfiguration = getOrCreateIpConfiguration(iface);
+ Log.d(TAG, "Tracking interface in client mode: " + iface);
+ mFactory.addInterface(iface, hwAddress, ipConfiguration, nc);
+ } else {
+ maybeUpdateServerModeInterfaceState(iface, true);
+ }
+
+ // Note: if the interface already has link (e.g., if we crashed and got
+ // restarted while it was running), we need to fake a link up notification so we
+ // start configuring it.
+ if (NetdUtils.hasFlag(config, "running")) {
+ updateInterfaceState(iface, true);
+ }
+ }
+
+ private void updateInterfaceState(String iface, boolean up) {
+ updateInterfaceState(iface, up, null /* listener */);
+ }
+
+ private void updateInterfaceState(@NonNull final String iface, final boolean up,
+ @Nullable final INetworkInterfaceOutcomeReceiver listener) {
+ final int mode = getInterfaceMode(iface);
+ final boolean factoryLinkStateUpdated = (mode == INTERFACE_MODE_CLIENT)
+ && mFactory.updateInterfaceLinkState(iface, up, listener);
+
+ if (factoryLinkStateUpdated) {
+ broadcastInterfaceStateChange(iface);
+ }
+ }
+
+ private void maybeUpdateServerModeInterfaceState(String iface, boolean available) {
+ if (available == mTetheredInterfaceWasAvailable || !iface.equals(mDefaultInterface)) return;
+
+ Log.d(TAG, (available ? "Tracking" : "No longer tracking")
+ + " interface in server mode: " + iface);
+
+ final int pendingCbs = mTetheredInterfaceRequests.beginBroadcast();
+ for (int i = 0; i < pendingCbs; i++) {
+ ITetheredInterfaceCallback item = mTetheredInterfaceRequests.getBroadcastItem(i);
+ if (available) {
+ notifyTetheredInterfaceAvailable(item, iface);
+ } else {
+ notifyTetheredInterfaceUnavailable(item);
+ }
+ }
+ mTetheredInterfaceRequests.finishBroadcast();
+ mTetheredInterfaceWasAvailable = available;
+ }
+
+ private void maybeTrackInterface(String iface) {
+ if (!iface.matches(mIfaceMatch)) {
+ return;
+ }
+
+ // If we don't already track this interface, and if this interface matches
+ // our regex, start tracking it.
+ if (mFactory.hasInterface(iface) || iface.equals(mDefaultInterface)) {
+ if (DBG) Log.w(TAG, "Ignoring already-tracked interface " + iface);
+ return;
+ }
+ if (DBG) Log.i(TAG, "maybeTrackInterface: " + iface);
+
+ // TODO: avoid making an interface default if it has configured NetworkCapabilities.
+ if (mDefaultInterface == null) {
+ mDefaultInterface = iface;
+ }
+
+ if (mIpConfigForDefaultInterface != null) {
+ updateIpConfiguration(iface, mIpConfigForDefaultInterface);
+ mIpConfigForDefaultInterface = null;
+ }
+
+ addInterface(iface);
+
+ broadcastInterfaceStateChange(iface);
+ }
+
+ private void trackAvailableInterfaces() {
+ try {
+ final String[] ifaces = mNetd.interfaceGetList();
+ for (String iface : ifaces) {
+ maybeTrackInterface(iface);
+ }
+ } catch (RemoteException | ServiceSpecificException e) {
+ Log.e(TAG, "Could not get list of interfaces " + e);
+ }
+ }
+
+ @VisibleForTesting
+ class InterfaceObserver extends BaseNetdUnsolicitedEventListener {
+
+ @Override
+ public void onInterfaceLinkStateChanged(String iface, boolean up) {
+ if (DBG) {
+ Log.i(TAG, "interfaceLinkStateChanged, iface: " + iface + ", up: " + up);
+ }
+ mHandler.post(() -> {
+ if (mEthernetState == ETHERNET_STATE_DISABLED) return;
+ updateInterfaceState(iface, up);
+ });
+ }
+
+ @Override
+ public void onInterfaceAdded(String iface) {
+ if (DBG) {
+ Log.i(TAG, "onInterfaceAdded, iface: " + iface);
+ }
+ mHandler.post(() -> {
+ if (mEthernetState == ETHERNET_STATE_DISABLED) return;
+ maybeTrackInterface(iface);
+ });
+ }
+
+ @Override
+ public void onInterfaceRemoved(String iface) {
+ if (DBG) {
+ Log.i(TAG, "onInterfaceRemoved, iface: " + iface);
+ }
+ mHandler.post(() -> {
+ if (mEthernetState == ETHERNET_STATE_DISABLED) return;
+ stopTrackingInterface(iface);
+ });
+ }
+ }
+
+ private static class ListenerInfo {
+
+ boolean canUseRestrictedNetworks = false;
+
+ ListenerInfo(boolean canUseRestrictedNetworks) {
+ this.canUseRestrictedNetworks = canUseRestrictedNetworks;
+ }
+ }
+
+ /**
+ * Parses an Ethernet interface configuration
+ *
+ * @param configString represents an Ethernet configuration in the following format: {@code
+ * <interface name|mac address>;[Network Capabilities];[IP config];[Override Transport]}
+ */
+ private void parseEthernetConfig(String configString) {
+ final EthernetTrackerConfig config = createEthernetTrackerConfig(configString);
+ NetworkCapabilities nc = createNetworkCapabilities(
+ !TextUtils.isEmpty(config.mCapabilities) /* clear default capabilities */,
+ config.mCapabilities, config.mTransport).build();
+ mNetworkCapabilities.put(config.mIface, nc);
+
+ if (null != config.mIpConfig) {
+ IpConfiguration ipConfig = parseStaticIpConfiguration(config.mIpConfig);
+ mIpConfigurations.put(config.mIface, ipConfig);
+ }
+ }
+
+ @VisibleForTesting
+ static EthernetTrackerConfig createEthernetTrackerConfig(@NonNull final String configString) {
+ Objects.requireNonNull(configString, "EthernetTrackerConfig requires non-null config");
+ return new EthernetTrackerConfig(configString.split(";", /* limit of tokens */ 4));
+ }
+
+ private static NetworkCapabilities createDefaultNetworkCapabilities(boolean isTestIface) {
+ NetworkCapabilities.Builder builder = createNetworkCapabilities(
+ false /* clear default capabilities */, null, null)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
+
+ if (isTestIface) {
+ builder.addTransportType(NetworkCapabilities.TRANSPORT_TEST);
+ } else {
+ builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+ }
+
+ return builder.build();
+ }
+
+ /**
+ * Parses a static list of network capabilities
+ *
+ * @param clearDefaultCapabilities Indicates whether or not to clear any default capabilities
+ * @param commaSeparatedCapabilities A comma separated string list of integer encoded
+ * NetworkCapability.NET_CAPABILITY_* values
+ * @param overrideTransport A string representing a single integer encoded override transport
+ * type. Must be one of the NetworkCapability.TRANSPORT_*
+ * values. TRANSPORT_VPN is not supported. Errors with input
+ * will cause the override to be ignored.
+ */
+ @VisibleForTesting
+ static NetworkCapabilities.Builder createNetworkCapabilities(
+ boolean clearDefaultCapabilities, @Nullable String commaSeparatedCapabilities,
+ @Nullable String overrideTransport) {
+
+ final NetworkCapabilities.Builder builder = clearDefaultCapabilities
+ ? NetworkCapabilities.Builder.withoutDefaultCapabilities()
+ : new NetworkCapabilities.Builder();
+
+ // Determine the transport type. If someone has tried to define an override transport then
+ // attempt to add it. Since we can only have one override, all errors with it will
+ // gracefully default back to TRANSPORT_ETHERNET and warn the user. VPN is not allowed as an
+ // override type. Wifi Aware and LoWPAN are currently unsupported as well.
+ int transport = NetworkCapabilities.TRANSPORT_ETHERNET;
+ if (!TextUtils.isEmpty(overrideTransport)) {
+ try {
+ int parsedTransport = Integer.valueOf(overrideTransport);
+ if (parsedTransport == NetworkCapabilities.TRANSPORT_VPN
+ || parsedTransport == NetworkCapabilities.TRANSPORT_WIFI_AWARE
+ || parsedTransport == NetworkCapabilities.TRANSPORT_LOWPAN) {
+ Log.e(TAG, "Override transport '" + parsedTransport + "' is not supported. "
+ + "Defaulting to TRANSPORT_ETHERNET");
+ } else {
+ transport = parsedTransport;
+ }
+ } catch (NumberFormatException nfe) {
+ Log.e(TAG, "Override transport type '" + overrideTransport + "' "
+ + "could not be parsed. Defaulting to TRANSPORT_ETHERNET");
+ }
+ }
+
+ // Apply the transport. If the user supplied a valid number that is not a valid transport
+ // then adding will throw an exception. Default back to TRANSPORT_ETHERNET if that happens
+ try {
+ builder.addTransportType(transport);
+ } catch (IllegalArgumentException iae) {
+ Log.e(TAG, transport + " is not a valid NetworkCapability.TRANSPORT_* value. "
+ + "Defaulting to TRANSPORT_ETHERNET");
+ builder.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET);
+ }
+
+ builder.setLinkUpstreamBandwidthKbps(100 * 1000);
+ builder.setLinkDownstreamBandwidthKbps(100 * 1000);
+
+ if (!TextUtils.isEmpty(commaSeparatedCapabilities)) {
+ for (String strNetworkCapability : commaSeparatedCapabilities.split(",")) {
+ if (!TextUtils.isEmpty(strNetworkCapability)) {
+ try {
+ builder.addCapability(Integer.valueOf(strNetworkCapability));
+ } catch (NumberFormatException nfe) {
+ Log.e(TAG, "Capability '" + strNetworkCapability + "' could not be parsed");
+ } catch (IllegalArgumentException iae) {
+ Log.e(TAG, strNetworkCapability + " is not a valid "
+ + "NetworkCapability.NET_CAPABILITY_* value");
+ }
+ }
+ }
+ }
+ // Ethernet networks have no way to update the following capabilities, so they always
+ // have them.
+ builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
+ builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED);
+ builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED);
+
+ return builder;
+ }
+
+ /**
+ * Parses static IP configuration.
+ *
+ * @param staticIpConfig represents static IP configuration in the following format: {@code
+ * ip=<ip-address/mask> gateway=<ip-address> dns=<comma-sep-ip-addresses>
+ * domains=<comma-sep-domains>}
+ */
+ @VisibleForTesting
+ static IpConfiguration parseStaticIpConfiguration(String staticIpConfig) {
+ final StaticIpConfiguration.Builder staticIpConfigBuilder =
+ new StaticIpConfiguration.Builder();
+
+ for (String keyValueAsString : staticIpConfig.trim().split(" ")) {
+ if (TextUtils.isEmpty(keyValueAsString)) continue;
+
+ String[] pair = keyValueAsString.split("=");
+ if (pair.length != 2) {
+ throw new IllegalArgumentException("Unexpected token: " + keyValueAsString
+ + " in " + staticIpConfig);
+ }
+
+ String key = pair[0];
+ String value = pair[1];
+
+ switch (key) {
+ case "ip":
+ staticIpConfigBuilder.setIpAddress(new LinkAddress(value));
+ break;
+ case "domains":
+ staticIpConfigBuilder.setDomains(value);
+ break;
+ case "gateway":
+ staticIpConfigBuilder.setGateway(InetAddress.parseNumericAddress(value));
+ break;
+ case "dns": {
+ ArrayList<InetAddress> dnsAddresses = new ArrayList<>();
+ for (String address: value.split(",")) {
+ dnsAddresses.add(InetAddress.parseNumericAddress(address));
+ }
+ staticIpConfigBuilder.setDnsServers(dnsAddresses);
+ break;
+ }
+ default : {
+ throw new IllegalArgumentException("Unexpected key: " + key
+ + " in " + staticIpConfig);
+ }
+ }
+ }
+ return createIpConfiguration(staticIpConfigBuilder.build());
+ }
+
+ private static IpConfiguration createIpConfiguration(
+ @NonNull final StaticIpConfiguration staticIpConfig) {
+ return new IpConfiguration.Builder().setStaticIpConfiguration(staticIpConfig).build();
+ }
+
+ private IpConfiguration getOrCreateIpConfiguration(String iface) {
+ IpConfiguration ret = mIpConfigurations.get(iface);
+ if (ret != null) return ret;
+ ret = new IpConfiguration();
+ ret.setIpAssignment(IpAssignment.DHCP);
+ ret.setProxySettings(ProxySettings.NONE);
+ return ret;
+ }
+
+ private void updateIfaceMatchRegexp() {
+ final String match = mDeps.getInterfaceRegexFromResource(mContext);
+ mIfaceMatch = mIncludeTestInterfaces
+ ? "(" + match + "|" + TEST_IFACE_REGEXP + ")"
+ : match;
+ Log.d(TAG, "Interface match regexp set to '" + mIfaceMatch + "'");
+ }
+
+ /**
+ * Validate if a given interface is valid for testing.
+ *
+ * @param iface the name of the interface to validate.
+ * @return {@code true} if test interfaces are enabled and the given {@code iface} has a test
+ * interface prefix, {@code false} otherwise.
+ */
+ public boolean isValidTestInterface(@NonNull final String iface) {
+ return mIncludeTestInterfaces && iface.matches(TEST_IFACE_REGEXP);
+ }
+
+ private void postAndWaitForRunnable(Runnable r) {
+ final ConditionVariable cv = new ConditionVariable();
+ if (mHandler.post(() -> {
+ r.run();
+ cv.open();
+ })) {
+ cv.block(2000L);
+ }
+ }
+
+ @VisibleForTesting(visibility = PACKAGE)
+ protected void setEthernetEnabled(boolean enabled) {
+ mHandler.post(() -> {
+ int newState = enabled ? ETHERNET_STATE_ENABLED : ETHERNET_STATE_DISABLED;
+ if (mEthernetState == newState) return;
+
+ mEthernetState = newState;
+
+ if (enabled) {
+ trackAvailableInterfaces();
+ } else {
+ // TODO: maybe also disable server mode interface as well.
+ untrackFactoryInterfaces();
+ }
+ broadcastEthernetStateChange(mEthernetState);
+ });
+ }
+
+ private void untrackFactoryInterfaces() {
+ for (String iface : mFactory.getAvailableInterfaces(true /* includeRestricted */)) {
+ stopTrackingInterface(iface);
+ }
+ }
+
+ private void unicastEthernetStateChange(@NonNull IEthernetServiceListener listener,
+ int state) {
+ ensureRunningOnEthernetServiceThread();
+ try {
+ listener.onEthernetStateChanged(state);
+ } catch (RemoteException e) {
+ // Do nothing here.
+ }
+ }
+
+ private void broadcastEthernetStateChange(int state) {
+ ensureRunningOnEthernetServiceThread();
+ final int n = mListeners.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ try {
+ mListeners.getBroadcastItem(i).onEthernetStateChanged(state);
+ } catch (RemoteException e) {
+ // Do nothing here.
+ }
+ }
+ mListeners.finishBroadcast();
+ }
+
+ void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
+ postAndWaitForRunnable(() -> {
+ pw.println(getClass().getSimpleName());
+ pw.println("Ethernet State: "
+ + (mEthernetState == ETHERNET_STATE_ENABLED ? "enabled" : "disabled"));
+ pw.println("Ethernet interface name filter: " + mIfaceMatch);
+ pw.println("Default interface: " + mDefaultInterface);
+ pw.println("Default interface mode: " + mDefaultInterfaceMode);
+ pw.println("Tethered interface requests: "
+ + mTetheredInterfaceRequests.getRegisteredCallbackCount());
+ pw.println("Listeners: " + mListeners.getRegisteredCallbackCount());
+ pw.println("IP Configurations:");
+ pw.increaseIndent();
+ for (String iface : mIpConfigurations.keySet()) {
+ pw.println(iface + ": " + mIpConfigurations.get(iface));
+ }
+ pw.decreaseIndent();
+ pw.println();
+
+ pw.println("Network Capabilities:");
+ pw.increaseIndent();
+ for (String iface : mNetworkCapabilities.keySet()) {
+ pw.println(iface + ": " + mNetworkCapabilities.get(iface));
+ }
+ pw.decreaseIndent();
+ pw.println();
+
+ mFactory.dump(fd, pw, args);
+ });
+ }
+
+ @VisibleForTesting
+ static class EthernetTrackerConfig {
+ final String mIface;
+ final String mCapabilities;
+ final String mIpConfig;
+ final String mTransport;
+
+ EthernetTrackerConfig(@NonNull final String[] tokens) {
+ Objects.requireNonNull(tokens, "EthernetTrackerConfig requires non-null tokens");
+ mIface = tokens[0];
+ mCapabilities = tokens.length > 1 ? tokens[1] : null;
+ mIpConfig = tokens.length > 2 && !TextUtils.isEmpty(tokens[2]) ? tokens[2] : null;
+ mTransport = tokens.length > 3 ? tokens[3] : null;
+ }
+ }
+}
diff --git a/service-t/src/com/android/server/net/BpfInterfaceMapUpdater.java b/service-t/src/com/android/server/net/BpfInterfaceMapUpdater.java
new file mode 100644
index 0000000..3b44d81
--- /dev/null
+++ b/service-t/src/com/android/server/net/BpfInterfaceMapUpdater.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.net;
+
+import android.content.Context;
+import android.net.INetd;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.system.ErrnoException;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
+import com.android.net.module.util.BpfMap;
+import com.android.net.module.util.IBpfMap;
+import com.android.net.module.util.InterfaceParams;
+import com.android.net.module.util.Struct.U32;
+
+/**
+ * Monitor interface added (without removed) and right interface name and its index to bpf map.
+ */
+public class BpfInterfaceMapUpdater {
+ private static final String TAG = BpfInterfaceMapUpdater.class.getSimpleName();
+ // This is current path but may be changed soon.
+ private static final String IFACE_INDEX_NAME_MAP_PATH =
+ "/sys/fs/bpf/netd_shared/map_netd_iface_index_name_map";
+ private final IBpfMap<U32, InterfaceMapValue> mBpfMap;
+ private final INetd mNetd;
+ private final Handler mHandler;
+ private final Dependencies mDeps;
+
+ public BpfInterfaceMapUpdater(Context ctx, Handler handler) {
+ this(ctx, handler, new Dependencies());
+ }
+
+ @VisibleForTesting
+ public BpfInterfaceMapUpdater(Context ctx, Handler handler, Dependencies deps) {
+ mDeps = deps;
+ mBpfMap = deps.getInterfaceMap();
+ mNetd = deps.getINetd(ctx);
+ mHandler = handler;
+ }
+
+ /**
+ * Dependencies of BpfInerfaceMapUpdater, for injection in tests.
+ */
+ @VisibleForTesting
+ public static class Dependencies {
+ /** Create BpfMap for updating interface and index mapping. */
+ public IBpfMap<U32, InterfaceMapValue> getInterfaceMap() {
+ try {
+ return new BpfMap<>(IFACE_INDEX_NAME_MAP_PATH, BpfMap.BPF_F_RDWR,
+ U32.class, InterfaceMapValue.class);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Cannot create interface map: " + e);
+ return null;
+ }
+ }
+
+ /** Get InterfaceParams for giving interface name. */
+ public InterfaceParams getInterfaceParams(String ifaceName) {
+ return InterfaceParams.getByName(ifaceName);
+ }
+
+ /** Get INetd binder object. */
+ public INetd getINetd(Context ctx) {
+ return INetd.Stub.asInterface((IBinder) ctx.getSystemService(Context.NETD_SERVICE));
+ }
+ }
+
+ /**
+ * Start listening interface update event.
+ * Query current interface names before listening.
+ */
+ public void start() {
+ mHandler.post(() -> {
+ if (mBpfMap == null) {
+ Log.wtf(TAG, "Fail to start: Null bpf map");
+ return;
+ }
+
+ try {
+ // TODO: use a NetlinkMonitor and listen for RTM_NEWLINK messages instead.
+ mNetd.registerUnsolicitedEventListener(new InterfaceChangeObserver());
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Unable to register netd UnsolicitedEventListener, " + e);
+ }
+
+ final String[] ifaces;
+ try {
+ // TODO: use a netlink dump to get the current interface list.
+ ifaces = mNetd.interfaceGetList();
+ } catch (RemoteException | ServiceSpecificException e) {
+ Log.wtf(TAG, "Unable to query interface names by netd, " + e);
+ return;
+ }
+
+ for (String ifaceName : ifaces) {
+ addInterface(ifaceName);
+ }
+ });
+ }
+
+ private void addInterface(String ifaceName) {
+ final InterfaceParams iface = mDeps.getInterfaceParams(ifaceName);
+ if (iface == null) {
+ Log.e(TAG, "Unable to get InterfaceParams for " + ifaceName);
+ return;
+ }
+
+ try {
+ mBpfMap.updateEntry(new U32(iface.index), new InterfaceMapValue(ifaceName));
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Unable to update entry for " + ifaceName + ", " + e);
+ }
+ }
+
+ private class InterfaceChangeObserver extends BaseNetdUnsolicitedEventListener {
+ @Override
+ public void onInterfaceAdded(String ifName) {
+ mHandler.post(() -> addInterface(ifName));
+ }
+ }
+}
diff --git a/service-t/src/com/android/server/net/CookieTagMapKey.java b/service-t/src/com/android/server/net/CookieTagMapKey.java
new file mode 100644
index 0000000..443e5b3
--- /dev/null
+++ b/service-t/src/com/android/server/net/CookieTagMapKey.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+/**
+ * Key for cookie tag map.
+ */
+public class CookieTagMapKey extends Struct {
+ @Field(order = 0, type = Type.S64)
+ public final long socketCookie;
+
+ public CookieTagMapKey(final long socketCookie) {
+ this.socketCookie = socketCookie;
+ }
+}
diff --git a/service-t/src/com/android/server/net/CookieTagMapValue.java b/service-t/src/com/android/server/net/CookieTagMapValue.java
new file mode 100644
index 0000000..93b9195
--- /dev/null
+++ b/service-t/src/com/android/server/net/CookieTagMapValue.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+/**
+ * Value for cookie tag map.
+ */
+public class CookieTagMapValue extends Struct {
+ @Field(order = 0, type = Type.U32)
+ public final long uid;
+
+ @Field(order = 1, type = Type.U32)
+ public final long tag;
+
+ public CookieTagMapValue(final long uid, final long tag) {
+ this.uid = uid;
+ this.tag = tag;
+ }
+}
diff --git a/service-t/src/com/android/server/net/InterfaceMapValue.java b/service-t/src/com/android/server/net/InterfaceMapValue.java
new file mode 100644
index 0000000..42c0044
--- /dev/null
+++ b/service-t/src/com/android/server/net/InterfaceMapValue.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.net;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+/**
+ * The value of bpf interface index map which is used for NetworkStatsService.
+ */
+public class InterfaceMapValue extends Struct {
+ @Field(order = 0, type = Type.ByteArray, arraysize = 16)
+ public final byte[] interfaceName;
+
+ public InterfaceMapValue(String iface) {
+ final byte[] ifaceArray = iface.getBytes();
+ interfaceName = new byte[16];
+ // All array bytes after the interface name, if any, must be 0.
+ System.arraycopy(ifaceArray, 0, interfaceName, 0, ifaceArray.length);
+ }
+}
diff --git a/service-t/src/com/android/server/net/IpConfigStore.java b/service-t/src/com/android/server/net/IpConfigStore.java
new file mode 100644
index 0000000..3a9a544
--- /dev/null
+++ b/service-t/src/com/android/server/net/IpConfigStore.java
@@ -0,0 +1,449 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net;
+
+import android.net.InetAddresses;
+import android.net.IpConfiguration;
+import android.net.IpConfiguration.IpAssignment;
+import android.net.IpConfiguration.ProxySettings;
+import android.net.LinkAddress;
+import android.net.ProxyInfo;
+import android.net.StaticIpConfiguration;
+import android.net.Uri;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.ProxyUtils;
+
+import java.io.BufferedInputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.EOFException;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class provides an API to store and manage L3 network IP configuration.
+ */
+public class IpConfigStore {
+ private static final String TAG = "IpConfigStore";
+ private static final boolean DBG = false;
+
+ protected final DelayedDiskWrite mWriter;
+
+ /* IP and proxy configuration keys */
+ protected static final String ID_KEY = "id";
+ protected static final String IP_ASSIGNMENT_KEY = "ipAssignment";
+ protected static final String LINK_ADDRESS_KEY = "linkAddress";
+ protected static final String GATEWAY_KEY = "gateway";
+ protected static final String DNS_KEY = "dns";
+ protected static final String PROXY_SETTINGS_KEY = "proxySettings";
+ protected static final String PROXY_HOST_KEY = "proxyHost";
+ protected static final String PROXY_PORT_KEY = "proxyPort";
+ protected static final String PROXY_PAC_FILE = "proxyPac";
+ protected static final String EXCLUSION_LIST_KEY = "exclusionList";
+ protected static final String EOS = "eos";
+
+ protected static final int IPCONFIG_FILE_VERSION = 3;
+
+ public IpConfigStore(DelayedDiskWrite writer) {
+ mWriter = writer;
+ }
+
+ public IpConfigStore() {
+ this(new DelayedDiskWrite());
+ }
+
+ private static boolean writeConfig(DataOutputStream out, String configKey,
+ IpConfiguration config) throws IOException {
+ return writeConfig(out, configKey, config, IPCONFIG_FILE_VERSION);
+ }
+
+ /**
+ * Write the IP configuration with the given parameters to {@link DataOutputStream}.
+ */
+ @VisibleForTesting
+ public static boolean writeConfig(DataOutputStream out, String configKey,
+ IpConfiguration config, int version) throws IOException {
+ boolean written = false;
+
+ try {
+ switch (config.getIpAssignment()) {
+ case STATIC:
+ out.writeUTF(IP_ASSIGNMENT_KEY);
+ out.writeUTF(config.getIpAssignment().toString());
+ StaticIpConfiguration staticIpConfiguration = config.getStaticIpConfiguration();
+ if (staticIpConfiguration != null) {
+ if (staticIpConfiguration.getIpAddress() != null) {
+ LinkAddress ipAddress = staticIpConfiguration.getIpAddress();
+ out.writeUTF(LINK_ADDRESS_KEY);
+ out.writeUTF(ipAddress.getAddress().getHostAddress());
+ out.writeInt(ipAddress.getPrefixLength());
+ }
+ if (staticIpConfiguration.getGateway() != null) {
+ out.writeUTF(GATEWAY_KEY);
+ out.writeInt(0); // Default route.
+ out.writeInt(1); // Have a gateway.
+ out.writeUTF(staticIpConfiguration.getGateway().getHostAddress());
+ }
+ for (InetAddress inetAddr : staticIpConfiguration.getDnsServers()) {
+ out.writeUTF(DNS_KEY);
+ out.writeUTF(inetAddr.getHostAddress());
+ }
+ }
+ written = true;
+ break;
+ case DHCP:
+ out.writeUTF(IP_ASSIGNMENT_KEY);
+ out.writeUTF(config.getIpAssignment().toString());
+ written = true;
+ break;
+ case UNASSIGNED:
+ /* Ignore */
+ break;
+ default:
+ loge("Ignore invalid ip assignment while writing");
+ break;
+ }
+
+ switch (config.getProxySettings()) {
+ case STATIC:
+ ProxyInfo proxyProperties = config.getHttpProxy();
+ String exclusionList = ProxyUtils.exclusionListAsString(
+ proxyProperties.getExclusionList());
+ out.writeUTF(PROXY_SETTINGS_KEY);
+ out.writeUTF(config.getProxySettings().toString());
+ out.writeUTF(PROXY_HOST_KEY);
+ out.writeUTF(proxyProperties.getHost());
+ out.writeUTF(PROXY_PORT_KEY);
+ out.writeInt(proxyProperties.getPort());
+ if (exclusionList != null) {
+ out.writeUTF(EXCLUSION_LIST_KEY);
+ out.writeUTF(exclusionList);
+ }
+ written = true;
+ break;
+ case PAC:
+ ProxyInfo proxyPacProperties = config.getHttpProxy();
+ out.writeUTF(PROXY_SETTINGS_KEY);
+ out.writeUTF(config.getProxySettings().toString());
+ out.writeUTF(PROXY_PAC_FILE);
+ out.writeUTF(proxyPacProperties.getPacFileUrl().toString());
+ written = true;
+ break;
+ case NONE:
+ out.writeUTF(PROXY_SETTINGS_KEY);
+ out.writeUTF(config.getProxySettings().toString());
+ written = true;
+ break;
+ case UNASSIGNED:
+ /* Ignore */
+ break;
+ default:
+ loge("Ignore invalid proxy settings while writing");
+ break;
+ }
+
+ if (written) {
+ out.writeUTF(ID_KEY);
+ if (version < 3) {
+ out.writeInt(Integer.valueOf(configKey));
+ } else {
+ out.writeUTF(configKey);
+ }
+ }
+ } catch (NullPointerException e) {
+ loge("Failure in writing " + config + e);
+ }
+ out.writeUTF(EOS);
+
+ return written;
+ }
+
+ /**
+ * @deprecated use {@link #writeIpConfigurations(String, ArrayMap)} instead.
+ * New method uses string as network identifier which could be interface name or MAC address or
+ * other token.
+ */
+ @Deprecated
+ public void writeIpAndProxyConfigurationsToFile(String filePath,
+ final SparseArray<IpConfiguration> networks) {
+ mWriter.write(filePath, out -> {
+ out.writeInt(IPCONFIG_FILE_VERSION);
+ for (int i = 0; i < networks.size(); i++) {
+ writeConfig(out, String.valueOf(networks.keyAt(i)), networks.valueAt(i));
+ }
+ });
+ }
+
+ /**
+ * Write the IP configuration associated to the target networks to the destination path.
+ */
+ public void writeIpConfigurations(String filePath,
+ ArrayMap<String, IpConfiguration> networks) {
+ mWriter.write(filePath, out -> {
+ out.writeInt(IPCONFIG_FILE_VERSION);
+ for (int i = 0; i < networks.size(); i++) {
+ writeConfig(out, networks.keyAt(i), networks.valueAt(i));
+ }
+ });
+ }
+
+ /**
+ * Read the IP configuration from the destination path to {@link BufferedInputStream}.
+ */
+ public static ArrayMap<String, IpConfiguration> readIpConfigurations(String filePath) {
+ BufferedInputStream bufferedInputStream;
+ try {
+ bufferedInputStream = new BufferedInputStream(new FileInputStream(filePath));
+ } catch (FileNotFoundException e) {
+ // Return an empty array here because callers expect an empty array when the file is
+ // not present.
+ loge("Error opening configuration file: " + e);
+ return new ArrayMap<>(0);
+ }
+ return readIpConfigurations(bufferedInputStream);
+ }
+
+ /** @deprecated use {@link #readIpConfigurations(String)} */
+ @Deprecated
+ public static SparseArray<IpConfiguration> readIpAndProxyConfigurations(String filePath) {
+ BufferedInputStream bufferedInputStream;
+ try {
+ bufferedInputStream = new BufferedInputStream(new FileInputStream(filePath));
+ } catch (FileNotFoundException e) {
+ // Return an empty array here because callers expect an empty array when the file is
+ // not present.
+ loge("Error opening configuration file: " + e);
+ return new SparseArray<>();
+ }
+ return readIpAndProxyConfigurations(bufferedInputStream);
+ }
+
+ /** @deprecated use {@link #readIpConfigurations(InputStream)} */
+ @Deprecated
+ public static SparseArray<IpConfiguration> readIpAndProxyConfigurations(
+ InputStream inputStream) {
+ ArrayMap<String, IpConfiguration> networks = readIpConfigurations(inputStream);
+ if (networks == null) {
+ return null;
+ }
+
+ SparseArray<IpConfiguration> networksById = new SparseArray<>();
+ for (int i = 0; i < networks.size(); i++) {
+ int id = Integer.valueOf(networks.keyAt(i));
+ networksById.put(id, networks.valueAt(i));
+ }
+
+ return networksById;
+ }
+
+ /** Returns a map of network identity token and {@link IpConfiguration}. */
+ public static ArrayMap<String, IpConfiguration> readIpConfigurations(
+ InputStream inputStream) {
+ ArrayMap<String, IpConfiguration> networks = new ArrayMap<>();
+ DataInputStream in = null;
+ try {
+ in = new DataInputStream(inputStream);
+
+ int version = in.readInt();
+ if (version != 3 && version != 2 && version != 1) {
+ loge("Bad version on IP configuration file, ignore read");
+ return null;
+ }
+
+ while (true) {
+ String uniqueToken = null;
+ // Default is DHCP with no proxy
+ IpAssignment ipAssignment = IpAssignment.DHCP;
+ ProxySettings proxySettings = ProxySettings.NONE;
+ StaticIpConfiguration staticIpConfiguration = new StaticIpConfiguration();
+ LinkAddress linkAddress = null;
+ InetAddress gatewayAddress = null;
+ String proxyHost = null;
+ String pacFileUrl = null;
+ int proxyPort = -1;
+ String exclusionList = null;
+ String key;
+ final List<InetAddress> dnsServers = new ArrayList<>();
+
+ do {
+ key = in.readUTF();
+ try {
+ if (key.equals(ID_KEY)) {
+ if (version < 3) {
+ int id = in.readInt();
+ uniqueToken = String.valueOf(id);
+ } else {
+ uniqueToken = in.readUTF();
+ }
+ } else if (key.equals(IP_ASSIGNMENT_KEY)) {
+ ipAssignment = IpAssignment.valueOf(in.readUTF());
+ } else if (key.equals(LINK_ADDRESS_KEY)) {
+ LinkAddress parsedLinkAddress =
+ new LinkAddress(
+ InetAddresses.parseNumericAddress(in.readUTF()),
+ in.readInt());
+ if (parsedLinkAddress.getAddress() instanceof Inet4Address
+ && linkAddress == null) {
+ linkAddress = parsedLinkAddress;
+ } else {
+ loge("Non-IPv4 or duplicate address: " + parsedLinkAddress);
+ }
+ } else if (key.equals(GATEWAY_KEY)) {
+ LinkAddress dest = null;
+ InetAddress gateway = null;
+ if (version == 1) {
+ // only supported default gateways - leave the dest/prefix empty
+ gateway = InetAddresses.parseNumericAddress(in.readUTF());
+ if (gatewayAddress == null) {
+ gatewayAddress = gateway;
+ } else {
+ loge("Duplicate gateway: " + gateway.getHostAddress());
+ }
+ } else {
+ if (in.readInt() == 1) {
+ dest =
+ new LinkAddress(
+ InetAddresses.parseNumericAddress(in.readUTF()),
+ in.readInt());
+ }
+ if (in.readInt() == 1) {
+ gateway = InetAddresses.parseNumericAddress(in.readUTF());
+ }
+ // If the destination is a default IPv4 route, use the gateway
+ // address unless already set. If there is no destination, assume
+ // it is default route and use the gateway address in all cases.
+ if (dest == null) {
+ gatewayAddress = gateway;
+ } else if (dest.getAddress() instanceof Inet4Address
+ && dest.getPrefixLength() == 0 && gatewayAddress == null) {
+ gatewayAddress = gateway;
+ } else {
+ loge("Non-IPv4 default or duplicate route: "
+ + dest.getAddress());
+ }
+ }
+ } else if (key.equals(DNS_KEY)) {
+ dnsServers.add(InetAddresses.parseNumericAddress(in.readUTF()));
+ } else if (key.equals(PROXY_SETTINGS_KEY)) {
+ proxySettings = ProxySettings.valueOf(in.readUTF());
+ } else if (key.equals(PROXY_HOST_KEY)) {
+ proxyHost = in.readUTF();
+ } else if (key.equals(PROXY_PORT_KEY)) {
+ proxyPort = in.readInt();
+ } else if (key.equals(PROXY_PAC_FILE)) {
+ pacFileUrl = in.readUTF();
+ } else if (key.equals(EXCLUSION_LIST_KEY)) {
+ exclusionList = in.readUTF();
+ } else if (key.equals(EOS)) {
+ break;
+ } else {
+ loge("Ignore unknown key " + key + "while reading");
+ }
+ } catch (IllegalArgumentException e) {
+ loge("Ignore invalid address while reading" + e);
+ }
+ } while (true);
+
+ staticIpConfiguration = new StaticIpConfiguration.Builder()
+ .setIpAddress(linkAddress)
+ .setGateway(gatewayAddress)
+ .setDnsServers(dnsServers)
+ .build();
+
+ if (uniqueToken != null) {
+ IpConfiguration config = new IpConfiguration();
+ networks.put(uniqueToken, config);
+
+ switch (ipAssignment) {
+ case STATIC:
+ config.setStaticIpConfiguration(staticIpConfiguration);
+ config.setIpAssignment(ipAssignment);
+ break;
+ case DHCP:
+ config.setIpAssignment(ipAssignment);
+ break;
+ case UNASSIGNED:
+ loge("BUG: Found UNASSIGNED IP on file, use DHCP");
+ config.setIpAssignment(IpAssignment.DHCP);
+ break;
+ default:
+ loge("Ignore invalid ip assignment while reading.");
+ config.setIpAssignment(IpAssignment.UNASSIGNED);
+ break;
+ }
+
+ switch (proxySettings) {
+ case STATIC:
+ ProxyInfo proxyInfo = ProxyInfo.buildDirectProxy(proxyHost, proxyPort,
+ ProxyUtils.exclusionStringAsList(exclusionList));
+ config.setProxySettings(proxySettings);
+ config.setHttpProxy(proxyInfo);
+ break;
+ case PAC:
+ ProxyInfo proxyPacProperties =
+ ProxyInfo.buildPacProxy(Uri.parse(pacFileUrl));
+ config.setProxySettings(proxySettings);
+ config.setHttpProxy(proxyPacProperties);
+ break;
+ case NONE:
+ config.setProxySettings(proxySettings);
+ break;
+ case UNASSIGNED:
+ loge("BUG: Found UNASSIGNED proxy on file, use NONE");
+ config.setProxySettings(ProxySettings.NONE);
+ break;
+ default:
+ loge("Ignore invalid proxy settings while reading");
+ config.setProxySettings(ProxySettings.UNASSIGNED);
+ break;
+ }
+ } else {
+ if (DBG) log("Missing id while parsing configuration");
+ }
+ }
+ } catch (EOFException ignore) {
+ } catch (IOException e) {
+ loge("Error parsing configuration: " + e);
+ } finally {
+ if (in != null) {
+ try {
+ in.close();
+ } catch (Exception e) { }
+ }
+ }
+
+ return networks;
+ }
+
+ protected static void loge(String s) {
+ Log.e(TAG, s);
+ }
+
+ protected static void log(String s) {
+ Log.d(TAG, s);
+ }
+}
diff --git a/service-t/src/com/android/server/net/NetworkStatsFactory.java b/service-t/src/com/android/server/net/NetworkStatsFactory.java
new file mode 100644
index 0000000..3b93f1a
--- /dev/null
+++ b/service-t/src/com/android/server/net/NetworkStatsFactory.java
@@ -0,0 +1,505 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net;
+
+import static android.net.NetworkStats.INTERFACES_ALL;
+import static android.net.NetworkStats.SET_ALL;
+import static android.net.NetworkStats.TAG_ALL;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkStats.UID_ALL;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.net.NetworkStats;
+import android.net.UnderlyingNetworkInfo;
+import android.os.ServiceSpecificException;
+import android.os.StrictMode;
+import android.os.SystemClock;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ProcFileReader;
+import com.android.net.module.util.CollectionUtils;
+import com.android.server.BpfNetMaps;
+
+import libcore.io.IoUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.ProtocolException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Creates {@link NetworkStats} instances by parsing various {@code /proc/}
+ * files as needed.
+ *
+ * @hide
+ */
+public class NetworkStatsFactory {
+ static {
+ System.loadLibrary("service-connectivity");
+ }
+
+ private static final String TAG = "NetworkStatsFactory";
+
+ private static final boolean USE_NATIVE_PARSING = true;
+ private static final boolean VALIDATE_NATIVE_STATS = false;
+
+ /** Path to {@code /proc/net/xt_qtaguid/iface_stat_all}. */
+ private final File mStatsXtIfaceAll;
+ /** Path to {@code /proc/net/xt_qtaguid/iface_stat_fmt}. */
+ private final File mStatsXtIfaceFmt;
+ /** Path to {@code /proc/net/xt_qtaguid/stats}. */
+ private final File mStatsXtUid;
+
+ private final boolean mUseBpfStats;
+
+ private final Context mContext;
+
+ private final BpfNetMaps mBpfNetMaps;
+
+ /**
+ * Guards persistent data access in this class
+ *
+ * <p>In order to prevent deadlocks, critical sections protected by this lock SHALL NOT call out
+ * to other code that will acquire other locks within the system server. See b/134244752.
+ */
+ private final Object mPersistentDataLock = new Object();
+
+ /** Set containing info about active VPNs and their underlying networks. */
+ private volatile UnderlyingNetworkInfo[] mUnderlyingNetworkInfos = new UnderlyingNetworkInfo[0];
+
+ // A persistent snapshot of cumulative stats since device start
+ @GuardedBy("mPersistentDataLock")
+ private NetworkStats mPersistSnapshot;
+
+ // The persistent snapshot of tun and 464xlat adjusted stats since device start
+ @GuardedBy("mPersistentDataLock")
+ private NetworkStats mTunAnd464xlatAdjustedStats;
+
+ /**
+ * (Stacked interface) -> (base interface) association for all connected ifaces since boot.
+ *
+ * Because counters must never roll backwards, once a given interface is stacked on top of an
+ * underlying interface, the stacked interface can never be stacked on top of
+ * another interface. */
+ private final ConcurrentHashMap<String, String> mStackedIfaces
+ = new ConcurrentHashMap<>();
+
+ /** Informs the factory of a new stacked interface. */
+ public void noteStackedIface(String stackedIface, String baseIface) {
+ if (stackedIface != null && baseIface != null) {
+ mStackedIfaces.put(stackedIface, baseIface);
+ }
+ }
+
+ /**
+ * Set active VPN information for data usage migration purposes
+ *
+ * <p>Traffic on TUN-based VPNs inherently all appear to be originated from the VPN providing
+ * app's UID. This method is used to support migration of VPN data usage, ensuring data is
+ * accurately billed to the real owner of the traffic.
+ *
+ * @param vpnArray The snapshot of the currently-running VPNs.
+ */
+ public void updateUnderlyingNetworkInfos(UnderlyingNetworkInfo[] vpnArray) {
+ mUnderlyingNetworkInfos = vpnArray.clone();
+ }
+
+ /**
+ * Get a set of interfaces containing specified ifaces and stacked interfaces.
+ *
+ * <p>The added stacked interfaces are ifaces stacked on top of the specified ones, or ifaces
+ * on which the specified ones are stacked. Stacked interfaces are those noted with
+ * {@link #noteStackedIface(String, String)}, but only interfaces noted before this method
+ * is called are guaranteed to be included.
+ */
+ public String[] augmentWithStackedInterfaces(@Nullable String[] requiredIfaces) {
+ if (requiredIfaces == NetworkStats.INTERFACES_ALL) {
+ return null;
+ }
+
+ HashSet<String> relatedIfaces = new HashSet<>(Arrays.asList(requiredIfaces));
+ // ConcurrentHashMap's EntrySet iterators are "guaranteed to traverse
+ // elements as they existed upon construction exactly once, and may
+ // (but are not guaranteed to) reflect any modifications subsequent to construction".
+ // This is enough here.
+ for (Map.Entry<String, String> entry : mStackedIfaces.entrySet()) {
+ if (relatedIfaces.contains(entry.getKey())) {
+ relatedIfaces.add(entry.getValue());
+ } else if (relatedIfaces.contains(entry.getValue())) {
+ relatedIfaces.add(entry.getKey());
+ }
+ }
+
+ String[] outArray = new String[relatedIfaces.size()];
+ return relatedIfaces.toArray(outArray);
+ }
+
+ /**
+ * Applies 464xlat adjustments with ifaces noted with {@link #noteStackedIface(String, String)}.
+ * @see NetworkStats#apply464xlatAdjustments(NetworkStats, NetworkStats, Map)
+ */
+ public void apply464xlatAdjustments(NetworkStats baseTraffic, NetworkStats stackedTraffic) {
+ NetworkStats.apply464xlatAdjustments(baseTraffic, stackedTraffic, mStackedIfaces);
+ }
+
+ public NetworkStatsFactory(@NonNull Context ctx) {
+ this(ctx, new File("/proc/"), true);
+ }
+
+ @VisibleForTesting
+ public NetworkStatsFactory(@NonNull Context ctx, File procRoot, boolean useBpfStats) {
+ mStatsXtIfaceAll = new File(procRoot, "net/xt_qtaguid/iface_stat_all");
+ mStatsXtIfaceFmt = new File(procRoot, "net/xt_qtaguid/iface_stat_fmt");
+ mStatsXtUid = new File(procRoot, "net/xt_qtaguid/stats");
+ mUseBpfStats = useBpfStats;
+ mBpfNetMaps = new BpfNetMaps();
+ synchronized (mPersistentDataLock) {
+ mPersistSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), -1);
+ mTunAnd464xlatAdjustedStats = new NetworkStats(SystemClock.elapsedRealtime(), -1);
+ }
+ mContext = ctx;
+ }
+
+ public NetworkStats readBpfNetworkStatsDev() throws IOException {
+ final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6);
+ if (nativeReadNetworkStatsDev(stats) != 0) {
+ throw new IOException("Failed to parse bpf iface stats");
+ }
+ return stats;
+ }
+
+ /**
+ * Parse and return interface-level summary {@link NetworkStats} measured
+ * using {@code /proc/net/dev} style hooks, which may include non IP layer
+ * traffic. Values monotonically increase since device boot, and may include
+ * details about inactive interfaces.
+ *
+ * @throws IllegalStateException when problem parsing stats.
+ */
+ public NetworkStats readNetworkStatsSummaryDev() throws IOException {
+
+ // Return xt_bpf stats if switched to bpf module.
+ if (mUseBpfStats)
+ return readBpfNetworkStatsDev();
+
+ final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
+
+ final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6);
+ final NetworkStats.Entry entry = new NetworkStats.Entry();
+
+ ProcFileReader reader = null;
+ try {
+ reader = new ProcFileReader(new FileInputStream(mStatsXtIfaceAll));
+
+ while (reader.hasMoreData()) {
+ entry.iface = reader.nextString();
+ entry.uid = UID_ALL;
+ entry.set = SET_ALL;
+ entry.tag = TAG_NONE;
+
+ final boolean active = reader.nextInt() != 0;
+
+ // always include snapshot values
+ entry.rxBytes = reader.nextLong();
+ entry.rxPackets = reader.nextLong();
+ entry.txBytes = reader.nextLong();
+ entry.txPackets = reader.nextLong();
+
+ // fold in active numbers, but only when active
+ if (active) {
+ entry.rxBytes += reader.nextLong();
+ entry.rxPackets += reader.nextLong();
+ entry.txBytes += reader.nextLong();
+ entry.txPackets += reader.nextLong();
+ }
+
+ stats.insertEntry(entry);
+ reader.finishLine();
+ }
+ } catch (NullPointerException|NumberFormatException e) {
+ throw protocolExceptionWithCause("problem parsing stats", e);
+ } finally {
+ IoUtils.closeQuietly(reader);
+ StrictMode.setThreadPolicy(savedPolicy);
+ }
+ return stats;
+ }
+
+ /**
+ * Parse and return interface-level summary {@link NetworkStats}. Designed
+ * to return only IP layer traffic. Values monotonically increase since
+ * device boot, and may include details about inactive interfaces.
+ *
+ * @throws IllegalStateException when problem parsing stats.
+ */
+ public NetworkStats readNetworkStatsSummaryXt() throws IOException {
+
+ // Return xt_bpf stats if qtaguid module is replaced.
+ if (mUseBpfStats)
+ return readBpfNetworkStatsDev();
+
+ final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
+
+ // return null when kernel doesn't support
+ if (!mStatsXtIfaceFmt.exists()) return null;
+
+ final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6);
+ final NetworkStats.Entry entry = new NetworkStats.Entry();
+
+ ProcFileReader reader = null;
+ try {
+ // open and consume header line
+ reader = new ProcFileReader(new FileInputStream(mStatsXtIfaceFmt));
+ reader.finishLine();
+
+ while (reader.hasMoreData()) {
+ entry.iface = reader.nextString();
+ entry.uid = UID_ALL;
+ entry.set = SET_ALL;
+ entry.tag = TAG_NONE;
+
+ entry.rxBytes = reader.nextLong();
+ entry.rxPackets = reader.nextLong();
+ entry.txBytes = reader.nextLong();
+ entry.txPackets = reader.nextLong();
+
+ stats.insertEntry(entry);
+ reader.finishLine();
+ }
+ } catch (NullPointerException|NumberFormatException e) {
+ throw protocolExceptionWithCause("problem parsing stats", e);
+ } finally {
+ IoUtils.closeQuietly(reader);
+ StrictMode.setThreadPolicy(savedPolicy);
+ }
+ return stats;
+ }
+
+ public NetworkStats readNetworkStatsDetail() throws IOException {
+ return readNetworkStatsDetail(UID_ALL, INTERFACES_ALL, TAG_ALL);
+ }
+
+ @GuardedBy("mPersistentDataLock")
+ private void requestSwapActiveStatsMapLocked() throws IOException {
+ try {
+ // Do a active map stats swap. Once the swap completes, this code
+ // can read and clean the inactive map without races.
+ mBpfNetMaps.swapActiveStatsMap();
+ } catch (ServiceSpecificException e) {
+ throw new IOException(e);
+ }
+ }
+
+ /**
+ * Reads the detailed UID stats based on the provided parameters
+ *
+ * @param limitUid the UID to limit this query to
+ * @param limitIfaces the interfaces to limit this query to. Use {@link
+ * NetworkStats.INTERFACES_ALL} to select all interfaces
+ * @param limitTag the tags to limit this query to
+ * @return the NetworkStats instance containing network statistics at the present time.
+ */
+ public NetworkStats readNetworkStatsDetail(
+ int limitUid, String[] limitIfaces, int limitTag) throws IOException {
+ // In order to prevent deadlocks, anything protected by this lock MUST NOT call out to other
+ // code that will acquire other locks within the system server. See b/134244752.
+ synchronized (mPersistentDataLock) {
+ // Take a reference. If this gets swapped out, we still have the old reference.
+ final UnderlyingNetworkInfo[] vpnArray = mUnderlyingNetworkInfos;
+ // Take a defensive copy. mPersistSnapshot is mutated in some cases below
+ final NetworkStats prev = mPersistSnapshot.clone();
+
+ if (USE_NATIVE_PARSING) {
+ final NetworkStats stats =
+ new NetworkStats(SystemClock.elapsedRealtime(), 0 /* initialSize */);
+ if (mUseBpfStats) {
+ requestSwapActiveStatsMapLocked();
+ // Stats are always read from the inactive map, so they must be read after the
+ // swap
+ if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), UID_ALL,
+ INTERFACES_ALL, TAG_ALL, mUseBpfStats) != 0) {
+ throw new IOException("Failed to parse network stats");
+ }
+
+ // BPF stats are incremental; fold into mPersistSnapshot.
+ mPersistSnapshot.setElapsedRealtime(stats.getElapsedRealtime());
+ mPersistSnapshot.combineAllValues(stats);
+ } else {
+ if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), UID_ALL,
+ INTERFACES_ALL, TAG_ALL, mUseBpfStats) != 0) {
+ throw new IOException("Failed to parse network stats");
+ }
+ if (VALIDATE_NATIVE_STATS) {
+ final NetworkStats javaStats = javaReadNetworkStatsDetail(mStatsXtUid,
+ UID_ALL, INTERFACES_ALL, TAG_ALL);
+ assertEquals(javaStats, stats);
+ }
+
+ mPersistSnapshot = stats;
+ }
+ } else {
+ mPersistSnapshot = javaReadNetworkStatsDetail(mStatsXtUid, UID_ALL, INTERFACES_ALL,
+ TAG_ALL);
+ }
+
+ NetworkStats adjustedStats = adjustForTunAnd464Xlat(mPersistSnapshot, prev, vpnArray);
+
+ // Filter return values
+ adjustedStats.filter(limitUid, limitIfaces, limitTag);
+ return adjustedStats;
+ }
+ }
+
+ @GuardedBy("mPersistentDataLock")
+ private NetworkStats adjustForTunAnd464Xlat(NetworkStats uidDetailStats,
+ NetworkStats previousStats, UnderlyingNetworkInfo[] vpnArray) {
+ // Calculate delta from last snapshot
+ final NetworkStats delta = uidDetailStats.subtract(previousStats);
+
+ // Apply 464xlat adjustments before VPN adjustments. If VPNs are using v4 on a v6 only
+ // network, the overhead is their fault.
+ // No locking here: apply464xlatAdjustments behaves fine with an add-only
+ // ConcurrentHashMap.
+ delta.apply464xlatAdjustments(mStackedIfaces);
+
+ // Migrate data usage over a VPN to the TUN network.
+ for (UnderlyingNetworkInfo info : vpnArray) {
+ delta.migrateTun(info.getOwnerUid(), info.getInterface(),
+ info.getUnderlyingInterfaces());
+ // Filter out debug entries as that may lead to over counting.
+ delta.filterDebugEntries();
+ }
+
+ // Update mTunAnd464xlatAdjustedStats with migrated delta.
+ mTunAnd464xlatAdjustedStats.combineAllValues(delta);
+ mTunAnd464xlatAdjustedStats.setElapsedRealtime(uidDetailStats.getElapsedRealtime());
+
+ return mTunAnd464xlatAdjustedStats.clone();
+ }
+
+ /**
+ * Parse and return {@link NetworkStats} with UID-level details. Values are
+ * expected to monotonically increase since device boot.
+ */
+ @VisibleForTesting
+ public static NetworkStats javaReadNetworkStatsDetail(File detailPath, int limitUid,
+ String[] limitIfaces, int limitTag)
+ throws IOException {
+ final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
+
+ final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 24);
+ final NetworkStats.Entry entry = new NetworkStats.Entry();
+
+ int idx = 1;
+ int lastIdx = 1;
+
+ ProcFileReader reader = null;
+ try {
+ // open and consume header line
+ reader = new ProcFileReader(new FileInputStream(detailPath));
+ reader.finishLine();
+
+ while (reader.hasMoreData()) {
+ idx = reader.nextInt();
+ if (idx != lastIdx + 1) {
+ throw new ProtocolException(
+ "inconsistent idx=" + idx + " after lastIdx=" + lastIdx);
+ }
+ lastIdx = idx;
+
+ entry.iface = reader.nextString();
+ entry.tag = kernelToTag(reader.nextString());
+ entry.uid = reader.nextInt();
+ entry.set = reader.nextInt();
+ entry.rxBytes = reader.nextLong();
+ entry.rxPackets = reader.nextLong();
+ entry.txBytes = reader.nextLong();
+ entry.txPackets = reader.nextLong();
+
+ if ((limitIfaces == null || CollectionUtils.contains(limitIfaces, entry.iface))
+ && (limitUid == UID_ALL || limitUid == entry.uid)
+ && (limitTag == TAG_ALL || limitTag == entry.tag)) {
+ stats.insertEntry(entry);
+ }
+
+ reader.finishLine();
+ }
+ } catch (NullPointerException|NumberFormatException e) {
+ throw protocolExceptionWithCause("problem parsing idx " + idx, e);
+ } finally {
+ IoUtils.closeQuietly(reader);
+ StrictMode.setThreadPolicy(savedPolicy);
+ }
+
+ return stats;
+ }
+
+ public void assertEquals(NetworkStats expected, NetworkStats actual) {
+ if (expected.size() != actual.size()) {
+ throw new AssertionError(
+ "Expected size " + expected.size() + ", actual size " + actual.size());
+ }
+
+ NetworkStats.Entry expectedRow = null;
+ NetworkStats.Entry actualRow = null;
+ for (int i = 0; i < expected.size(); i++) {
+ expectedRow = expected.getValues(i, expectedRow);
+ actualRow = actual.getValues(i, actualRow);
+ if (!expectedRow.equals(actualRow)) {
+ throw new AssertionError(
+ "Expected row " + i + ": " + expectedRow + ", actual row " + actualRow);
+ }
+ }
+ }
+
+ /**
+ * Convert {@code /proc/} tag format to {@link Integer}. Assumes incoming
+ * format like {@code 0x7fffffff00000000}.
+ */
+ public static int kernelToTag(String string) {
+ int length = string.length();
+ if (length > 10) {
+ return Long.decode(string.substring(0, length - 8)).intValue();
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Parse statistics from file into given {@link NetworkStats} object. Values
+ * are expected to monotonically increase since device boot.
+ */
+ @VisibleForTesting
+ public static native int nativeReadNetworkStatsDetail(NetworkStats stats, String path,
+ int limitUid, String[] limitIfaces, int limitTag, boolean useBpfStats);
+
+ @VisibleForTesting
+ public static native int nativeReadNetworkStatsDev(NetworkStats stats);
+
+ private static ProtocolException protocolExceptionWithCause(String message, Throwable cause) {
+ ProtocolException pe = new ProtocolException(message);
+ pe.initCause(cause);
+ return pe;
+ }
+}
diff --git a/service-t/src/com/android/server/net/NetworkStatsObservers.java b/service-t/src/com/android/server/net/NetworkStatsObservers.java
new file mode 100644
index 0000000..1cd670a
--- /dev/null
+++ b/service-t/src/com/android/server/net/NetworkStatsObservers.java
@@ -0,0 +1,482 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net;
+
+import static android.app.usage.NetworkStatsManager.MIN_THRESHOLD_BYTES;
+
+import android.annotation.NonNull;
+import android.app.usage.NetworkStatsManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.DataUsageRequest;
+import android.net.NetworkIdentitySet;
+import android.net.NetworkStack;
+import android.net.NetworkStats;
+import android.net.NetworkStatsAccess;
+import android.net.NetworkStatsCollection;
+import android.net.NetworkStatsHistory;
+import android.net.NetworkTemplate;
+import android.net.netstats.IUsageCallback;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.PerUidCounter;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Manages observers of {@link NetworkStats}. Allows observers to be notified when
+ * data usage has been reported in {@link NetworkStatsService}. An observer can set
+ * a threshold of how much data it cares about to be notified.
+ */
+class NetworkStatsObservers {
+ private static final String TAG = "NetworkStatsObservers";
+ private static final boolean LOG = true;
+ private static final boolean LOGV = false;
+
+ private static final int MSG_REGISTER = 1;
+ private static final int MSG_UNREGISTER = 2;
+ private static final int MSG_UPDATE_STATS = 3;
+
+ private static final int DUMP_USAGE_REQUESTS_COUNT = 200;
+
+ // The maximum number of request allowed per uid before an exception is thrown.
+ @VisibleForTesting
+ static final int MAX_REQUESTS_PER_UID = 100;
+
+ // All access to this map must be done from the handler thread.
+ // indexed by DataUsageRequest#requestId
+ private final SparseArray<RequestInfo> mDataUsageRequests = new SparseArray<>();
+
+ // Request counters per uid, this is thread safe.
+ private final PerUidCounter mDataUsageRequestsPerUid = new PerUidCounter(MAX_REQUESTS_PER_UID);
+
+ // Sequence number of DataUsageRequests
+ private final AtomicInteger mNextDataUsageRequestId = new AtomicInteger();
+
+ // Lazily instantiated when an observer is registered.
+ private volatile Handler mHandler;
+
+ /**
+ * Creates a wrapper that contains the caller context and a normalized request.
+ * The request should be returned to the caller app, and the wrapper should be sent to this
+ * object through #addObserver by the service handler.
+ *
+ * <p>It will register the observer asynchronously, so it is safe to call from any thread.
+ *
+ * @return the normalized request wrapped within {@link RequestInfo}.
+ */
+ public DataUsageRequest register(@NonNull Context context,
+ @NonNull DataUsageRequest inputRequest, @NonNull IUsageCallback callback,
+ int callingPid, int callingUid, @NonNull String callingPackage,
+ @NetworkStatsAccess.Level int accessLevel) {
+ DataUsageRequest request = buildRequest(context, inputRequest, callingUid);
+ RequestInfo requestInfo = buildRequestInfo(request, callback, callingPid, callingUid,
+ callingPackage, accessLevel);
+ if (LOG) Log.d(TAG, "Registering observer for " + requestInfo);
+ mDataUsageRequestsPerUid.incrementCountOrThrow(callingUid);
+
+ getHandler().sendMessage(mHandler.obtainMessage(MSG_REGISTER, requestInfo));
+ return request;
+ }
+
+ /**
+ * Unregister a data usage observer.
+ *
+ * <p>It will unregister the observer asynchronously, so it is safe to call from any thread.
+ */
+ public void unregister(DataUsageRequest request, int callingUid) {
+ getHandler().sendMessage(mHandler.obtainMessage(MSG_UNREGISTER, callingUid, 0 /* ignore */,
+ request));
+ }
+
+ /**
+ * Updates data usage statistics of registered observers and notifies if limits are reached.
+ *
+ * <p>It will update stats asynchronously, so it is safe to call from any thread.
+ */
+ public void updateStats(NetworkStats xtSnapshot, NetworkStats uidSnapshot,
+ ArrayMap<String, NetworkIdentitySet> activeIfaces,
+ ArrayMap<String, NetworkIdentitySet> activeUidIfaces,
+ long currentTime) {
+ StatsContext statsContext = new StatsContext(xtSnapshot, uidSnapshot, activeIfaces,
+ activeUidIfaces, currentTime);
+ getHandler().sendMessage(mHandler.obtainMessage(MSG_UPDATE_STATS, statsContext));
+ }
+
+ private Handler getHandler() {
+ if (mHandler == null) {
+ synchronized (this) {
+ if (mHandler == null) {
+ if (LOGV) Log.v(TAG, "Creating handler");
+ mHandler = new Handler(getHandlerLooperLocked(), mHandlerCallback);
+ }
+ }
+ }
+ return mHandler;
+ }
+
+ @VisibleForTesting
+ protected Looper getHandlerLooperLocked() {
+ HandlerThread handlerThread = new HandlerThread(TAG);
+ handlerThread.start();
+ return handlerThread.getLooper();
+ }
+
+ private Handler.Callback mHandlerCallback = new Handler.Callback() {
+ @Override
+ public boolean handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_REGISTER: {
+ handleRegister((RequestInfo) msg.obj);
+ return true;
+ }
+ case MSG_UNREGISTER: {
+ handleUnregister((DataUsageRequest) msg.obj, msg.arg1 /* callingUid */);
+ return true;
+ }
+ case MSG_UPDATE_STATS: {
+ handleUpdateStats((StatsContext) msg.obj);
+ return true;
+ }
+ default: {
+ return false;
+ }
+ }
+ }
+ };
+
+ /**
+ * Adds a {@link RequestInfo} as an observer.
+ * Should only be called from the handler thread otherwise there will be a race condition
+ * on mDataUsageRequests.
+ */
+ private void handleRegister(RequestInfo requestInfo) {
+ mDataUsageRequests.put(requestInfo.mRequest.requestId, requestInfo);
+ }
+
+ /**
+ * Removes a {@link DataUsageRequest} if the calling uid is authorized.
+ * Should only be called from the handler thread otherwise there will be a race condition
+ * on mDataUsageRequests.
+ */
+ private void handleUnregister(DataUsageRequest request, int callingUid) {
+ RequestInfo requestInfo;
+ requestInfo = mDataUsageRequests.get(request.requestId);
+ if (requestInfo == null) {
+ if (LOG) Log.d(TAG, "Trying to unregister unknown request " + request);
+ return;
+ }
+ if (Process.SYSTEM_UID != callingUid && requestInfo.mCallingUid != callingUid) {
+ Log.w(TAG, "Caller uid " + callingUid + " is not owner of " + request);
+ return;
+ }
+
+ if (LOG) Log.d(TAG, "Unregistering " + requestInfo);
+ mDataUsageRequests.remove(request.requestId);
+ mDataUsageRequestsPerUid.decrementCountOrThrow(requestInfo.mCallingUid);
+ requestInfo.unlinkDeathRecipient();
+ requestInfo.callCallback(NetworkStatsManager.CALLBACK_RELEASED);
+ }
+
+ private void handleUpdateStats(StatsContext statsContext) {
+ if (mDataUsageRequests.size() == 0) {
+ return;
+ }
+
+ for (int i = 0; i < mDataUsageRequests.size(); i++) {
+ RequestInfo requestInfo = mDataUsageRequests.valueAt(i);
+ requestInfo.updateStats(statsContext);
+ }
+ }
+
+ private DataUsageRequest buildRequest(Context context, DataUsageRequest request,
+ int callingUid) {
+ // For non-NETWORK_STACK permission uid, cap the minimum threshold to a safe default to
+ // avoid too many callbacks.
+ final long thresholdInBytes = (context.checkPermission(
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, Process.myPid(), callingUid)
+ == PackageManager.PERMISSION_GRANTED ? request.thresholdInBytes
+ : Math.max(MIN_THRESHOLD_BYTES, request.thresholdInBytes));
+ if (thresholdInBytes > request.thresholdInBytes) {
+ Log.w(TAG, "Threshold was too low for " + request
+ + ". Overriding to a safer default of " + thresholdInBytes + " bytes");
+ }
+ return new DataUsageRequest(mNextDataUsageRequestId.incrementAndGet(),
+ request.template, thresholdInBytes);
+ }
+
+ private RequestInfo buildRequestInfo(DataUsageRequest request, IUsageCallback callback,
+ int callingPid, int callingUid, @NonNull String callingPackage,
+ @NetworkStatsAccess.Level int accessLevel) {
+ if (accessLevel <= NetworkStatsAccess.Level.USER) {
+ return new UserUsageRequestInfo(this, request, callback, callingPid,
+ callingUid, callingPackage, accessLevel);
+ } else {
+ // Safety check in case a new access level is added and we forgot to update this
+ if (accessLevel < NetworkStatsAccess.Level.DEVICESUMMARY) {
+ throw new IllegalArgumentException(
+ "accessLevel " + accessLevel + " is less than DEVICESUMMARY.");
+ }
+ return new NetworkUsageRequestInfo(this, request, callback, callingPid,
+ callingUid, callingPackage, accessLevel);
+ }
+ }
+
+ /**
+ * Tracks information relevant to a data usage observer.
+ * It will notice when the calling process dies so we can self-expire.
+ */
+ private abstract static class RequestInfo implements IBinder.DeathRecipient {
+ private final NetworkStatsObservers mStatsObserver;
+ protected final DataUsageRequest mRequest;
+ private final IUsageCallback mCallback;
+ protected final int mCallingPid;
+ protected final int mCallingUid;
+ protected final String mCallingPackage;
+ protected final @NetworkStatsAccess.Level int mAccessLevel;
+ protected NetworkStatsRecorder mRecorder;
+ protected NetworkStatsCollection mCollection;
+
+ RequestInfo(NetworkStatsObservers statsObserver, DataUsageRequest request,
+ IUsageCallback callback, int callingPid, int callingUid,
+ @NonNull String callingPackage, @NetworkStatsAccess.Level int accessLevel) {
+ mStatsObserver = statsObserver;
+ mRequest = request;
+ mCallback = callback;
+ mCallingPid = callingPid;
+ mCallingUid = callingUid;
+ mCallingPackage = callingPackage;
+ mAccessLevel = accessLevel;
+
+ try {
+ mCallback.asBinder().linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ binderDied();
+ }
+ }
+
+ @Override
+ public void binderDied() {
+ if (LOGV) {
+ Log.v(TAG, "RequestInfo binderDied(" + mRequest + ", " + mCallback + ")");
+ }
+ mStatsObserver.unregister(mRequest, Process.SYSTEM_UID);
+ callCallback(NetworkStatsManager.CALLBACK_RELEASED);
+ }
+
+ @Override
+ public String toString() {
+ return "RequestInfo from pid/uid:" + mCallingPid + "/" + mCallingUid
+ + "(" + mCallingPackage + ")"
+ + " for " + mRequest + " accessLevel:" + mAccessLevel;
+ }
+
+ private void unlinkDeathRecipient() {
+ mCallback.asBinder().unlinkToDeath(this, 0);
+ }
+
+ /**
+ * Update stats given the samples and interface to identity mappings.
+ */
+ private void updateStats(StatsContext statsContext) {
+ if (mRecorder == null) {
+ // First run; establish baseline stats
+ resetRecorder();
+ recordSample(statsContext);
+ return;
+ }
+ recordSample(statsContext);
+
+ if (checkStats()) {
+ resetRecorder();
+ callCallback(NetworkStatsManager.CALLBACK_LIMIT_REACHED);
+ }
+ }
+
+ private void callCallback(int callbackType) {
+ try {
+ if (LOGV) {
+ Log.v(TAG, "sending notification " + callbackTypeToName(callbackType)
+ + " for " + mRequest);
+ }
+ switch (callbackType) {
+ case NetworkStatsManager.CALLBACK_LIMIT_REACHED:
+ mCallback.onThresholdReached(mRequest);
+ break;
+ case NetworkStatsManager.CALLBACK_RELEASED:
+ mCallback.onCallbackReleased(mRequest);
+ break;
+ }
+ } catch (RemoteException e) {
+ // May occur naturally in the race of binder death.
+ Log.w(TAG, "RemoteException caught trying to send a callback msg for " + mRequest);
+ }
+ }
+
+ private void resetRecorder() {
+ mRecorder = new NetworkStatsRecorder();
+ mCollection = mRecorder.getSinceBoot();
+ }
+
+ protected abstract boolean checkStats();
+
+ protected abstract void recordSample(StatsContext statsContext);
+
+ private String callbackTypeToName(int callbackType) {
+ switch (callbackType) {
+ case NetworkStatsManager.CALLBACK_LIMIT_REACHED:
+ return "LIMIT_REACHED";
+ case NetworkStatsManager.CALLBACK_RELEASED:
+ return "RELEASED";
+ default:
+ return "UNKNOWN";
+ }
+ }
+ }
+
+ private static class NetworkUsageRequestInfo extends RequestInfo {
+ NetworkUsageRequestInfo(NetworkStatsObservers statsObserver, DataUsageRequest request,
+ IUsageCallback callback, int callingPid, int callingUid,
+ @NonNull String callingPackage, @NetworkStatsAccess.Level int accessLevel) {
+ super(statsObserver, request, callback, callingPid, callingUid, callingPackage,
+ accessLevel);
+ }
+
+ @Override
+ protected boolean checkStats() {
+ long bytesSoFar = getTotalBytesForNetwork(mRequest.template);
+ if (LOGV) {
+ Log.v(TAG, bytesSoFar + " bytes so far since notification for "
+ + mRequest.template);
+ }
+ if (bytesSoFar > mRequest.thresholdInBytes) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ protected void recordSample(StatsContext statsContext) {
+ // Recorder does not need to be locked in this context since only the handler
+ // thread will update it. We pass a null VPN array because usage is aggregated by uid
+ // for this snapshot, so VPN traffic can't be reattributed to responsible apps.
+ mRecorder.recordSnapshotLocked(statsContext.mXtSnapshot, statsContext.mActiveIfaces,
+ statsContext.mCurrentTime);
+ }
+
+ /**
+ * Reads stats matching the given template. {@link NetworkStatsCollection} will aggregate
+ * over all buckets, which in this case should be only one since we built it big enough
+ * that it will outlive the caller. If it doesn't, then there will be multiple buckets.
+ */
+ private long getTotalBytesForNetwork(NetworkTemplate template) {
+ NetworkStats stats = mCollection.getSummary(template,
+ Long.MIN_VALUE /* start */, Long.MAX_VALUE /* end */,
+ mAccessLevel, mCallingUid);
+ return stats.getTotalBytes();
+ }
+ }
+
+ private static class UserUsageRequestInfo extends RequestInfo {
+ UserUsageRequestInfo(NetworkStatsObservers statsObserver, DataUsageRequest request,
+ IUsageCallback callback, int callingPid, int callingUid,
+ @NonNull String callingPackage, @NetworkStatsAccess.Level int accessLevel) {
+ super(statsObserver, request, callback, callingPid, callingUid,
+ callingPackage, accessLevel);
+ }
+
+ @Override
+ protected boolean checkStats() {
+ int[] uidsToMonitor = mCollection.getRelevantUids(mAccessLevel, mCallingUid);
+
+ for (int i = 0; i < uidsToMonitor.length; i++) {
+ long bytesSoFar = getTotalBytesForNetworkUid(mRequest.template, uidsToMonitor[i]);
+ if (bytesSoFar > mRequest.thresholdInBytes) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ protected void recordSample(StatsContext statsContext) {
+ // Recorder does not need to be locked in this context since only the handler
+ // thread will update it. We pass the VPN info so VPN traffic is reattributed to
+ // responsible apps.
+ mRecorder.recordSnapshotLocked(statsContext.mUidSnapshot, statsContext.mActiveUidIfaces,
+ statsContext.mCurrentTime);
+ }
+
+ /**
+ * Reads all stats matching the given template and uid. Ther history will likely only
+ * contain one bucket per ident since we build it big enough that it will outlive the
+ * caller lifetime.
+ */
+ private long getTotalBytesForNetworkUid(NetworkTemplate template, int uid) {
+ try {
+ NetworkStatsHistory history = mCollection.getHistory(template, null, uid,
+ NetworkStats.SET_ALL, NetworkStats.TAG_NONE,
+ NetworkStatsHistory.FIELD_ALL,
+ Long.MIN_VALUE /* start */, Long.MAX_VALUE /* end */,
+ mAccessLevel, mCallingUid);
+ return history.getTotalBytes();
+ } catch (SecurityException e) {
+ if (LOGV) {
+ Log.w(TAG, "CallerUid " + mCallingUid + " may have lost access to uid "
+ + uid);
+ }
+ return 0;
+ }
+ }
+ }
+
+ private static class StatsContext {
+ NetworkStats mXtSnapshot;
+ NetworkStats mUidSnapshot;
+ ArrayMap<String, NetworkIdentitySet> mActiveIfaces;
+ ArrayMap<String, NetworkIdentitySet> mActiveUidIfaces;
+ long mCurrentTime;
+
+ StatsContext(NetworkStats xtSnapshot, NetworkStats uidSnapshot,
+ ArrayMap<String, NetworkIdentitySet> activeIfaces,
+ ArrayMap<String, NetworkIdentitySet> activeUidIfaces,
+ long currentTime) {
+ mXtSnapshot = xtSnapshot;
+ mUidSnapshot = uidSnapshot;
+ mActiveIfaces = activeIfaces;
+ mActiveUidIfaces = activeUidIfaces;
+ mCurrentTime = currentTime;
+ }
+ }
+
+ public void dump(IndentingPrintWriter pw) {
+ for (int i = 0; i < Math.min(mDataUsageRequests.size(), DUMP_USAGE_REQUESTS_COUNT); i++) {
+ pw.println(mDataUsageRequests.valueAt(i));
+ }
+ }
+}
diff --git a/service-t/src/com/android/server/net/NetworkStatsRecorder.java b/service-t/src/com/android/server/net/NetworkStatsRecorder.java
new file mode 100644
index 0000000..d99e164
--- /dev/null
+++ b/service-t/src/com/android/server/net/NetworkStatsRecorder.java
@@ -0,0 +1,558 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net;
+
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.TrafficStats.KB_IN_BYTES;
+import static android.net.TrafficStats.MB_IN_BYTES;
+import static android.text.format.DateUtils.YEAR_IN_MILLIS;
+
+import android.annotation.NonNull;
+import android.net.NetworkIdentitySet;
+import android.net.NetworkStats;
+import android.net.NetworkStats.NonMonotonicObserver;
+import android.net.NetworkStatsAccess;
+import android.net.NetworkStatsCollection;
+import android.net.NetworkStatsHistory;
+import android.net.NetworkTemplate;
+import android.net.TrafficStats;
+import android.os.Binder;
+import android.os.DropBoxManager;
+import android.service.NetworkStatsRecorderProto;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.util.FileRotator;
+import com.android.net.module.util.NetworkStatsUtils;
+
+import libcore.io.IoUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Logic to record deltas between periodic {@link NetworkStats} snapshots into
+ * {@link NetworkStatsHistory} that belong to {@link NetworkStatsCollection}.
+ * Keeps pending changes in memory until they pass a specific threshold, in
+ * bytes. Uses {@link FileRotator} for persistence logic if present.
+ * <p>
+ * Not inherently thread safe.
+ */
+public class NetworkStatsRecorder {
+ private static final String TAG = "NetworkStatsRecorder";
+ private static final boolean LOGD = false;
+ private static final boolean LOGV = false;
+
+ private static final String TAG_NETSTATS_DUMP = "netstats_dump";
+
+ /** Dump before deleting in {@link #recoverAndDeleteData()}. */
+ private static final boolean DUMP_BEFORE_DELETE = true;
+
+ private final FileRotator mRotator;
+ private final NonMonotonicObserver<String> mObserver;
+ private final DropBoxManager mDropBox;
+ private final String mCookie;
+
+ private final long mBucketDuration;
+ private final boolean mOnlyTags;
+
+ private long mPersistThresholdBytes = 2 * MB_IN_BYTES;
+ private NetworkStats mLastSnapshot;
+
+ private final NetworkStatsCollection mPending;
+ private final NetworkStatsCollection mSinceBoot;
+
+ private final CombiningRewriter mPendingRewriter;
+
+ private WeakReference<NetworkStatsCollection> mComplete;
+
+ /**
+ * Non-persisted recorder, with only one bucket. Used by {@link NetworkStatsObservers}.
+ */
+ public NetworkStatsRecorder() {
+ mRotator = null;
+ mObserver = null;
+ mDropBox = null;
+ mCookie = null;
+
+ // set the bucket big enough to have all data in one bucket, but allow some
+ // slack to avoid overflow
+ mBucketDuration = YEAR_IN_MILLIS;
+ mOnlyTags = false;
+
+ mPending = null;
+ mSinceBoot = new NetworkStatsCollection(mBucketDuration);
+
+ mPendingRewriter = null;
+ }
+
+ /**
+ * Persisted recorder.
+ */
+ public NetworkStatsRecorder(FileRotator rotator, NonMonotonicObserver<String> observer,
+ DropBoxManager dropBox, String cookie, long bucketDuration, boolean onlyTags) {
+ mRotator = Objects.requireNonNull(rotator, "missing FileRotator");
+ mObserver = Objects.requireNonNull(observer, "missing NonMonotonicObserver");
+ mDropBox = Objects.requireNonNull(dropBox, "missing DropBoxManager");
+ mCookie = cookie;
+
+ mBucketDuration = bucketDuration;
+ mOnlyTags = onlyTags;
+
+ mPending = new NetworkStatsCollection(bucketDuration);
+ mSinceBoot = new NetworkStatsCollection(bucketDuration);
+
+ mPendingRewriter = new CombiningRewriter(mPending);
+ }
+
+ public void setPersistThreshold(long thresholdBytes) {
+ if (LOGV) Log.v(TAG, "setPersistThreshold() with " + thresholdBytes);
+ mPersistThresholdBytes = NetworkStatsUtils.constrain(
+ thresholdBytes, 1 * KB_IN_BYTES, 100 * MB_IN_BYTES);
+ }
+
+ public void resetLocked() {
+ mLastSnapshot = null;
+ if (mPending != null) {
+ mPending.reset();
+ }
+ if (mSinceBoot != null) {
+ mSinceBoot.reset();
+ }
+ if (mComplete != null) {
+ mComplete.clear();
+ }
+ }
+
+ public NetworkStats.Entry getTotalSinceBootLocked(NetworkTemplate template) {
+ return mSinceBoot.getSummary(template, Long.MIN_VALUE, Long.MAX_VALUE,
+ NetworkStatsAccess.Level.DEVICE, Binder.getCallingUid()).getTotal(null);
+ }
+
+ public NetworkStatsCollection getSinceBoot() {
+ return mSinceBoot;
+ }
+
+ public long getBucketDuration() {
+ return mBucketDuration;
+ }
+
+ @NonNull
+ public String getCookie() {
+ return mCookie;
+ }
+
+ /**
+ * Load complete history represented by {@link FileRotator}. Caches
+ * internally as a {@link WeakReference}, and updated with future
+ * {@link #recordSnapshotLocked(NetworkStats, Map, long)} snapshots as long
+ * as reference is valid.
+ */
+ public NetworkStatsCollection getOrLoadCompleteLocked() {
+ Objects.requireNonNull(mRotator, "missing FileRotator");
+ NetworkStatsCollection res = mComplete != null ? mComplete.get() : null;
+ if (res == null) {
+ res = loadLocked(Long.MIN_VALUE, Long.MAX_VALUE);
+ mComplete = new WeakReference<NetworkStatsCollection>(res);
+ }
+ return res;
+ }
+
+ public NetworkStatsCollection getOrLoadPartialLocked(long start, long end) {
+ Objects.requireNonNull(mRotator, "missing FileRotator");
+ NetworkStatsCollection res = mComplete != null ? mComplete.get() : null;
+ if (res == null) {
+ res = loadLocked(start, end);
+ }
+ return res;
+ }
+
+ private NetworkStatsCollection loadLocked(long start, long end) {
+ if (LOGD) Log.d(TAG, "loadLocked() reading from disk for " + mCookie);
+ final NetworkStatsCollection res = new NetworkStatsCollection(mBucketDuration);
+ try {
+ mRotator.readMatching(res, start, end);
+ res.recordCollection(mPending);
+ } catch (IOException e) {
+ Log.wtf(TAG, "problem completely reading network stats", e);
+ recoverAndDeleteData();
+ } catch (OutOfMemoryError e) {
+ Log.wtf(TAG, "problem completely reading network stats", e);
+ recoverAndDeleteData();
+ }
+ return res;
+ }
+
+ /**
+ * Record any delta that occurred since last {@link NetworkStats} snapshot, using the given
+ * {@link Map} to identify network interfaces. First snapshot is considered bootstrap, and is
+ * not counted as delta.
+ */
+ public void recordSnapshotLocked(NetworkStats snapshot,
+ Map<String, NetworkIdentitySet> ifaceIdent, long currentTimeMillis) {
+ final HashSet<String> unknownIfaces = new HashSet<>();
+
+ // skip recording when snapshot missing
+ if (snapshot == null) return;
+
+ // assume first snapshot is bootstrap and don't record
+ if (mLastSnapshot == null) {
+ mLastSnapshot = snapshot;
+ return;
+ }
+
+ final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
+
+ final NetworkStats delta = NetworkStats.subtract(
+ snapshot, mLastSnapshot, mObserver, mCookie);
+ final long end = currentTimeMillis;
+ final long start = end - delta.getElapsedRealtime();
+
+ NetworkStats.Entry entry = null;
+ for (int i = 0; i < delta.size(); i++) {
+ entry = delta.getValues(i, entry);
+
+ // As a last-ditch check, report any negative values and
+ // clamp them so recording below doesn't croak.
+ if (entry.isNegative()) {
+ if (mObserver != null) {
+ mObserver.foundNonMonotonic(delta, i, mCookie);
+ }
+ entry.rxBytes = Math.max(entry.rxBytes, 0);
+ entry.rxPackets = Math.max(entry.rxPackets, 0);
+ entry.txBytes = Math.max(entry.txBytes, 0);
+ entry.txPackets = Math.max(entry.txPackets, 0);
+ entry.operations = Math.max(entry.operations, 0);
+ }
+
+ final NetworkIdentitySet ident = ifaceIdent.get(entry.iface);
+ if (ident == null) {
+ unknownIfaces.add(entry.iface);
+ continue;
+ }
+
+ // skip when no delta occurred
+ if (entry.isEmpty()) continue;
+
+ // only record tag data when requested
+ if ((entry.tag == TAG_NONE) != mOnlyTags) {
+ if (mPending != null) {
+ mPending.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
+ }
+
+ // also record against boot stats when present
+ if (mSinceBoot != null) {
+ mSinceBoot.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
+ }
+
+ // also record against complete dataset when present
+ if (complete != null) {
+ complete.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
+ }
+ }
+ }
+
+ mLastSnapshot = snapshot;
+
+ if (LOGV && unknownIfaces.size() > 0) {
+ Log.w(TAG, "unknown interfaces " + unknownIfaces + ", ignoring those stats");
+ }
+ }
+
+ /**
+ * Consider persisting any pending deltas, if they are beyond
+ * {@link #mPersistThresholdBytes}.
+ */
+ public void maybePersistLocked(long currentTimeMillis) {
+ Objects.requireNonNull(mRotator, "missing FileRotator");
+ final long pendingBytes = mPending.getTotalBytes();
+ if (pendingBytes >= mPersistThresholdBytes) {
+ forcePersistLocked(currentTimeMillis);
+ } else {
+ mRotator.maybeRotate(currentTimeMillis);
+ }
+ }
+
+ /**
+ * Force persisting any pending deltas.
+ */
+ public void forcePersistLocked(long currentTimeMillis) {
+ Objects.requireNonNull(mRotator, "missing FileRotator");
+ if (mPending.isDirty()) {
+ if (LOGD) Log.d(TAG, "forcePersistLocked() writing for " + mCookie);
+ try {
+ mRotator.rewriteActive(mPendingRewriter, currentTimeMillis);
+ mRotator.maybeRotate(currentTimeMillis);
+ mPending.reset();
+ } catch (IOException e) {
+ Log.wtf(TAG, "problem persisting pending stats", e);
+ recoverAndDeleteData();
+ } catch (OutOfMemoryError e) {
+ Log.wtf(TAG, "problem persisting pending stats", e);
+ recoverAndDeleteData();
+ }
+ }
+ }
+
+ /**
+ * Remove the given UID from all {@link FileRotator} history, migrating it
+ * to {@link TrafficStats#UID_REMOVED}.
+ */
+ public void removeUidsLocked(int[] uids) {
+ if (mRotator != null) {
+ try {
+ // Rewrite all persisted data to migrate UID stats
+ mRotator.rewriteAll(new RemoveUidRewriter(mBucketDuration, uids));
+ } catch (IOException e) {
+ Log.wtf(TAG, "problem removing UIDs " + Arrays.toString(uids), e);
+ recoverAndDeleteData();
+ } catch (OutOfMemoryError e) {
+ Log.wtf(TAG, "problem removing UIDs " + Arrays.toString(uids), e);
+ recoverAndDeleteData();
+ }
+ }
+
+ // Remove any pending stats
+ if (mPending != null) {
+ mPending.removeUids(uids);
+ }
+ if (mSinceBoot != null) {
+ mSinceBoot.removeUids(uids);
+ }
+
+ // Clear UID from current stats snapshot
+ if (mLastSnapshot != null) {
+ mLastSnapshot.removeUids(uids);
+ }
+
+ final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
+ if (complete != null) {
+ complete.removeUids(uids);
+ }
+ }
+
+ /**
+ * Rewriter that will combine current {@link NetworkStatsCollection} values
+ * with anything read from disk, and write combined set to disk.
+ */
+ private static class CombiningRewriter implements FileRotator.Rewriter {
+ private final NetworkStatsCollection mCollection;
+
+ public CombiningRewriter(NetworkStatsCollection collection) {
+ mCollection = Objects.requireNonNull(collection, "missing NetworkStatsCollection");
+ }
+
+ @Override
+ public void reset() {
+ // ignored
+ }
+
+ @Override
+ public void read(InputStream in) throws IOException {
+ mCollection.read(in);
+ }
+
+ @Override
+ public boolean shouldWrite() {
+ return true;
+ }
+
+ @Override
+ public void write(OutputStream out) throws IOException {
+ mCollection.write(out);
+ }
+ }
+
+ /**
+ * Rewriter that will remove any {@link NetworkStatsHistory} attributed to
+ * the requested UID, only writing data back when modified.
+ */
+ public static class RemoveUidRewriter implements FileRotator.Rewriter {
+ private final NetworkStatsCollection mTemp;
+ private final int[] mUids;
+
+ public RemoveUidRewriter(long bucketDuration, int[] uids) {
+ mTemp = new NetworkStatsCollection(bucketDuration);
+ mUids = uids;
+ }
+
+ @Override
+ public void reset() {
+ mTemp.reset();
+ }
+
+ @Override
+ public void read(InputStream in) throws IOException {
+ mTemp.read(in);
+ mTemp.clearDirty();
+ mTemp.removeUids(mUids);
+ }
+
+ @Override
+ public boolean shouldWrite() {
+ return mTemp.isDirty();
+ }
+
+ @Override
+ public void write(OutputStream out) throws IOException {
+ mTemp.write(out);
+ }
+ }
+
+ /**
+ * Import a specified {@link NetworkStatsCollection} instance into this recorder,
+ * and write it into a standalone file.
+ * @param collection The target {@link NetworkStatsCollection} instance to be imported.
+ */
+ public void importCollectionLocked(@NonNull NetworkStatsCollection collection)
+ throws IOException {
+ if (mRotator != null) {
+ mRotator.rewriteSingle(new CombiningRewriter(collection), collection.getStartMillis(),
+ collection.getEndMillis());
+ }
+
+ if (mComplete != null) {
+ throw new IllegalStateException("cannot import data when data already loaded");
+ }
+ }
+
+ /**
+ * Rewriter that will remove any histories or persisted data points before the
+ * specified cutoff time, only writing data back when modified.
+ */
+ public static class RemoveDataBeforeRewriter implements FileRotator.Rewriter {
+ private final NetworkStatsCollection mTemp;
+ private final long mCutoffMills;
+
+ public RemoveDataBeforeRewriter(long bucketDuration, long cutoffMills) {
+ mTemp = new NetworkStatsCollection(bucketDuration);
+ mCutoffMills = cutoffMills;
+ }
+
+ @Override
+ public void reset() {
+ mTemp.reset();
+ }
+
+ @Override
+ public void read(InputStream in) throws IOException {
+ mTemp.read(in);
+ mTemp.clearDirty();
+ mTemp.removeHistoryBefore(mCutoffMills);
+ }
+
+ @Override
+ public boolean shouldWrite() {
+ return mTemp.isDirty();
+ }
+
+ @Override
+ public void write(OutputStream out) throws IOException {
+ mTemp.write(out);
+ }
+ }
+
+ /**
+ * Remove persisted data which contains or is before the cutoff timestamp.
+ */
+ public void removeDataBefore(long cutoffMillis) throws IOException {
+ if (mRotator != null) {
+ try {
+ mRotator.rewriteAll(new RemoveDataBeforeRewriter(
+ mBucketDuration, cutoffMillis));
+ } catch (IOException e) {
+ Log.wtf(TAG, "problem importing netstats", e);
+ recoverAndDeleteData();
+ } catch (OutOfMemoryError e) {
+ Log.wtf(TAG, "problem importing netstats", e);
+ recoverAndDeleteData();
+ }
+ }
+
+ // Clean up any pending stats
+ if (mPending != null) {
+ mPending.removeHistoryBefore(cutoffMillis);
+ }
+ if (mSinceBoot != null) {
+ mSinceBoot.removeHistoryBefore(cutoffMillis);
+ }
+
+ final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
+ if (complete != null) {
+ complete.removeHistoryBefore(cutoffMillis);
+ }
+ }
+
+ public void dumpLocked(IndentingPrintWriter pw, boolean fullHistory) {
+ if (mPending != null) {
+ pw.print("Pending bytes: "); pw.println(mPending.getTotalBytes());
+ }
+ if (fullHistory) {
+ pw.println("Complete history:");
+ getOrLoadCompleteLocked().dump(pw);
+ } else {
+ pw.println("History since boot:");
+ mSinceBoot.dump(pw);
+ }
+ }
+
+ public void dumpDebugLocked(ProtoOutputStream proto, long tag) {
+ final long start = proto.start(tag);
+ if (mPending != null) {
+ proto.write(NetworkStatsRecorderProto.PENDING_TOTAL_BYTES,
+ mPending.getTotalBytes());
+ }
+ getOrLoadCompleteLocked().dumpDebug(proto,
+ NetworkStatsRecorderProto.COMPLETE_HISTORY);
+ proto.end(start);
+ }
+
+ public void dumpCheckin(PrintWriter pw, long start, long end) {
+ // Only load and dump stats from the requested window
+ getOrLoadPartialLocked(start, end).dumpCheckin(pw, start, end);
+ }
+
+ /**
+ * Recover from {@link FileRotator} failure by dumping state to
+ * {@link DropBoxManager} and deleting contents.
+ */
+ void recoverAndDeleteData() {
+ if (DUMP_BEFORE_DELETE) {
+ final ByteArrayOutputStream os = new ByteArrayOutputStream();
+ try {
+ mRotator.dumpAll(os);
+ } catch (IOException e) {
+ // ignore partial contents
+ os.reset();
+ } finally {
+ IoUtils.closeQuietly(os);
+ }
+ mDropBox.addData(TAG_NETSTATS_DUMP, os.toByteArray(), 0);
+ }
+
+ mRotator.deleteAll();
+ }
+}
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
new file mode 100644
index 0000000..63e6501
--- /dev/null
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -0,0 +1,2974 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net;
+
+import static android.Manifest.permission.NETWORK_STATS_PROVIDER;
+import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
+import static android.Manifest.permission.UPDATE_DEVICE_STATS;
+import static android.app.usage.NetworkStatsManager.PREFIX_DEV;
+import static android.content.Intent.ACTION_SHUTDOWN;
+import static android.content.Intent.ACTION_UID_REMOVED;
+import static android.content.Intent.ACTION_USER_REMOVED;
+import static android.content.Intent.EXTRA_UID;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.NetworkStats.DEFAULT_NETWORK_ALL;
+import static android.net.NetworkStats.IFACE_ALL;
+import static android.net.NetworkStats.IFACE_VT;
+import static android.net.NetworkStats.INTERFACES_ALL;
+import static android.net.NetworkStats.METERED_ALL;
+import static android.net.NetworkStats.ROAMING_ALL;
+import static android.net.NetworkStats.SET_ALL;
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.SET_FOREGROUND;
+import static android.net.NetworkStats.STATS_PER_IFACE;
+import static android.net.NetworkStats.STATS_PER_UID;
+import static android.net.NetworkStats.TAG_ALL;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkStats.UID_ALL;
+import static android.net.NetworkStatsHistory.FIELD_ALL;
+import static android.net.NetworkTemplate.buildTemplateMobileWildcard;
+import static android.net.NetworkTemplate.buildTemplateWifiWildcard;
+import static android.net.TrafficStats.KB_IN_BYTES;
+import static android.net.TrafficStats.MB_IN_BYTES;
+import static android.net.TrafficStats.UID_TETHERING;
+import static android.net.TrafficStats.UNSUPPORTED;
+import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID;
+import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID_TAG;
+import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_XT;
+import static android.os.Trace.TRACE_TAG_NETWORK;
+import static android.system.OsConstants.ENOENT;
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+import static android.text.format.DateUtils.DAY_IN_MILLIS;
+import static android.text.format.DateUtils.HOUR_IN_MILLIS;
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+import static android.text.format.DateUtils.SECOND_IN_MILLIS;
+
+import static com.android.net.module.util.NetworkCapabilitiesUtils.getDisplayTransport;
+import static com.android.net.module.util.NetworkStatsUtils.LIMIT_GLOBAL_ALERT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TargetApi;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.app.usage.NetworkStatsManager;
+import android.content.ApexEnvironment;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.database.ContentObserver;
+import android.net.ConnectivityManager;
+import android.net.DataUsageRequest;
+import android.net.INetd;
+import android.net.INetworkStatsService;
+import android.net.INetworkStatsSession;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkIdentity;
+import android.net.NetworkIdentitySet;
+import android.net.NetworkPolicyManager;
+import android.net.NetworkSpecifier;
+import android.net.NetworkStack;
+import android.net.NetworkStateSnapshot;
+import android.net.NetworkStats;
+import android.net.NetworkStats.NonMonotonicObserver;
+import android.net.NetworkStatsAccess;
+import android.net.NetworkStatsCollection;
+import android.net.NetworkStatsHistory;
+import android.net.NetworkTemplate;
+import android.net.TelephonyNetworkSpecifier;
+import android.net.TetherStatsParcel;
+import android.net.TetheringManager;
+import android.net.TrafficStats;
+import android.net.UnderlyingNetworkInfo;
+import android.net.Uri;
+import android.net.netstats.IUsageCallback;
+import android.net.netstats.NetworkStatsDataMigrationUtils;
+import android.net.netstats.provider.INetworkStatsProvider;
+import android.net.netstats.provider.INetworkStatsProviderCallback;
+import android.net.netstats.provider.NetworkStatsProvider;
+import android.os.Binder;
+import android.os.Build;
+import android.os.DropBoxManager;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.provider.DeviceConfig;
+import android.provider.Settings;
+import android.provider.Settings.Global;
+import android.service.NetworkInterfaceProto;
+import android.service.NetworkStatsServiceDumpProto;
+import android.system.ErrnoException;
+import android.telephony.PhoneStateListener;
+import android.telephony.SubscriptionPlan;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.EventLog;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+import android.util.SparseIntArray;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FileRotator;
+import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
+import com.android.net.module.util.BestClock;
+import com.android.net.module.util.BinderUtils;
+import com.android.net.module.util.BpfMap;
+import com.android.net.module.util.CollectionUtils;
+import com.android.net.module.util.DeviceConfigUtils;
+import com.android.net.module.util.IBpfMap;
+import com.android.net.module.util.LocationPermissionChecker;
+import com.android.net.module.util.NetworkStatsUtils;
+import com.android.net.module.util.PermissionUtils;
+import com.android.net.module.util.Struct.U32;
+import com.android.net.module.util.Struct.U8;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.file.Path;
+import java.time.Clock;
+import java.time.Instant;
+import java.time.ZoneOffset;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Collect and persist detailed network statistics, and provide this data to
+ * other system services.
+ */
+@TargetApi(Build.VERSION_CODES.TIRAMISU)
+public class NetworkStatsService extends INetworkStatsService.Stub {
+ static {
+ System.loadLibrary("service-connectivity");
+ }
+
+ static final String TAG = "NetworkStats";
+ static final boolean LOGD = Log.isLoggable(TAG, Log.DEBUG);
+ static final boolean LOGV = Log.isLoggable(TAG, Log.VERBOSE);
+
+ // Perform polling and persist all (FLAG_PERSIST_ALL).
+ private static final int MSG_PERFORM_POLL = 1;
+ // Perform polling, persist network, and register the global alert again.
+ private static final int MSG_PERFORM_POLL_REGISTER_ALERT = 2;
+ private static final int MSG_NOTIFY_NETWORK_STATUS = 3;
+ // A message for broadcasting ACTION_NETWORK_STATS_UPDATED in handler thread to prevent
+ // deadlock.
+ private static final int MSG_BROADCAST_NETWORK_STATS_UPDATED = 4;
+
+ /** Flags to control detail level of poll event. */
+ private static final int FLAG_PERSIST_NETWORK = 0x1;
+ private static final int FLAG_PERSIST_UID = 0x2;
+ private static final int FLAG_PERSIST_ALL = FLAG_PERSIST_NETWORK | FLAG_PERSIST_UID;
+ private static final int FLAG_PERSIST_FORCE = 0x100;
+
+ /**
+ * When global alert quota is high, wait for this delay before processing each polling,
+ * and do not schedule further polls once there is already one queued.
+ * This avoids firing the global alert too often on devices with high transfer speeds and
+ * high quota.
+ */
+ private static final int DEFAULT_PERFORM_POLL_DELAY_MS = 1000;
+
+ private static final String TAG_NETSTATS_ERROR = "netstats_error";
+
+ /**
+ * EventLog tags used when logging into the event log. Note the values must be sync with
+ * frameworks/base/services/core/java/com/android/server/EventLogTags.logtags to get correct
+ * name translation.
+ */
+ private static final int LOG_TAG_NETSTATS_MOBILE_SAMPLE = 51100;
+ private static final int LOG_TAG_NETSTATS_WIFI_SAMPLE = 51101;
+
+ // TODO: Replace the hardcoded string and move it into ConnectivitySettingsManager.
+ private static final String NETSTATS_COMBINE_SUBTYPE_ENABLED =
+ "netstats_combine_subtype_enabled";
+
+ private static final String UID_COUNTERSET_MAP_PATH =
+ "/sys/fs/bpf/netd_shared/map_netd_uid_counterset_map";
+ private static final String COOKIE_TAG_MAP_PATH =
+ "/sys/fs/bpf/netd_shared/map_netd_cookie_tag_map";
+ private static final String APP_UID_STATS_MAP_PATH =
+ "/sys/fs/bpf/netd_shared/map_netd_app_uid_stats_map";
+ private static final String STATS_MAP_A_PATH =
+ "/sys/fs/bpf/netd_shared/map_netd_stats_map_A";
+ private static final String STATS_MAP_B_PATH =
+ "/sys/fs/bpf/netd_shared/map_netd_stats_map_B";
+
+ /**
+ * DeviceConfig flag used to indicate whether the files should be stored in the apex data
+ * directory.
+ */
+ static final String NETSTATS_STORE_FILES_IN_APEXDATA = "netstats_store_files_in_apexdata";
+ /**
+ * DeviceConfig flag is used to indicate whether the legacy files need to be imported, and
+ * retry count before giving up. Only valid when {@link #NETSTATS_STORE_FILES_IN_APEXDATA}
+ * set to true. Note that the value gets rollback when the mainline module gets rollback.
+ */
+ static final String NETSTATS_IMPORT_LEGACY_TARGET_ATTEMPTS =
+ "netstats_import_legacy_target_attempts";
+ static final int DEFAULT_NETSTATS_IMPORT_LEGACY_TARGET_ATTEMPTS = 1;
+ static final String NETSTATS_IMPORT_ATTEMPTS_COUNTER_NAME = "import.attempts";
+ static final String NETSTATS_IMPORT_SUCCESS_COUNTER_NAME = "import.successes";
+
+ private final Context mContext;
+ private final NetworkStatsFactory mStatsFactory;
+ private final AlarmManager mAlarmManager;
+ private final Clock mClock;
+ private final NetworkStatsSettings mSettings;
+ private final NetworkStatsObservers mStatsObservers;
+
+ private final File mStatsDir;
+
+ private final PowerManager.WakeLock mWakeLock;
+
+ private final ContentObserver mContentObserver;
+ private final ContentResolver mContentResolver;
+
+ protected INetd mNetd;
+ private final AlertObserver mAlertObserver = new AlertObserver();
+
+ // Persistent counters that backed by AtomicFile which stored in the data directory as a file,
+ // to track attempts/successes count across reboot. Note that these counter values will be
+ // rollback as the module rollbacks.
+ private PersistentInt mImportLegacyAttemptsCounter = null;
+ private PersistentInt mImportLegacySuccessesCounter = null;
+
+ @VisibleForTesting
+ public static final String ACTION_NETWORK_STATS_POLL =
+ "com.android.server.action.NETWORK_STATS_POLL";
+ public static final String ACTION_NETWORK_STATS_UPDATED =
+ "com.android.server.action.NETWORK_STATS_UPDATED";
+
+ private PendingIntent mPollIntent;
+
+ /**
+ * Settings that can be changed externally.
+ */
+ public interface NetworkStatsSettings {
+ long getPollInterval();
+ long getPollDelay();
+ boolean getSampleEnabled();
+ boolean getAugmentEnabled();
+ /**
+ * When enabled, all mobile data is reported under {@link NetworkTemplate#NETWORK_TYPE_ALL}.
+ * When disabled, mobile data is broken down by a granular ratType representative of the
+ * actual ratType. {@see android.app.usage.NetworkStatsManager#getCollapsedRatType}.
+ * Enabling this decreases the level of detail but saves performance, disk space and
+ * amount of data logged.
+ */
+ boolean getCombineSubtypeEnabled();
+
+ class Config {
+ public final long bucketDuration;
+ public final long rotateAgeMillis;
+ public final long deleteAgeMillis;
+
+ public Config(long bucketDuration, long rotateAgeMillis, long deleteAgeMillis) {
+ this.bucketDuration = bucketDuration;
+ this.rotateAgeMillis = rotateAgeMillis;
+ this.deleteAgeMillis = deleteAgeMillis;
+ }
+ }
+
+ Config getDevConfig();
+ Config getXtConfig();
+ Config getUidConfig();
+ Config getUidTagConfig();
+
+ long getGlobalAlertBytes(long def);
+ long getDevPersistBytes(long def);
+ long getXtPersistBytes(long def);
+ long getUidPersistBytes(long def);
+ long getUidTagPersistBytes(long def);
+ }
+
+ private final Object mStatsLock = new Object();
+
+ /** Set of currently active ifaces. */
+ @GuardedBy("mStatsLock")
+ private final ArrayMap<String, NetworkIdentitySet> mActiveIfaces = new ArrayMap<>();
+
+ /** Set of currently active ifaces for UID stats. */
+ @GuardedBy("mStatsLock")
+ private final ArrayMap<String, NetworkIdentitySet> mActiveUidIfaces = new ArrayMap<>();
+
+ /** Current default active iface. */
+ @GuardedBy("mStatsLock")
+ private String mActiveIface;
+
+ /** Set of any ifaces associated with mobile networks since boot. */
+ private volatile String[] mMobileIfaces = new String[0];
+
+ /** Set of any ifaces associated with wifi networks since boot. */
+ private volatile String[] mWifiIfaces = new String[0];
+
+ /** Set of all ifaces currently used by traffic that does not explicitly specify a Network. */
+ @GuardedBy("mStatsLock")
+ private Network[] mDefaultNetworks = new Network[0];
+
+ /** Last states of all networks sent from ConnectivityService. */
+ @GuardedBy("mStatsLock")
+ @Nullable
+ private NetworkStateSnapshot[] mLastNetworkStateSnapshots = null;
+
+ private final DropBoxNonMonotonicObserver mNonMonotonicObserver =
+ new DropBoxNonMonotonicObserver();
+
+ private static final int MAX_STATS_PROVIDER_POLL_WAIT_TIME_MS = 100;
+ private final CopyOnWriteArrayList<NetworkStatsProviderCallbackImpl> mStatsProviderCbList =
+ new CopyOnWriteArrayList<>();
+ /** Semaphore used to wait for stats provider to respond to request stats update. */
+ private final Semaphore mStatsProviderSem = new Semaphore(0, true);
+
+ @GuardedBy("mStatsLock")
+ private NetworkStatsRecorder mDevRecorder;
+ @GuardedBy("mStatsLock")
+ private NetworkStatsRecorder mXtRecorder;
+ @GuardedBy("mStatsLock")
+ private NetworkStatsRecorder mUidRecorder;
+ @GuardedBy("mStatsLock")
+ private NetworkStatsRecorder mUidTagRecorder;
+
+ /** Cached {@link #mXtRecorder} stats. */
+ @GuardedBy("mStatsLock")
+ private NetworkStatsCollection mXtStatsCached;
+
+ /**
+ * Current counter sets for each UID.
+ * TODO: maybe remove mActiveUidCounterSet and read UidCouneterSet value from mUidCounterSetMap
+ * directly ? But if mActiveUidCounterSet would be accessed very frequently, maybe keep
+ * mActiveUidCounterSet to avoid accessing kernel too frequently.
+ */
+ private SparseIntArray mActiveUidCounterSet = new SparseIntArray();
+ private final IBpfMap<U32, U8> mUidCounterSetMap;
+ private final IBpfMap<CookieTagMapKey, CookieTagMapValue> mCookieTagMap;
+ private final IBpfMap<StatsMapKey, StatsMapValue> mStatsMapA;
+ private final IBpfMap<StatsMapKey, StatsMapValue> mStatsMapB;
+ private final IBpfMap<UidStatsMapKey, StatsMapValue> mAppUidStatsMap;
+
+ /** Data layer operation counters for splicing into other structures. */
+ private NetworkStats mUidOperations = new NetworkStats(0L, 10);
+
+ @NonNull
+ private final Handler mHandler;
+
+ private volatile boolean mSystemReady;
+ private long mPersistThreshold = 2 * MB_IN_BYTES;
+ private long mGlobalAlertBytes;
+
+ private static final long POLL_RATE_LIMIT_MS = 15_000;
+
+ private long mLastStatsSessionPoll;
+
+ private final Object mOpenSessionCallsLock = new Object();
+ /**
+ * Map from UID to number of opened sessions. This is used for rate-limt an app to open
+ * session frequently
+ */
+ @GuardedBy("mOpenSessionCallsLock")
+ private final SparseIntArray mOpenSessionCallsPerUid = new SparseIntArray();
+ /**
+ * Map from key {@code OpenSessionKey} to count of opened sessions. This is for recording
+ * the caller of open session and it is only for debugging.
+ */
+ @GuardedBy("mOpenSessionCallsLock")
+ private final HashMap<OpenSessionKey, Integer> mOpenSessionCallsPerCaller = new HashMap<>();
+
+ private final static int DUMP_STATS_SESSION_COUNT = 20;
+
+ @NonNull
+ private final Dependencies mDeps;
+
+ @NonNull
+ private final NetworkStatsSubscriptionsMonitor mNetworkStatsSubscriptionsMonitor;
+
+ @NonNull
+ private final LocationPermissionChecker mLocationPermissionChecker;
+
+ @NonNull
+ private final BpfInterfaceMapUpdater mInterfaceMapUpdater;
+
+ private static @NonNull Clock getDefaultClock() {
+ return new BestClock(ZoneOffset.UTC, SystemClock.currentNetworkTimeClock(),
+ Clock.systemUTC());
+ }
+
+ /**
+ * This class is a key that used in {@code mOpenSessionCallsPerCaller} to identify the count of
+ * the caller.
+ */
+ private static class OpenSessionKey {
+ public final int uid;
+ public final String packageName;
+
+ OpenSessionKey(int uid, @NonNull String packageName) {
+ this.uid = uid;
+ this.packageName = packageName;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("{");
+ sb.append("uid=").append(uid).append(",");
+ sb.append("package=").append(packageName);
+ sb.append("}");
+ return sb.toString();
+ }
+
+ @Override
+ public boolean equals(@NonNull Object o) {
+ if (this == o) return true;
+ if (o.getClass() != getClass()) return false;
+
+ final OpenSessionKey key = (OpenSessionKey) o;
+ return this.uid == key.uid && TextUtils.equals(this.packageName, key.packageName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(uid, packageName);
+ }
+ }
+
+ private final class NetworkStatsHandler extends Handler {
+ NetworkStatsHandler(@NonNull Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_PERFORM_POLL: {
+ performPoll(FLAG_PERSIST_ALL);
+ break;
+ }
+ case MSG_NOTIFY_NETWORK_STATUS: {
+ // If no cached states, ignore.
+ if (mLastNetworkStateSnapshots == null) break;
+ // TODO (b/181642673): Protect mDefaultNetworks from concurrent accessing.
+ handleNotifyNetworkStatus(
+ mDefaultNetworks, mLastNetworkStateSnapshots, mActiveIface);
+ break;
+ }
+ case MSG_PERFORM_POLL_REGISTER_ALERT: {
+ performPoll(FLAG_PERSIST_NETWORK);
+ registerGlobalAlert();
+ break;
+ }
+ case MSG_BROADCAST_NETWORK_STATS_UPDATED: {
+ final Intent updatedIntent = new Intent(ACTION_NETWORK_STATS_UPDATED);
+ updatedIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ mContext.sendBroadcastAsUser(updatedIntent, UserHandle.ALL,
+ READ_NETWORK_USAGE_HISTORY);
+ break;
+ }
+ }
+ }
+ }
+
+ /** Creates a new NetworkStatsService */
+ public static NetworkStatsService create(Context context) {
+ AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ PowerManager.WakeLock wakeLock =
+ powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+ final INetd netd = INetd.Stub.asInterface(
+ (IBinder) context.getSystemService(Context.NETD_SERVICE));
+ final NetworkStatsService service = new NetworkStatsService(context,
+ INetd.Stub.asInterface((IBinder) context.getSystemService(Context.NETD_SERVICE)),
+ alarmManager, wakeLock, getDefaultClock(),
+ new DefaultNetworkStatsSettings(), new NetworkStatsFactory(context),
+ new NetworkStatsObservers(), new Dependencies());
+
+ return service;
+ }
+
+ // This must not be called outside of tests, even within the same package, as this constructor
+ // does not register the local service. Use the create() helper above.
+ @VisibleForTesting
+ NetworkStatsService(Context context, INetd netd, AlarmManager alarmManager,
+ PowerManager.WakeLock wakeLock, Clock clock, NetworkStatsSettings settings,
+ NetworkStatsFactory factory, NetworkStatsObservers statsObservers,
+ @NonNull Dependencies deps) {
+ mContext = Objects.requireNonNull(context, "missing Context");
+ mNetd = Objects.requireNonNull(netd, "missing Netd");
+ mAlarmManager = Objects.requireNonNull(alarmManager, "missing AlarmManager");
+ mClock = Objects.requireNonNull(clock, "missing Clock");
+ mSettings = Objects.requireNonNull(settings, "missing NetworkStatsSettings");
+ mWakeLock = Objects.requireNonNull(wakeLock, "missing WakeLock");
+ mStatsFactory = Objects.requireNonNull(factory, "missing factory");
+ mStatsObservers = Objects.requireNonNull(statsObservers, "missing NetworkStatsObservers");
+ mDeps = Objects.requireNonNull(deps, "missing Dependencies");
+ mStatsDir = mDeps.getOrCreateStatsDir();
+ if (!mStatsDir.exists()) {
+ throw new IllegalStateException("Persist data directory does not exist: " + mStatsDir);
+ }
+
+ final HandlerThread handlerThread = mDeps.makeHandlerThread();
+ handlerThread.start();
+ mHandler = new NetworkStatsHandler(handlerThread.getLooper());
+ mNetworkStatsSubscriptionsMonitor = deps.makeSubscriptionsMonitor(mContext,
+ (command) -> mHandler.post(command) , this);
+ mContentResolver = mContext.getContentResolver();
+ mContentObserver = mDeps.makeContentObserver(mHandler, mSettings,
+ mNetworkStatsSubscriptionsMonitor);
+ mLocationPermissionChecker = mDeps.makeLocationPermissionChecker(mContext);
+ mInterfaceMapUpdater = mDeps.makeBpfInterfaceMapUpdater(mContext, mHandler);
+ mInterfaceMapUpdater.start();
+ mUidCounterSetMap = mDeps.getUidCounterSetMap();
+ mCookieTagMap = mDeps.getCookieTagMap();
+ mStatsMapA = mDeps.getStatsMapA();
+ mStatsMapB = mDeps.getStatsMapB();
+ mAppUidStatsMap = mDeps.getAppUidStatsMap();
+ }
+
+ /**
+ * Dependencies of NetworkStatsService, for injection in tests.
+ */
+ // TODO: Move more stuff into dependencies object.
+ @VisibleForTesting
+ public static class Dependencies {
+ /**
+ * Get legacy platform stats directory.
+ */
+ @NonNull
+ public File getLegacyStatsDir() {
+ final File systemDataDir = new File(Environment.getDataDirectory(), "system");
+ return new File(systemDataDir, "netstats");
+ }
+
+ /**
+ * Get or create the directory that stores the persisted data usage.
+ */
+ @NonNull
+ public File getOrCreateStatsDir() {
+ final boolean storeInApexDataDir = getStoreFilesInApexData();
+
+ final File statsDataDir;
+ if (storeInApexDataDir) {
+ final File apexDataDir = ApexEnvironment
+ .getApexEnvironment(DeviceConfigUtils.TETHERING_MODULE_NAME)
+ .getDeviceProtectedDataDir();
+ statsDataDir = new File(apexDataDir, "netstats");
+
+ } else {
+ statsDataDir = getLegacyStatsDir();
+ }
+
+ if (statsDataDir.exists() || statsDataDir.mkdirs()) {
+ return statsDataDir;
+ }
+ throw new IllegalStateException("Cannot write into stats data directory: "
+ + statsDataDir);
+ }
+
+ /**
+ * Get the count of import legacy target attempts.
+ */
+ public int getImportLegacyTargetAttempts() {
+ return DeviceConfigUtils.getDeviceConfigPropertyInt(
+ DeviceConfig.NAMESPACE_TETHERING,
+ NETSTATS_IMPORT_LEGACY_TARGET_ATTEMPTS,
+ DEFAULT_NETSTATS_IMPORT_LEGACY_TARGET_ATTEMPTS);
+ }
+
+ /**
+ * Create the persistent counter that counts total import legacy stats attempts.
+ */
+ public PersistentInt createImportLegacyAttemptsCounter(@NonNull Path path)
+ throws IOException {
+ // TODO: Modify PersistentInt to call setStartTime every time a write is made.
+ // Create and pass a real logger here.
+ return new PersistentInt(path.toString(), null /* logger */);
+ }
+
+ /**
+ * Create the persistent counter that counts total import legacy stats successes.
+ */
+ public PersistentInt createImportLegacySuccessesCounter(@NonNull Path path)
+ throws IOException {
+ return new PersistentInt(path.toString(), null /* logger */);
+ }
+
+ /**
+ * Get the flag of storing files in the apex data directory.
+ * @return whether to store files in the apex data directory.
+ */
+ public boolean getStoreFilesInApexData() {
+ return DeviceConfigUtils.getDeviceConfigPropertyBoolean(
+ DeviceConfig.NAMESPACE_TETHERING,
+ NETSTATS_STORE_FILES_IN_APEXDATA, true);
+ }
+
+ /**
+ * Read legacy persisted network stats from disk.
+ */
+ @NonNull
+ public NetworkStatsCollection readPlatformCollection(
+ @NonNull String prefix, long bucketDuration) throws IOException {
+ return NetworkStatsDataMigrationUtils.readPlatformCollection(prefix, bucketDuration);
+ }
+
+ /**
+ * Create a HandlerThread to use in NetworkStatsService.
+ */
+ @NonNull
+ public HandlerThread makeHandlerThread() {
+ return new HandlerThread(TAG);
+ }
+
+ /**
+ * Create a {@link NetworkStatsSubscriptionsMonitor}, can be used to monitor RAT change
+ * event in NetworkStatsService.
+ */
+ @NonNull
+ public NetworkStatsSubscriptionsMonitor makeSubscriptionsMonitor(@NonNull Context context,
+ @NonNull Executor executor, @NonNull NetworkStatsService service) {
+ // TODO: Update RatType passively in NSS, instead of querying into the monitor
+ // when notifyNetworkStatus.
+ return new NetworkStatsSubscriptionsMonitor(context, executor,
+ (subscriberId, type) -> service.handleOnCollapsedRatTypeChanged());
+ }
+
+ /**
+ * Create a ContentObserver instance which is used to observe settings changes,
+ * and dispatch onChange events on handler thread.
+ */
+ public @NonNull ContentObserver makeContentObserver(@NonNull Handler handler,
+ @NonNull NetworkStatsSettings settings,
+ @NonNull NetworkStatsSubscriptionsMonitor monitor) {
+ return new ContentObserver(handler) {
+ @Override
+ public void onChange(boolean selfChange, @NonNull Uri uri) {
+ if (!settings.getCombineSubtypeEnabled()) {
+ monitor.start();
+ } else {
+ monitor.stop();
+ }
+ }
+ };
+ }
+
+ /**
+ * @see LocationPermissionChecker
+ */
+ public LocationPermissionChecker makeLocationPermissionChecker(final Context context) {
+ return new LocationPermissionChecker(context);
+ }
+
+ /** Create BpfInterfaceMapUpdater to update bpf interface map. */
+ @NonNull
+ public BpfInterfaceMapUpdater makeBpfInterfaceMapUpdater(
+ @NonNull Context ctx, @NonNull Handler handler) {
+ return new BpfInterfaceMapUpdater(ctx, handler);
+ }
+
+ /** Get counter sets map for each UID. */
+ public IBpfMap<U32, U8> getUidCounterSetMap() {
+ try {
+ return new BpfMap<U32, U8>(UID_COUNTERSET_MAP_PATH, BpfMap.BPF_F_RDWR,
+ U32.class, U8.class);
+ } catch (ErrnoException e) {
+ Log.wtf(TAG, "Cannot open uid counter set map: " + e);
+ return null;
+ }
+ }
+
+ /** Gets the cookie tag map */
+ public IBpfMap<CookieTagMapKey, CookieTagMapValue> getCookieTagMap() {
+ try {
+ return new BpfMap<CookieTagMapKey, CookieTagMapValue>(COOKIE_TAG_MAP_PATH,
+ BpfMap.BPF_F_RDWR, CookieTagMapKey.class, CookieTagMapValue.class);
+ } catch (ErrnoException e) {
+ Log.wtf(TAG, "Cannot open cookie tag map: " + e);
+ return null;
+ }
+ }
+
+ /** Gets stats map A */
+ public IBpfMap<StatsMapKey, StatsMapValue> getStatsMapA() {
+ try {
+ return new BpfMap<StatsMapKey, StatsMapValue>(STATS_MAP_A_PATH,
+ BpfMap.BPF_F_RDWR, StatsMapKey.class, StatsMapValue.class);
+ } catch (ErrnoException e) {
+ Log.wtf(TAG, "Cannot open stats map A: " + e);
+ return null;
+ }
+ }
+
+ /** Gets stats map B */
+ public IBpfMap<StatsMapKey, StatsMapValue> getStatsMapB() {
+ try {
+ return new BpfMap<StatsMapKey, StatsMapValue>(STATS_MAP_B_PATH,
+ BpfMap.BPF_F_RDWR, StatsMapKey.class, StatsMapValue.class);
+ } catch (ErrnoException e) {
+ Log.wtf(TAG, "Cannot open stats map B: " + e);
+ return null;
+ }
+ }
+
+ /** Gets the uid stats map */
+ public IBpfMap<UidStatsMapKey, StatsMapValue> getAppUidStatsMap() {
+ try {
+ return new BpfMap<UidStatsMapKey, StatsMapValue>(APP_UID_STATS_MAP_PATH,
+ BpfMap.BPF_F_RDWR, UidStatsMapKey.class, StatsMapValue.class);
+ } catch (ErrnoException e) {
+ Log.wtf(TAG, "Cannot open app uid stats map: " + e);
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Observer that watches for {@link INetdUnsolicitedEventListener} alerts.
+ */
+ @VisibleForTesting
+ public class AlertObserver extends BaseNetdUnsolicitedEventListener {
+ @Override
+ public void onQuotaLimitReached(@NonNull String alertName, @NonNull String ifName) {
+ PermissionUtils.enforceNetworkStackPermission(mContext);
+
+ if (LIMIT_GLOBAL_ALERT.equals(alertName)) {
+ // kick off background poll to collect network stats unless there is already
+ // such a call pending; UID stats are handled during normal polling interval.
+ if (!mHandler.hasMessages(MSG_PERFORM_POLL_REGISTER_ALERT)) {
+ mHandler.sendEmptyMessageDelayed(MSG_PERFORM_POLL_REGISTER_ALERT,
+ mSettings.getPollDelay());
+ }
+ }
+ }
+ }
+
+ public void systemReady() {
+ synchronized (mStatsLock) {
+ mSystemReady = true;
+
+ // create data recorders along with historical rotators
+ mDevRecorder = buildRecorder(PREFIX_DEV, mSettings.getDevConfig(), false, mStatsDir);
+ mXtRecorder = buildRecorder(PREFIX_XT, mSettings.getXtConfig(), false, mStatsDir);
+ mUidRecorder = buildRecorder(PREFIX_UID, mSettings.getUidConfig(), false, mStatsDir);
+ mUidTagRecorder = buildRecorder(PREFIX_UID_TAG, mSettings.getUidTagConfig(), true,
+ mStatsDir);
+
+ updatePersistThresholdsLocked();
+
+ // upgrade any legacy stats
+ maybeUpgradeLegacyStatsLocked();
+
+ // read historical network stats from disk, since policy service
+ // might need them right away.
+ mXtStatsCached = mXtRecorder.getOrLoadCompleteLocked();
+
+ // bootstrap initial stats to prevent double-counting later
+ bootstrapStatsLocked();
+ }
+
+ // watch for tethering changes
+ final TetheringManager tetheringManager = mContext.getSystemService(TetheringManager.class);
+ tetheringManager.registerTetheringEventCallback(
+ (command) -> mHandler.post(command), mTetherListener);
+
+ // listen for periodic polling events
+ final IntentFilter pollFilter = new IntentFilter(ACTION_NETWORK_STATS_POLL);
+ mContext.registerReceiver(mPollReceiver, pollFilter, READ_NETWORK_USAGE_HISTORY, mHandler);
+
+ // listen for uid removal to clean stats
+ final IntentFilter removedFilter = new IntentFilter(ACTION_UID_REMOVED);
+ mContext.registerReceiver(mRemovedReceiver, removedFilter, null, mHandler);
+
+ // listen for user changes to clean stats
+ final IntentFilter userFilter = new IntentFilter(ACTION_USER_REMOVED);
+ mContext.registerReceiver(mUserReceiver, userFilter, null, mHandler);
+
+ // persist stats during clean shutdown
+ final IntentFilter shutdownFilter = new IntentFilter(ACTION_SHUTDOWN);
+ mContext.registerReceiver(mShutdownReceiver, shutdownFilter);
+
+ try {
+ mNetd.registerUnsolicitedEventListener(mAlertObserver);
+ } catch (RemoteException | ServiceSpecificException e) {
+ Log.wtf(TAG, "Error registering event listener :", e);
+ }
+
+ // schedule periodic pall alarm based on {@link NetworkStatsSettings#getPollInterval()}.
+ final PendingIntent pollIntent =
+ PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_NETWORK_STATS_POLL),
+ PendingIntent.FLAG_IMMUTABLE);
+
+ final long currentRealtime = SystemClock.elapsedRealtime();
+ mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, currentRealtime,
+ mSettings.getPollInterval(), pollIntent);
+
+ mContentResolver.registerContentObserver(Settings.Global
+ .getUriFor(NETSTATS_COMBINE_SUBTYPE_ENABLED),
+ false /* notifyForDescendants */, mContentObserver);
+
+ // Post a runnable on handler thread to call onChange(). It's for getting current value of
+ // NETSTATS_COMBINE_SUBTYPE_ENABLED to decide start or stop monitoring RAT type changes.
+ mHandler.post(() -> mContentObserver.onChange(false, Settings.Global
+ .getUriFor(NETSTATS_COMBINE_SUBTYPE_ENABLED)));
+
+ registerGlobalAlert();
+ }
+
+ private NetworkStatsRecorder buildRecorder(
+ String prefix, NetworkStatsSettings.Config config, boolean includeTags,
+ File baseDir) {
+ final DropBoxManager dropBox = (DropBoxManager) mContext.getSystemService(
+ Context.DROPBOX_SERVICE);
+ return new NetworkStatsRecorder(new FileRotator(
+ baseDir, prefix, config.rotateAgeMillis, config.deleteAgeMillis),
+ mNonMonotonicObserver, dropBox, prefix, config.bucketDuration, includeTags);
+ }
+
+ @GuardedBy("mStatsLock")
+ private void shutdownLocked() {
+ final TetheringManager tetheringManager = mContext.getSystemService(TetheringManager.class);
+ tetheringManager.unregisterTetheringEventCallback(mTetherListener);
+ mContext.unregisterReceiver(mPollReceiver);
+ mContext.unregisterReceiver(mRemovedReceiver);
+ mContext.unregisterReceiver(mUserReceiver);
+ mContext.unregisterReceiver(mShutdownReceiver);
+
+ if (!mSettings.getCombineSubtypeEnabled()) {
+ mNetworkStatsSubscriptionsMonitor.stop();
+ }
+
+ mContentResolver.unregisterContentObserver(mContentObserver);
+
+ final long currentTime = mClock.millis();
+
+ // persist any pending stats
+ mDevRecorder.forcePersistLocked(currentTime);
+ mXtRecorder.forcePersistLocked(currentTime);
+ mUidRecorder.forcePersistLocked(currentTime);
+ mUidTagRecorder.forcePersistLocked(currentTime);
+
+ mSystemReady = false;
+ }
+
+ private static class MigrationInfo {
+ public final NetworkStatsRecorder recorder;
+ public NetworkStatsCollection collection;
+ public boolean imported;
+ MigrationInfo(@NonNull final NetworkStatsRecorder recorder) {
+ this.recorder = recorder;
+ collection = null;
+ imported = false;
+ }
+ }
+
+ @GuardedBy("mStatsLock")
+ private void maybeUpgradeLegacyStatsLocked() {
+ final boolean storeFilesInApexData = mDeps.getStoreFilesInApexData();
+ if (!storeFilesInApexData) {
+ return;
+ }
+ try {
+ mImportLegacyAttemptsCounter = mDeps.createImportLegacyAttemptsCounter(
+ mStatsDir.toPath().resolve(NETSTATS_IMPORT_ATTEMPTS_COUNTER_NAME));
+ mImportLegacySuccessesCounter = mDeps.createImportLegacySuccessesCounter(
+ mStatsDir.toPath().resolve(NETSTATS_IMPORT_SUCCESS_COUNTER_NAME));
+ } catch (IOException e) {
+ Log.wtf(TAG, "Failed to create persistent counters, skip.", e);
+ return;
+ }
+
+ final int targetAttempts = mDeps.getImportLegacyTargetAttempts();
+ final int attempts;
+ try {
+ attempts = mImportLegacyAttemptsCounter.get();
+ } catch (IOException e) {
+ Log.wtf(TAG, "Failed to read attempts counter, skip.", e);
+ return;
+ }
+ if (attempts >= targetAttempts) return;
+
+ Log.i(TAG, "Starting import : attempts " + attempts + "/" + targetAttempts);
+
+ final MigrationInfo[] migrations = new MigrationInfo[]{
+ new MigrationInfo(mDevRecorder), new MigrationInfo(mXtRecorder),
+ new MigrationInfo(mUidRecorder), new MigrationInfo(mUidTagRecorder)
+ };
+
+ // Legacy directories will be created by recorders if they do not exist
+ final File legacyBaseDir = mDeps.getLegacyStatsDir();
+ final NetworkStatsRecorder[] legacyRecorders = new NetworkStatsRecorder[]{
+ buildRecorder(PREFIX_DEV, mSettings.getDevConfig(), false, legacyBaseDir),
+ buildRecorder(PREFIX_XT, mSettings.getXtConfig(), false, legacyBaseDir),
+ buildRecorder(PREFIX_UID, mSettings.getUidConfig(), false, legacyBaseDir),
+ buildRecorder(PREFIX_UID_TAG, mSettings.getUidTagConfig(), true, legacyBaseDir)
+ };
+
+ long migrationEndTime = Long.MIN_VALUE;
+ boolean endedWithFallback = false;
+ try {
+ // First, read all legacy collections. This is OEM code and it can throw. Don't
+ // commit any data to disk until all are read.
+ for (int i = 0; i < migrations.length; i++) {
+ final MigrationInfo migration = migrations[i];
+ migration.collection = readPlatformCollectionForRecorder(migration.recorder);
+
+ // Also read the collection with legacy method
+ final NetworkStatsRecorder legacyRecorder = legacyRecorders[i];
+
+ final NetworkStatsCollection legacyStats;
+ try {
+ legacyStats = legacyRecorder.getOrLoadCompleteLocked();
+ } catch (Throwable e) {
+ Log.wtf(TAG, "Failed to read stats with legacy method", e);
+ // Newer stats will be used here; that's the only thing that is usable
+ continue;
+ }
+
+ String errMsg;
+ Throwable exception = null;
+ try {
+ errMsg = compareStats(migration.collection, legacyStats);
+ } catch (Throwable e) {
+ errMsg = "Failed to compare migrated stats with all stats";
+ exception = e;
+ }
+
+ if (errMsg != null) {
+ Log.wtf(TAG, "NetworkStats import for migration " + i
+ + " returned invalid data: " + errMsg, exception);
+ // Fall back to legacy stats for this boot. The stats for old data will be
+ // re-imported again on next boot until they succeed the import. This is fine
+ // since every import clears the previous stats for the imported timespan.
+ migration.collection = legacyStats;
+ endedWithFallback = true;
+ }
+ }
+
+ // Find the latest end time.
+ for (final MigrationInfo migration : migrations) {
+ final long migrationEnd = migration.collection.getEndMillis();
+ if (migrationEnd > migrationEndTime) migrationEndTime = migrationEnd;
+ }
+
+ // Reading all collections from legacy data has succeeded. At this point it is
+ // safe to start overwriting the files on disk. The next step is to remove all
+ // data in the new location that overlaps with imported data. This ensures that
+ // any data in the new location that was created by a previous failed import is
+ // ignored. After that, write the imported data into the recorder. The code
+ // below can still possibly throw (disk error or OutOfMemory for example), but
+ // does not depend on code from non-mainline code.
+ Log.i(TAG, "Rewriting data with imported collections with cutoff "
+ + Instant.ofEpochMilli(migrationEndTime));
+ for (final MigrationInfo migration : migrations) {
+ migration.imported = true;
+ migration.recorder.removeDataBefore(migrationEndTime);
+ if (migration.collection.isEmpty()) continue;
+ migration.recorder.importCollectionLocked(migration.collection);
+ }
+
+ if (endedWithFallback) {
+ Log.wtf(TAG, "Imported platform collections with legacy fallback");
+ } else {
+ Log.i(TAG, "Successfully imported platform collections");
+ }
+ } catch (Throwable e) {
+ // The code above calls OEM code that may behave differently across devices.
+ // It can throw any exception including RuntimeExceptions and
+ // OutOfMemoryErrors. Try to recover anyway.
+ Log.wtf(TAG, "Platform data import failed. Remaining tries "
+ + (targetAttempts - attempts), e);
+
+ // Failed this time around : try again next time unless we're out of tries.
+ try {
+ mImportLegacyAttemptsCounter.set(attempts + 1);
+ } catch (IOException ex) {
+ Log.wtf(TAG, "Failed to update attempts counter.", ex);
+ }
+
+ // Try to remove any data from the failed import.
+ if (migrationEndTime > Long.MIN_VALUE) {
+ try {
+ for (final MigrationInfo migration : migrations) {
+ if (migration.imported) {
+ migration.recorder.removeDataBefore(migrationEndTime);
+ }
+ }
+ } catch (Throwable f) {
+ // If rollback still throws, there isn't much left to do. Try nuking
+ // all data, since that's the last stop. If nuking still throws, the
+ // framework will reboot, and if there are remaining tries, the migration
+ // process will retry, which is fine because it's idempotent.
+ for (final MigrationInfo migration : migrations) {
+ migration.recorder.recoverAndDeleteData();
+ }
+ }
+ }
+
+ return;
+ }
+
+ // Success ! No need to import again next time.
+ try {
+ mImportLegacyAttemptsCounter.set(targetAttempts);
+ // The successes counter is only for debugging. Hence, the synchronization
+ // between these two counters are not very critical.
+ final int successCount = mImportLegacySuccessesCounter.get();
+ mImportLegacySuccessesCounter.set(successCount + 1);
+ } catch (IOException e) {
+ Log.wtf(TAG, "Succeed but failed to update counters.", e);
+ }
+ }
+
+ private static String str(NetworkStatsCollection.Key key) {
+ StringBuilder sb = new StringBuilder()
+ .append(key.ident.toString())
+ .append(" uid=").append(key.uid);
+ if (key.set != SET_FOREGROUND) {
+ sb.append(" set=").append(key.set);
+ }
+ if (key.tag != 0) {
+ sb.append(" tag=").append(key.tag);
+ }
+ return sb.toString();
+ }
+
+ // The importer will modify some keys when importing them.
+ // In order to keep the comparison code simple, add such special cases here and simply
+ // ignore them. This should not impact fidelity much because the start/end checks and the total
+ // bytes check still need to pass.
+ private static boolean couldKeyChangeOnImport(NetworkStatsCollection.Key key) {
+ if (key.ident.isEmpty()) return false;
+ final NetworkIdentity firstIdent = key.ident.iterator().next();
+
+ // Non-mobile network with non-empty RAT type.
+ // This combination is invalid and the NetworkIdentity.Builder will throw if it is passed
+ // in, but it looks like it was previously possible to persist it to disk. The importer sets
+ // the RAT type to NETWORK_TYPE_ALL.
+ if (firstIdent.getType() != ConnectivityManager.TYPE_MOBILE
+ && firstIdent.getRatType() != NetworkTemplate.NETWORK_TYPE_ALL) {
+ return true;
+ }
+
+ return false;
+ }
+
+ @Nullable
+ private static String compareStats(
+ NetworkStatsCollection migrated, NetworkStatsCollection legacy) {
+ final Map<NetworkStatsCollection.Key, NetworkStatsHistory> migEntries =
+ migrated.getEntries();
+ final Map<NetworkStatsCollection.Key, NetworkStatsHistory> legEntries = legacy.getEntries();
+
+ final ArraySet<NetworkStatsCollection.Key> unmatchedLegKeys =
+ new ArraySet<>(legEntries.keySet());
+
+ for (NetworkStatsCollection.Key legKey : legEntries.keySet()) {
+ final NetworkStatsHistory legHistory = legEntries.get(legKey);
+ final NetworkStatsHistory migHistory = migEntries.get(legKey);
+
+ if (migHistory == null && couldKeyChangeOnImport(legKey)) {
+ unmatchedLegKeys.remove(legKey);
+ continue;
+ }
+
+ if (migHistory == null) {
+ return "Missing migrated history for legacy key " + str(legKey)
+ + ", legacy history was " + legHistory;
+ }
+ if (!migHistory.isSameAs(legHistory)) {
+ return "Difference in history for key " + legKey + "; legacy history " + legHistory
+ + ", migrated history " + migHistory;
+ }
+ unmatchedLegKeys.remove(legKey);
+ }
+
+ if (!unmatchedLegKeys.isEmpty()) {
+ final NetworkStatsHistory first = legEntries.get(unmatchedLegKeys.valueAt(0));
+ return "Found unmatched legacy keys: count=" + unmatchedLegKeys.size()
+ + ", first unmatched collection " + first;
+ }
+
+ if (migrated.getStartMillis() != legacy.getStartMillis()
+ || migrated.getEndMillis() != legacy.getEndMillis()) {
+ return "Start / end of the collections "
+ + migrated.getStartMillis() + "/" + legacy.getStartMillis() + " and "
+ + migrated.getEndMillis() + "/" + legacy.getEndMillis()
+ + " don't match";
+ }
+
+ if (migrated.getTotalBytes() != legacy.getTotalBytes()) {
+ return "Total bytes " + migrated.getTotalBytes() + " and " + legacy.getTotalBytes()
+ + " don't match for collections with start/end "
+ + migrated.getStartMillis()
+ + "/" + legacy.getStartMillis();
+ }
+
+ return null;
+ }
+
+ @GuardedBy("mStatsLock")
+ @NonNull
+ private NetworkStatsCollection readPlatformCollectionForRecorder(
+ @NonNull final NetworkStatsRecorder rec) throws IOException {
+ final String prefix = rec.getCookie();
+ Log.i(TAG, "Importing platform collection for prefix " + prefix);
+ final NetworkStatsCollection collection = Objects.requireNonNull(
+ mDeps.readPlatformCollection(prefix, rec.getBucketDuration()),
+ "Imported platform collection for prefix " + prefix + " must not be null");
+
+ final long bootTimestamp = System.currentTimeMillis() - SystemClock.elapsedRealtime();
+ if (!collection.isEmpty() && bootTimestamp < collection.getStartMillis()) {
+ throw new IllegalArgumentException("Platform collection for prefix " + prefix
+ + " contains data that could not possibly come from the previous boot "
+ + "(start timestamp = " + Instant.ofEpochMilli(collection.getStartMillis())
+ + ", last booted at " + Instant.ofEpochMilli(bootTimestamp));
+ }
+
+ Log.i(TAG, "Successfully read platform collection spanning from "
+ // Instant uses ISO-8601 for toString()
+ + Instant.ofEpochMilli(collection.getStartMillis()).toString() + " to "
+ + Instant.ofEpochMilli(collection.getEndMillis()).toString());
+ return collection;
+ }
+
+ /**
+ * Register for a global alert that is delivered through {@link AlertObserver}
+ * or {@link NetworkStatsProviderCallback#onAlertReached()} once a threshold amount of data has
+ * been transferred.
+ */
+ private void registerGlobalAlert() {
+ try {
+ mNetd.bandwidthSetGlobalAlert(mGlobalAlertBytes);
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "problem registering for global alert: " + e);
+ } catch (RemoteException e) {
+ // ignored; service lives in system_server
+ }
+ invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.onSetAlert(mGlobalAlertBytes));
+ }
+
+ @Override
+ public INetworkStatsSession openSession() {
+ return openSessionInternal(NetworkStatsManager.FLAG_AUGMENT_WITH_SUBSCRIPTION_PLAN, null);
+ }
+
+ @Override
+ public INetworkStatsSession openSessionForUsageStats(int flags, String callingPackage) {
+ return openSessionInternal(flags, callingPackage);
+ }
+
+ private boolean isRateLimitedForPoll(@NonNull OpenSessionKey key) {
+ final long lastCallTime;
+ final long now = SystemClock.elapsedRealtime();
+
+ synchronized (mOpenSessionCallsLock) {
+ Integer callsPerCaller = mOpenSessionCallsPerCaller.get(key);
+ if (callsPerCaller == null) {
+ mOpenSessionCallsPerCaller.put((key), 1);
+ } else {
+ mOpenSessionCallsPerCaller.put(key, Integer.sum(callsPerCaller, 1));
+ }
+
+ int callsPerUid = mOpenSessionCallsPerUid.get(key.uid, 0);
+ mOpenSessionCallsPerUid.put(key.uid, callsPerUid + 1);
+
+ if (key.uid == android.os.Process.SYSTEM_UID) {
+ return false;
+ }
+
+ // To avoid a non-system user to be rate-limited after system users open sessions,
+ // so update mLastStatsSessionPoll after checked if the uid is SYSTEM_UID.
+ lastCallTime = mLastStatsSessionPoll;
+ mLastStatsSessionPoll = now;
+ }
+
+ return now - lastCallTime < POLL_RATE_LIMIT_MS;
+ }
+
+ private int restrictFlagsForCaller(int flags, @NonNull String callingPackage) {
+ // All non-privileged callers are not allowed to turn off POLL_ON_OPEN.
+ final boolean isPrivileged = PermissionUtils.checkAnyPermissionOf(mContext,
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ android.Manifest.permission.NETWORK_STACK);
+ if (!isPrivileged) {
+ flags |= NetworkStatsManager.FLAG_POLL_ON_OPEN;
+ }
+ // Non-system uids are rate limited for POLL_ON_OPEN.
+ final int callingUid = Binder.getCallingUid();
+ final OpenSessionKey key = new OpenSessionKey(callingUid, callingPackage);
+ flags = isRateLimitedForPoll(key)
+ ? flags & (~NetworkStatsManager.FLAG_POLL_ON_OPEN)
+ : flags;
+ return flags;
+ }
+
+ private INetworkStatsSession openSessionInternal(final int flags, final String callingPackage) {
+ final int restrictedFlags = restrictFlagsForCaller(flags, callingPackage);
+ if ((restrictedFlags & (NetworkStatsManager.FLAG_POLL_ON_OPEN
+ | NetworkStatsManager.FLAG_POLL_FORCE)) != 0) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ performPoll(FLAG_PERSIST_ALL);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ // return an IBinder which holds strong references to any loaded stats
+ // for its lifetime; when caller closes only weak references remain.
+
+ return new INetworkStatsSession.Stub() {
+ private final int mCallingUid = Binder.getCallingUid();
+ private final String mCallingPackage = callingPackage;
+ private final @NetworkStatsAccess.Level int mAccessLevel = checkAccessLevel(
+ callingPackage);
+
+ private NetworkStatsCollection mUidComplete;
+ private NetworkStatsCollection mUidTagComplete;
+
+ private NetworkStatsCollection getUidComplete() {
+ synchronized (mStatsLock) {
+ if (mUidComplete == null) {
+ mUidComplete = mUidRecorder.getOrLoadCompleteLocked();
+ }
+ return mUidComplete;
+ }
+ }
+
+ private NetworkStatsCollection getUidTagComplete() {
+ synchronized (mStatsLock) {
+ if (mUidTagComplete == null) {
+ mUidTagComplete = mUidTagRecorder.getOrLoadCompleteLocked();
+ }
+ return mUidTagComplete;
+ }
+ }
+
+ @Override
+ public int[] getRelevantUids() {
+ return getUidComplete().getRelevantUids(mAccessLevel);
+ }
+
+ @Override
+ public NetworkStats getDeviceSummaryForNetwork(
+ NetworkTemplate template, long start, long end) {
+ enforceTemplatePermissions(template, callingPackage);
+ return internalGetSummaryForNetwork(template, restrictedFlags, start, end,
+ mAccessLevel, mCallingUid);
+ }
+
+ @Override
+ public NetworkStats getSummaryForNetwork(
+ NetworkTemplate template, long start, long end) {
+ enforceTemplatePermissions(template, callingPackage);
+ return internalGetSummaryForNetwork(template, restrictedFlags, start, end,
+ mAccessLevel, mCallingUid);
+ }
+
+ // TODO: Remove this after all callers are removed.
+ @Override
+ public NetworkStatsHistory getHistoryForNetwork(NetworkTemplate template, int fields) {
+ enforceTemplatePermissions(template, callingPackage);
+ return internalGetHistoryForNetwork(template, restrictedFlags, fields,
+ mAccessLevel, mCallingUid, Long.MIN_VALUE, Long.MAX_VALUE);
+ }
+
+ @Override
+ public NetworkStatsHistory getHistoryIntervalForNetwork(NetworkTemplate template,
+ int fields, long start, long end) {
+ enforceTemplatePermissions(template, callingPackage);
+ // TODO(b/200768422): Redact returned history if the template is location
+ // sensitive but the caller is not privileged.
+ return internalGetHistoryForNetwork(template, restrictedFlags, fields,
+ mAccessLevel, mCallingUid, start, end);
+ }
+
+ @Override
+ public NetworkStats getSummaryForAllUid(
+ NetworkTemplate template, long start, long end, boolean includeTags) {
+ enforceTemplatePermissions(template, callingPackage);
+ try {
+ final NetworkStats stats = getUidComplete()
+ .getSummary(template, start, end, mAccessLevel, mCallingUid);
+ if (includeTags) {
+ final NetworkStats tagStats = getUidTagComplete()
+ .getSummary(template, start, end, mAccessLevel, mCallingUid);
+ stats.combineAllValues(tagStats);
+ }
+ return stats;
+ } catch (NullPointerException e) {
+ throw e;
+ }
+ }
+
+ @Override
+ public NetworkStats getTaggedSummaryForAllUid(
+ NetworkTemplate template, long start, long end) {
+ enforceTemplatePermissions(template, callingPackage);
+ try {
+ final NetworkStats tagStats = getUidTagComplete()
+ .getSummary(template, start, end, mAccessLevel, mCallingUid);
+ return tagStats;
+ } catch (NullPointerException e) {
+ throw e;
+ }
+ }
+
+ @Override
+ public NetworkStatsHistory getHistoryForUid(
+ NetworkTemplate template, int uid, int set, int tag, int fields) {
+ enforceTemplatePermissions(template, callingPackage);
+ // NOTE: We don't augment UID-level statistics
+ if (tag == TAG_NONE) {
+ return getUidComplete().getHistory(template, null, uid, set, tag, fields,
+ Long.MIN_VALUE, Long.MAX_VALUE, mAccessLevel, mCallingUid);
+ } else {
+ return getUidTagComplete().getHistory(template, null, uid, set, tag, fields,
+ Long.MIN_VALUE, Long.MAX_VALUE, mAccessLevel, mCallingUid);
+ }
+ }
+
+ @Override
+ public NetworkStatsHistory getHistoryIntervalForUid(
+ NetworkTemplate template, int uid, int set, int tag, int fields,
+ long start, long end) {
+ enforceTemplatePermissions(template, callingPackage);
+ // TODO(b/200768422): Redact returned history if the template is location
+ // sensitive but the caller is not privileged.
+ // NOTE: We don't augment UID-level statistics
+ if (tag == TAG_NONE) {
+ return getUidComplete().getHistory(template, null, uid, set, tag, fields,
+ start, end, mAccessLevel, mCallingUid);
+ } else if (uid == Binder.getCallingUid()) {
+ return getUidTagComplete().getHistory(template, null, uid, set, tag, fields,
+ start, end, mAccessLevel, mCallingUid);
+ } else {
+ throw new SecurityException("Calling package " + mCallingPackage
+ + " cannot access tag information from a different uid");
+ }
+ }
+
+ @Override
+ public void close() {
+ mUidComplete = null;
+ mUidTagComplete = null;
+ }
+ };
+ }
+
+ private void enforceTemplatePermissions(@NonNull NetworkTemplate template,
+ @NonNull String callingPackage) {
+ // For a template with wifi network keys, it is possible for a malicious
+ // client to track the user locations via querying data usage. Thus, enforce
+ // fine location permission check.
+ if (!template.getWifiNetworkKeys().isEmpty()) {
+ final boolean canAccessFineLocation = mLocationPermissionChecker
+ .checkCallersLocationPermission(callingPackage,
+ null /* featureId */,
+ Binder.getCallingUid(),
+ false /* coarseForTargetSdkLessThanQ */,
+ null /* message */);
+ if (!canAccessFineLocation) {
+ throw new SecurityException("Access fine location is required when querying"
+ + " with wifi network keys, make sure the app has the necessary"
+ + "permissions and the location toggle is on.");
+ }
+ }
+ }
+
+ private @NetworkStatsAccess.Level int checkAccessLevel(String callingPackage) {
+ return NetworkStatsAccess.checkAccessLevel(
+ mContext, Binder.getCallingPid(), Binder.getCallingUid(), callingPackage);
+ }
+
+ /**
+ * Find the most relevant {@link SubscriptionPlan} for the given
+ * {@link NetworkTemplate} and flags. This is typically used to augment
+ * local measurement results to match a known anchor from the carrier.
+ */
+ private SubscriptionPlan resolveSubscriptionPlan(NetworkTemplate template, int flags) {
+ SubscriptionPlan plan = null;
+ if ((flags & NetworkStatsManager.FLAG_AUGMENT_WITH_SUBSCRIPTION_PLAN) != 0
+ && mSettings.getAugmentEnabled()) {
+ if (LOGD) Log.d(TAG, "Resolving plan for " + template);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ plan = mContext.getSystemService(NetworkPolicyManager.class)
+ .getSubscriptionPlan(template);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ if (LOGD) Log.d(TAG, "Resolved to plan " + plan);
+ }
+ return plan;
+ }
+
+ /**
+ * Return network summary, splicing between DEV and XT stats when
+ * appropriate.
+ */
+ private NetworkStats internalGetSummaryForNetwork(NetworkTemplate template, int flags,
+ long start, long end, @NetworkStatsAccess.Level int accessLevel, int callingUid) {
+ // We've been using pure XT stats long enough that we no longer need to
+ // splice DEV and XT together.
+ final NetworkStatsHistory history = internalGetHistoryForNetwork(template, flags, FIELD_ALL,
+ accessLevel, callingUid, start, end);
+
+ final long now = System.currentTimeMillis();
+ final NetworkStatsHistory.Entry entry = history.getValues(start, end, now, null);
+
+ final NetworkStats stats = new NetworkStats(end - start, 1);
+ stats.insertEntry(new NetworkStats.Entry(IFACE_ALL, UID_ALL, SET_ALL, TAG_NONE,
+ METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, entry.rxBytes, entry.rxPackets,
+ entry.txBytes, entry.txPackets, entry.operations));
+ return stats;
+ }
+
+ /**
+ * Return network history, splicing between DEV and XT stats when
+ * appropriate.
+ */
+ private NetworkStatsHistory internalGetHistoryForNetwork(NetworkTemplate template,
+ int flags, int fields, @NetworkStatsAccess.Level int accessLevel, int callingUid,
+ long start, long end) {
+ // We've been using pure XT stats long enough that we no longer need to
+ // splice DEV and XT together.
+ final SubscriptionPlan augmentPlan = resolveSubscriptionPlan(template, flags);
+ synchronized (mStatsLock) {
+ return mXtStatsCached.getHistory(template, augmentPlan,
+ UID_ALL, SET_ALL, TAG_NONE, fields, start, end, accessLevel, callingUid);
+ }
+ }
+
+ private long getNetworkTotalBytes(NetworkTemplate template, long start, long end) {
+ assertSystemReady();
+
+ return internalGetSummaryForNetwork(template,
+ NetworkStatsManager.FLAG_AUGMENT_WITH_SUBSCRIPTION_PLAN, start, end,
+ NetworkStatsAccess.Level.DEVICE, Binder.getCallingUid()).getTotalBytes();
+ }
+
+ private NetworkStats getNetworkUidBytes(NetworkTemplate template, long start, long end) {
+ assertSystemReady();
+
+ final NetworkStatsCollection uidComplete;
+ synchronized (mStatsLock) {
+ uidComplete = mUidRecorder.getOrLoadCompleteLocked();
+ }
+ return uidComplete.getSummary(template, start, end, NetworkStatsAccess.Level.DEVICE,
+ android.os.Process.SYSTEM_UID);
+ }
+
+ @Override
+ public NetworkStats getDataLayerSnapshotForUid(int uid) throws RemoteException {
+ if (Binder.getCallingUid() != uid) {
+ Log.w(TAG, "Snapshots only available for calling UID");
+ return new NetworkStats(SystemClock.elapsedRealtime(), 0);
+ }
+
+ // TODO: switch to data layer stats once kernel exports
+ // for now, read network layer stats and flatten across all ifaces.
+ // This function is used to query NeworkStats for calle's uid. The only caller method
+ // TrafficStats#getDataLayerSnapshotForUid alrady claim no special permission to query
+ // its own NetworkStats.
+ final long ident = Binder.clearCallingIdentity();
+ final NetworkStats networkLayer;
+ try {
+ networkLayer = readNetworkStatsUidDetail(uid, INTERFACES_ALL, TAG_ALL);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+
+ // splice in operation counts
+ networkLayer.spliceOperationsFrom(mUidOperations);
+
+ final NetworkStats dataLayer = new NetworkStats(
+ networkLayer.getElapsedRealtime(), networkLayer.size());
+
+ NetworkStats.Entry entry = null;
+ for (int i = 0; i < networkLayer.size(); i++) {
+ entry = networkLayer.getValues(i, entry);
+ entry.iface = IFACE_ALL;
+ dataLayer.combineValues(entry);
+ }
+
+ return dataLayer;
+ }
+
+ @Override
+ public NetworkStats getUidStatsForTransport(int transport) {
+ PermissionUtils.enforceNetworkStackPermission(mContext);
+ try {
+ final String[] relevantIfaces =
+ transport == TRANSPORT_WIFI ? mWifiIfaces : mMobileIfaces;
+ // TODO(b/215633405) : mMobileIfaces and mWifiIfaces already contain the stacked
+ // interfaces, so this is not useful, remove it.
+ final String[] ifacesToQuery =
+ mStatsFactory.augmentWithStackedInterfaces(relevantIfaces);
+ return getNetworkStatsUidDetail(ifacesToQuery);
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Error compiling UID stats", e);
+ return new NetworkStats(0L, 0);
+ }
+ }
+
+ @Override
+ public String[] getMobileIfaces() {
+ // TODO (b/192758557): Remove debug log.
+ if (CollectionUtils.contains(mMobileIfaces, null)) {
+ throw new NullPointerException(
+ "null element in mMobileIfaces: " + Arrays.toString(mMobileIfaces));
+ }
+ return mMobileIfaces.clone();
+ }
+
+ @Override
+ public void incrementOperationCount(int uid, int tag, int operationCount) {
+ if (Binder.getCallingUid() != uid) {
+ mContext.enforceCallingOrSelfPermission(UPDATE_DEVICE_STATS, TAG);
+ }
+
+ if (operationCount < 0) {
+ throw new IllegalArgumentException("operation count can only be incremented");
+ }
+ if (tag == TAG_NONE) {
+ throw new IllegalArgumentException("operation count must have specific tag");
+ }
+
+ synchronized (mStatsLock) {
+ final int set = mActiveUidCounterSet.get(uid, SET_DEFAULT);
+ mUidOperations.combineValues(
+ mActiveIface, uid, set, tag, 0L, 0L, 0L, 0L, operationCount);
+ mUidOperations.combineValues(
+ mActiveIface, uid, set, TAG_NONE, 0L, 0L, 0L, 0L, operationCount);
+ }
+ }
+
+ private void setKernelCounterSet(int uid, int set) {
+ if (mUidCounterSetMap == null) {
+ Log.wtf(TAG, "Fail to set UidCounterSet: Null bpf map");
+ return;
+ }
+
+ if (set == SET_DEFAULT) {
+ try {
+ mUidCounterSetMap.deleteEntry(new U32(uid));
+ } catch (ErrnoException e) {
+ Log.w(TAG, "UidCounterSetMap.deleteEntry(" + uid + ") failed with errno: " + e);
+ }
+ return;
+ }
+
+ try {
+ mUidCounterSetMap.updateEntry(new U32(uid), new U8((short) set));
+ } catch (ErrnoException e) {
+ Log.w(TAG, "UidCounterSetMap.updateEntry(" + uid + ", " + set
+ + ") failed with errno: " + e);
+ }
+ }
+
+ @VisibleForTesting
+ public void noteUidForeground(int uid, boolean uidForeground) {
+ PermissionUtils.enforceNetworkStackPermission(mContext);
+ synchronized (mStatsLock) {
+ final int set = uidForeground ? SET_FOREGROUND : SET_DEFAULT;
+ final int oldSet = mActiveUidCounterSet.get(uid, SET_DEFAULT);
+ if (oldSet != set) {
+ mActiveUidCounterSet.put(uid, set);
+ setKernelCounterSet(uid, set);
+ }
+ }
+ }
+
+ /**
+ * Notify {@code NetworkStatsService} about network status changed.
+ */
+ public void notifyNetworkStatus(
+ @NonNull Network[] defaultNetworks,
+ @NonNull NetworkStateSnapshot[] networkStates,
+ @Nullable String activeIface,
+ @NonNull UnderlyingNetworkInfo[] underlyingNetworkInfos) {
+ PermissionUtils.enforceNetworkStackPermission(mContext);
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ handleNotifyNetworkStatus(defaultNetworks, networkStates, activeIface);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+
+ // Update the VPN underlying interfaces only after the poll is made and tun data has been
+ // migrated. Otherwise the migration would use the new interfaces instead of the ones that
+ // were current when the polled data was transferred.
+ mStatsFactory.updateUnderlyingNetworkInfos(underlyingNetworkInfos);
+ }
+
+ @Override
+ public void forceUpdate() {
+ PermissionUtils.enforceNetworkStackPermission(mContext);
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ performPoll(FLAG_PERSIST_ALL);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /** Advise persistence threshold; may be overridden internally. */
+ public void advisePersistThreshold(long thresholdBytes) {
+ PermissionUtils.enforceNetworkStackPermission(mContext);
+ // clamp threshold into safe range
+ mPersistThreshold = NetworkStatsUtils.constrain(thresholdBytes,
+ 128 * KB_IN_BYTES, 2 * MB_IN_BYTES);
+ if (LOGV) {
+ Log.v(TAG, "advisePersistThreshold() given " + thresholdBytes + ", clamped to "
+ + mPersistThreshold);
+ }
+
+ final long oldGlobalAlertBytes = mGlobalAlertBytes;
+
+ // update and persist if beyond new thresholds
+ final long currentTime = mClock.millis();
+ synchronized (mStatsLock) {
+ if (!mSystemReady) return;
+
+ updatePersistThresholdsLocked();
+
+ mDevRecorder.maybePersistLocked(currentTime);
+ mXtRecorder.maybePersistLocked(currentTime);
+ mUidRecorder.maybePersistLocked(currentTime);
+ mUidTagRecorder.maybePersistLocked(currentTime);
+ }
+
+ if (oldGlobalAlertBytes != mGlobalAlertBytes) {
+ registerGlobalAlert();
+ }
+ }
+
+ @Override
+ public DataUsageRequest registerUsageCallback(@NonNull String callingPackage,
+ @NonNull DataUsageRequest request, @NonNull IUsageCallback callback) {
+ Objects.requireNonNull(callingPackage, "calling package is null");
+ Objects.requireNonNull(request, "DataUsageRequest is null");
+ Objects.requireNonNull(request.template, "NetworkTemplate is null");
+ Objects.requireNonNull(callback, "callback is null");
+
+ final int callingPid = Binder.getCallingPid();
+ final int callingUid = Binder.getCallingUid();
+ @NetworkStatsAccess.Level int accessLevel = checkAccessLevel(callingPackage);
+ DataUsageRequest normalizedRequest;
+ final long token = Binder.clearCallingIdentity();
+ try {
+ normalizedRequest = mStatsObservers.register(mContext,
+ request, callback, callingPid, callingUid, callingPackage, accessLevel);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+
+ // Create baseline stats
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_PERFORM_POLL));
+
+ return normalizedRequest;
+ }
+
+ @Override
+ public void unregisterUsageRequest(DataUsageRequest request) {
+ Objects.requireNonNull(request, "DataUsageRequest is null");
+
+ int callingUid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mStatsObservers.unregister(request, callingUid);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public long getUidStats(int uid, int type) {
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid != android.os.Process.SYSTEM_UID && callingUid != uid) {
+ return UNSUPPORTED;
+ }
+ return nativeGetUidStat(uid, type);
+ }
+
+ @Override
+ public long getIfaceStats(@NonNull String iface, int type) {
+ Objects.requireNonNull(iface);
+ long nativeIfaceStats = nativeGetIfaceStat(iface, type);
+ if (nativeIfaceStats == -1) {
+ return nativeIfaceStats;
+ } else {
+ // When tethering offload is in use, nativeIfaceStats does not contain usage from
+ // offload, add it back here. Note that the included statistics might be stale
+ // since polling newest stats from hardware might impact system health and not
+ // suitable for TrafficStats API use cases.
+ return nativeIfaceStats + getProviderIfaceStats(iface, type);
+ }
+ }
+
+ @Override
+ public long getTotalStats(int type) {
+ long nativeTotalStats = nativeGetTotalStat(type);
+ if (nativeTotalStats == -1) {
+ return nativeTotalStats;
+ } else {
+ // Refer to comment in getIfaceStats
+ return nativeTotalStats + getProviderIfaceStats(IFACE_ALL, type);
+ }
+ }
+
+ private long getProviderIfaceStats(@Nullable String iface, int type) {
+ final NetworkStats providerSnapshot = getNetworkStatsFromProviders(STATS_PER_IFACE);
+ final HashSet<String> limitIfaces;
+ if (iface == IFACE_ALL) {
+ limitIfaces = null;
+ } else {
+ limitIfaces = new HashSet<>();
+ limitIfaces.add(iface);
+ }
+ final NetworkStats.Entry entry = providerSnapshot.getTotal(null, limitIfaces);
+ switch (type) {
+ case TrafficStats.TYPE_RX_BYTES:
+ return entry.rxBytes;
+ case TrafficStats.TYPE_RX_PACKETS:
+ return entry.rxPackets;
+ case TrafficStats.TYPE_TX_BYTES:
+ return entry.txBytes;
+ case TrafficStats.TYPE_TX_PACKETS:
+ return entry.txPackets;
+ default:
+ return 0;
+ }
+ }
+
+ /**
+ * Update {@link NetworkStatsRecorder} and {@link #mGlobalAlertBytes} to
+ * reflect current {@link #mPersistThreshold} value. Always defers to
+ * {@link Global} values when defined.
+ */
+ @GuardedBy("mStatsLock")
+ private void updatePersistThresholdsLocked() {
+ mDevRecorder.setPersistThreshold(mSettings.getDevPersistBytes(mPersistThreshold));
+ mXtRecorder.setPersistThreshold(mSettings.getXtPersistBytes(mPersistThreshold));
+ mUidRecorder.setPersistThreshold(mSettings.getUidPersistBytes(mPersistThreshold));
+ mUidTagRecorder.setPersistThreshold(mSettings.getUidTagPersistBytes(mPersistThreshold));
+ mGlobalAlertBytes = mSettings.getGlobalAlertBytes(mPersistThreshold);
+ }
+
+ /**
+ * Listener that watches for {@link TetheringManager} to claim interface pairs.
+ */
+ private final TetheringManager.TetheringEventCallback mTetherListener =
+ new TetheringManager.TetheringEventCallback() {
+ @Override
+ public void onUpstreamChanged(@Nullable Network network) {
+ performPoll(FLAG_PERSIST_NETWORK);
+ }
+ };
+
+ private BroadcastReceiver mPollReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // on background handler thread, and verified UPDATE_DEVICE_STATS
+ // permission above.
+ performPoll(FLAG_PERSIST_ALL);
+
+ // verify that we're watching global alert
+ registerGlobalAlert();
+ }
+ };
+
+ private BroadcastReceiver mRemovedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // on background handler thread, and UID_REMOVED is protected
+ // broadcast.
+
+ final int uid = intent.getIntExtra(EXTRA_UID, -1);
+ if (uid == -1) return;
+
+ synchronized (mStatsLock) {
+ mWakeLock.acquire();
+ try {
+ removeUidsLocked(uid);
+ } finally {
+ mWakeLock.release();
+ }
+ }
+ }
+ };
+
+ private BroadcastReceiver mUserReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // On background handler thread, and USER_REMOVED is protected
+ // broadcast.
+
+ final UserHandle userHandle = intent.getParcelableExtra(Intent.EXTRA_USER);
+ if (userHandle == null) return;
+
+ synchronized (mStatsLock) {
+ mWakeLock.acquire();
+ try {
+ removeUserLocked(userHandle);
+ } finally {
+ mWakeLock.release();
+ }
+ }
+ }
+ };
+
+ private BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // SHUTDOWN is protected broadcast.
+ synchronized (mStatsLock) {
+ shutdownLocked();
+ }
+ }
+ };
+
+ /**
+ * Handle collapsed RAT type changed event.
+ */
+ @VisibleForTesting
+ public void handleOnCollapsedRatTypeChanged() {
+ // Protect service from frequently updating. Remove pending messages if any.
+ mHandler.removeMessages(MSG_NOTIFY_NETWORK_STATUS);
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(MSG_NOTIFY_NETWORK_STATUS), mSettings.getPollDelay());
+ }
+
+ private void handleNotifyNetworkStatus(
+ Network[] defaultNetworks,
+ NetworkStateSnapshot[] snapshots,
+ String activeIface) {
+ synchronized (mStatsLock) {
+ mWakeLock.acquire();
+ try {
+ mActiveIface = activeIface;
+ handleNotifyNetworkStatusLocked(defaultNetworks, snapshots);
+ } finally {
+ mWakeLock.release();
+ }
+ }
+ }
+
+ /**
+ * Inspect all current {@link NetworkStateSnapshot}s to derive mapping from {@code iface} to
+ * {@link NetworkStatsHistory}. When multiple networks are active on a single {@code iface},
+ * they are combined under a single {@link NetworkIdentitySet}.
+ */
+ @GuardedBy("mStatsLock")
+ private void handleNotifyNetworkStatusLocked(@NonNull Network[] defaultNetworks,
+ @NonNull NetworkStateSnapshot[] snapshots) {
+ if (!mSystemReady) return;
+ if (LOGV) Log.v(TAG, "handleNotifyNetworkStatusLocked()");
+
+ // take one last stats snapshot before updating iface mapping. this
+ // isn't perfect, since the kernel may already be counting traffic from
+ // the updated network.
+
+ // poll, but only persist network stats to keep codepath fast. UID stats
+ // will be persisted during next alarm poll event.
+ performPollLocked(FLAG_PERSIST_NETWORK);
+
+ // Rebuild active interfaces based on connected networks
+ mActiveIfaces.clear();
+ mActiveUidIfaces.clear();
+ // Update the list of default networks.
+ mDefaultNetworks = defaultNetworks;
+
+ mLastNetworkStateSnapshots = snapshots;
+
+ final boolean combineSubtypeEnabled = mSettings.getCombineSubtypeEnabled();
+ final ArraySet<String> mobileIfaces = new ArraySet<>();
+ final ArraySet<String> wifiIfaces = new ArraySet<>();
+ for (NetworkStateSnapshot snapshot : snapshots) {
+ final int displayTransport =
+ getDisplayTransport(snapshot.getNetworkCapabilities().getTransportTypes());
+ final boolean isMobile = (NetworkCapabilities.TRANSPORT_CELLULAR == displayTransport);
+ final boolean isWifi = (NetworkCapabilities.TRANSPORT_WIFI == displayTransport);
+ final boolean isDefault = CollectionUtils.contains(
+ mDefaultNetworks, snapshot.getNetwork());
+ final int ratType = combineSubtypeEnabled ? NetworkTemplate.NETWORK_TYPE_ALL
+ : getRatTypeForStateSnapshot(snapshot);
+ final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, snapshot,
+ isDefault, ratType);
+
+ // Traffic occurring on the base interface is always counted for
+ // both total usage and UID details.
+ final String baseIface = snapshot.getLinkProperties().getInterfaceName();
+ if (baseIface != null) {
+ findOrCreateNetworkIdentitySet(mActiveIfaces, baseIface).add(ident);
+ findOrCreateNetworkIdentitySet(mActiveUidIfaces, baseIface).add(ident);
+
+ // Build a separate virtual interface for VT (Video Telephony) data usage.
+ // Only do this when IMS is not metered, but VT is metered.
+ // If IMS is metered, then the IMS network usage has already included VT usage.
+ // VT is considered always metered in framework's layer. If VT is not metered
+ // per carrier's policy, modem will report 0 usage for VT calls.
+ if (snapshot.getNetworkCapabilities().hasCapability(
+ NetworkCapabilities.NET_CAPABILITY_IMS) && !ident.isMetered()) {
+
+ // Copy the identify from IMS one but mark it as metered.
+ NetworkIdentity vtIdent = new NetworkIdentity.Builder()
+ .setType(ident.getType())
+ .setRatType(ident.getRatType())
+ .setSubscriberId(ident.getSubscriberId())
+ .setWifiNetworkKey(ident.getWifiNetworkKey())
+ .setRoaming(ident.isRoaming()).setMetered(true)
+ .setDefaultNetwork(true)
+ .setOemManaged(ident.getOemManaged())
+ .setSubId(ident.getSubId()).build();
+ final String ifaceVt = IFACE_VT + getSubIdForMobile(snapshot);
+ findOrCreateNetworkIdentitySet(mActiveIfaces, ifaceVt).add(vtIdent);
+ findOrCreateNetworkIdentitySet(mActiveUidIfaces, ifaceVt).add(vtIdent);
+ }
+
+ if (isMobile) {
+ mobileIfaces.add(baseIface);
+ }
+ if (isWifi) {
+ wifiIfaces.add(baseIface);
+ }
+ }
+
+ // Traffic occurring on stacked interfaces is usually clatd.
+ //
+ // UID stats are always counted on the stacked interface and never on the base
+ // interface, because the packets on the base interface do not actually match
+ // application sockets (they're not IPv4) and thus the app uid is not known.
+ // For receive this is obvious: packets must be translated from IPv6 to IPv4
+ // before the application socket can be found.
+ // For transmit: either they go through the clat daemon which by virtue of going
+ // through userspace strips the original socket association during the IPv4 to
+ // IPv6 translation process, or they are offloaded by eBPF, which doesn't:
+ // However, on an ebpf device the accounting is done in cgroup ebpf hooks,
+ // which don't trigger again post ebpf translation.
+ // (as such stats accounted to the clat uid are ignored)
+ //
+ // Interface stats are more complicated.
+ //
+ // eBPF offloaded 464xlat'ed packets never hit base interface ip6tables, and thus
+ // *all* statistics are collected by iptables on the stacked v4-* interface.
+ //
+ // Additionally for ingress all packets bound for the clat IPv6 address are dropped
+ // in ip6tables raw prerouting and thus even non-offloaded packets are only
+ // accounted for on the stacked interface.
+ //
+ // For egress, packets subject to eBPF offload never appear on the base interface
+ // and only appear on the stacked interface. Thus to ensure packets increment
+ // interface stats, we must collate data from stacked interfaces. For xt_qtaguid
+ // (or non eBPF offloaded) TX they would appear on both, however egress interface
+ // accounting is explicitly bypassed for traffic from the clat uid.
+ //
+ // TODO: This code might be combined to above code.
+ for (String iface : snapshot.getLinkProperties().getAllInterfaceNames()) {
+ // baseIface has been handled, so ignore it.
+ if (TextUtils.equals(baseIface, iface)) continue;
+ if (iface != null) {
+ findOrCreateNetworkIdentitySet(mActiveIfaces, iface).add(ident);
+ findOrCreateNetworkIdentitySet(mActiveUidIfaces, iface).add(ident);
+ if (isMobile) {
+ mobileIfaces.add(iface);
+ }
+ if (isWifi) {
+ wifiIfaces.add(iface);
+ }
+
+ mStatsFactory.noteStackedIface(iface, baseIface);
+ }
+ }
+ }
+
+ mMobileIfaces = mobileIfaces.toArray(new String[0]);
+ mWifiIfaces = wifiIfaces.toArray(new String[0]);
+ // TODO (b/192758557): Remove debug log.
+ if (CollectionUtils.contains(mMobileIfaces, null)) {
+ throw new NullPointerException(
+ "null element in mMobileIfaces: " + Arrays.toString(mMobileIfaces));
+ }
+ if (CollectionUtils.contains(mWifiIfaces, null)) {
+ throw new NullPointerException(
+ "null element in mWifiIfaces: " + Arrays.toString(mWifiIfaces));
+ }
+ }
+
+ private static int getSubIdForMobile(@NonNull NetworkStateSnapshot state) {
+ if (!state.getNetworkCapabilities().hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
+ throw new IllegalArgumentException("Mobile state need capability TRANSPORT_CELLULAR");
+ }
+
+ final NetworkSpecifier spec = state.getNetworkCapabilities().getNetworkSpecifier();
+ if (spec instanceof TelephonyNetworkSpecifier) {
+ return ((TelephonyNetworkSpecifier) spec).getSubscriptionId();
+ } else {
+ Log.wtf(TAG, "getSubIdForState invalid NetworkSpecifier");
+ return INVALID_SUBSCRIPTION_ID;
+ }
+ }
+
+ /**
+ * For networks with {@code TRANSPORT_CELLULAR}, get ratType that was obtained through
+ * {@link PhoneStateListener}. Otherwise, return 0 given that other networks with different
+ * transport types do not actually fill this value.
+ */
+ private int getRatTypeForStateSnapshot(@NonNull NetworkStateSnapshot state) {
+ if (!state.getNetworkCapabilities().hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
+ return 0;
+ }
+
+ return mNetworkStatsSubscriptionsMonitor.getRatTypeForSubscriberId(state.getSubscriberId());
+ }
+
+ private static <K> NetworkIdentitySet findOrCreateNetworkIdentitySet(
+ ArrayMap<K, NetworkIdentitySet> map, K key) {
+ NetworkIdentitySet ident = map.get(key);
+ if (ident == null) {
+ ident = new NetworkIdentitySet();
+ map.put(key, ident);
+ }
+ return ident;
+ }
+
+ @GuardedBy("mStatsLock")
+ private void recordSnapshotLocked(long currentTime) throws RemoteException {
+ // snapshot and record current counters; read UID stats first to
+ // avoid over counting dev stats.
+ Trace.traceBegin(TRACE_TAG_NETWORK, "snapshotUid");
+ final NetworkStats uidSnapshot = getNetworkStatsUidDetail(INTERFACES_ALL);
+ Trace.traceEnd(TRACE_TAG_NETWORK);
+ Trace.traceBegin(TRACE_TAG_NETWORK, "snapshotXt");
+ final NetworkStats xtSnapshot = readNetworkStatsSummaryXt();
+ Trace.traceEnd(TRACE_TAG_NETWORK);
+ Trace.traceBegin(TRACE_TAG_NETWORK, "snapshotDev");
+ final NetworkStats devSnapshot = readNetworkStatsSummaryDev();
+ Trace.traceEnd(TRACE_TAG_NETWORK);
+
+ // Snapshot for dev/xt stats from all custom stats providers. Counts per-interface data
+ // from stats providers that isn't already counted by dev and XT stats.
+ Trace.traceBegin(TRACE_TAG_NETWORK, "snapshotStatsProvider");
+ final NetworkStats providersnapshot = getNetworkStatsFromProviders(STATS_PER_IFACE);
+ Trace.traceEnd(TRACE_TAG_NETWORK);
+ xtSnapshot.combineAllValues(providersnapshot);
+ devSnapshot.combineAllValues(providersnapshot);
+
+ // For xt/dev, we pass a null VPN array because usage is aggregated by UID, so VPN traffic
+ // can't be reattributed to responsible apps.
+ Trace.traceBegin(TRACE_TAG_NETWORK, "recordDev");
+ mDevRecorder.recordSnapshotLocked(devSnapshot, mActiveIfaces, currentTime);
+ Trace.traceEnd(TRACE_TAG_NETWORK);
+ Trace.traceBegin(TRACE_TAG_NETWORK, "recordXt");
+ mXtRecorder.recordSnapshotLocked(xtSnapshot, mActiveIfaces, currentTime);
+ Trace.traceEnd(TRACE_TAG_NETWORK);
+
+ // For per-UID stats, pass the VPN info so VPN traffic is reattributed to responsible apps.
+ Trace.traceBegin(TRACE_TAG_NETWORK, "recordUid");
+ mUidRecorder.recordSnapshotLocked(uidSnapshot, mActiveUidIfaces, currentTime);
+ Trace.traceEnd(TRACE_TAG_NETWORK);
+ Trace.traceBegin(TRACE_TAG_NETWORK, "recordUidTag");
+ mUidTagRecorder.recordSnapshotLocked(uidSnapshot, mActiveUidIfaces, currentTime);
+ Trace.traceEnd(TRACE_TAG_NETWORK);
+
+ // We need to make copies of member fields that are sent to the observer to avoid
+ // a race condition between the service handler thread and the observer's
+ mStatsObservers.updateStats(xtSnapshot, uidSnapshot, new ArrayMap<>(mActiveIfaces),
+ new ArrayMap<>(mActiveUidIfaces), currentTime);
+ }
+
+ /**
+ * Bootstrap initial stats snapshot, usually during {@link #systemReady()}
+ * so we have baseline values without double-counting.
+ */
+ @GuardedBy("mStatsLock")
+ private void bootstrapStatsLocked() {
+ final long currentTime = mClock.millis();
+
+ try {
+ recordSnapshotLocked(currentTime);
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "problem reading network stats: " + e);
+ } catch (RemoteException e) {
+ // ignored; service lives in system_server
+ }
+ }
+
+ private void performPoll(int flags) {
+ synchronized (mStatsLock) {
+ mWakeLock.acquire();
+
+ try {
+ performPollLocked(flags);
+ } finally {
+ mWakeLock.release();
+ }
+ }
+ }
+
+ /**
+ * Periodic poll operation, reading current statistics and recording into
+ * {@link NetworkStatsHistory}.
+ */
+ @GuardedBy("mStatsLock")
+ private void performPollLocked(int flags) {
+ if (!mSystemReady) return;
+ if (LOGV) Log.v(TAG, "performPollLocked(flags=0x" + Integer.toHexString(flags) + ")");
+ Trace.traceBegin(TRACE_TAG_NETWORK, "performPollLocked");
+
+ final boolean persistNetwork = (flags & FLAG_PERSIST_NETWORK) != 0;
+ final boolean persistUid = (flags & FLAG_PERSIST_UID) != 0;
+ final boolean persistForce = (flags & FLAG_PERSIST_FORCE) != 0;
+
+ performPollFromProvidersLocked();
+
+ // TODO: consider marking "untrusted" times in historical stats
+ final long currentTime = mClock.millis();
+
+ try {
+ recordSnapshotLocked(currentTime);
+ } catch (IllegalStateException e) {
+ Log.wtf(TAG, "problem reading network stats", e);
+ return;
+ } catch (RemoteException e) {
+ // ignored; service lives in system_server
+ return;
+ }
+
+ // persist any pending data depending on requested flags
+ Trace.traceBegin(TRACE_TAG_NETWORK, "[persisting]");
+ if (persistForce) {
+ mDevRecorder.forcePersistLocked(currentTime);
+ mXtRecorder.forcePersistLocked(currentTime);
+ mUidRecorder.forcePersistLocked(currentTime);
+ mUidTagRecorder.forcePersistLocked(currentTime);
+ } else {
+ if (persistNetwork) {
+ mDevRecorder.maybePersistLocked(currentTime);
+ mXtRecorder.maybePersistLocked(currentTime);
+ }
+ if (persistUid) {
+ mUidRecorder.maybePersistLocked(currentTime);
+ mUidTagRecorder.maybePersistLocked(currentTime);
+ }
+ }
+ Trace.traceEnd(TRACE_TAG_NETWORK);
+
+ if (mSettings.getSampleEnabled()) {
+ // sample stats after each full poll
+ performSampleLocked();
+ }
+
+ // finally, dispatch updated event to any listeners
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_BROADCAST_NETWORK_STATS_UPDATED));
+
+ Trace.traceEnd(TRACE_TAG_NETWORK);
+ }
+
+ @GuardedBy("mStatsLock")
+ private void performPollFromProvidersLocked() {
+ // Request asynchronous stats update from all providers for next poll. And wait a bit of
+ // time to allow providers report-in given that normally binder call should be fast. Note
+ // that size of list might be changed because addition/removing at the same time. For
+ // addition, the stats of the missed provider can only be collected in next poll;
+ // for removal, wait might take up to MAX_STATS_PROVIDER_POLL_WAIT_TIME_MS
+ // once that happened.
+ // TODO: request with a valid token.
+ Trace.traceBegin(TRACE_TAG_NETWORK, "provider.requestStatsUpdate");
+ final int registeredCallbackCount = mStatsProviderCbList.size();
+ mStatsProviderSem.drainPermits();
+ invokeForAllStatsProviderCallbacks(
+ (cb) -> cb.mProvider.onRequestStatsUpdate(0 /* unused */));
+ try {
+ mStatsProviderSem.tryAcquire(registeredCallbackCount,
+ MAX_STATS_PROVIDER_POLL_WAIT_TIME_MS, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ // Strictly speaking it's possible a provider happened to deliver between the timeout
+ // and the log, and that doesn't matter too much as this is just a debug log.
+ Log.d(TAG, "requestStatsUpdate - providers responded "
+ + mStatsProviderSem.availablePermits()
+ + "/" + registeredCallbackCount + " : " + e);
+ }
+ Trace.traceEnd(TRACE_TAG_NETWORK);
+ }
+
+ /**
+ * Sample recent statistics summary into {@link EventLog}.
+ */
+ @GuardedBy("mStatsLock")
+ private void performSampleLocked() {
+ // TODO: migrate trustedtime fixes to separate binary log events
+ final long currentTime = mClock.millis();
+
+ NetworkTemplate template;
+ NetworkStats.Entry devTotal;
+ NetworkStats.Entry xtTotal;
+ NetworkStats.Entry uidTotal;
+
+ // collect mobile sample
+ template = buildTemplateMobileWildcard();
+ devTotal = mDevRecorder.getTotalSinceBootLocked(template);
+ xtTotal = mXtRecorder.getTotalSinceBootLocked(template);
+ uidTotal = mUidRecorder.getTotalSinceBootLocked(template);
+
+ EventLog.writeEvent(LOG_TAG_NETSTATS_MOBILE_SAMPLE,
+ devTotal.rxBytes, devTotal.rxPackets, devTotal.txBytes, devTotal.txPackets,
+ xtTotal.rxBytes, xtTotal.rxPackets, xtTotal.txBytes, xtTotal.txPackets,
+ uidTotal.rxBytes, uidTotal.rxPackets, uidTotal.txBytes, uidTotal.txPackets,
+ currentTime);
+
+ // collect wifi sample
+ template = buildTemplateWifiWildcard();
+ devTotal = mDevRecorder.getTotalSinceBootLocked(template);
+ xtTotal = mXtRecorder.getTotalSinceBootLocked(template);
+ uidTotal = mUidRecorder.getTotalSinceBootLocked(template);
+
+ EventLog.writeEvent(LOG_TAG_NETSTATS_WIFI_SAMPLE,
+ devTotal.rxBytes, devTotal.rxPackets, devTotal.txBytes, devTotal.txPackets,
+ xtTotal.rxBytes, xtTotal.rxPackets, xtTotal.txBytes, xtTotal.txPackets,
+ uidTotal.rxBytes, uidTotal.rxPackets, uidTotal.txBytes, uidTotal.txPackets,
+ currentTime);
+ }
+
+ // deleteKernelTagData can ignore ENOENT; otherwise we should log an error
+ private void logErrorIfNotErrNoent(final ErrnoException e, final String msg) {
+ if (e.errno != ENOENT) Log.e(TAG, msg, e);
+ }
+
+ private <K extends StatsMapKey, V extends StatsMapValue> void deleteStatsMapTagData(
+ IBpfMap<K, V> statsMap, int uid) {
+ try {
+ statsMap.forEach((key, value) -> {
+ if (key.uid == uid) {
+ try {
+ statsMap.deleteEntry(key);
+ } catch (ErrnoException e) {
+ logErrorIfNotErrNoent(e, "Failed to delete data(uid = " + key.uid + ")");
+ }
+ }
+ });
+ } catch (ErrnoException e) {
+ Log.e(TAG, "FAILED to delete tag data from stats map", e);
+ }
+ }
+
+ /**
+ * Deletes uid tag data from CookieTagMap, StatsMapA, StatsMapB, and UidStatsMap
+ * @param uid
+ */
+ private void deleteKernelTagData(int uid) {
+ try {
+ mCookieTagMap.forEach((key, value) -> {
+ // If SkDestroyListener deletes the socket tag while this code is running,
+ // forEach will either restart iteration from the beginning or return null,
+ // depending on when the deletion happens.
+ // If it returns null, continue iteration to delete the data and in fact it would
+ // just iterate from first key because BpfMap#getNextKey would return first key
+ // if the current key is not exist.
+ if (value != null && value.uid == uid) {
+ try {
+ mCookieTagMap.deleteEntry(key);
+ } catch (ErrnoException e) {
+ logErrorIfNotErrNoent(e, "Failed to delete data(cookie = " + key + ")");
+ }
+ }
+ });
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Failed to delete tag data from cookie tag map", e);
+ }
+
+ deleteStatsMapTagData(mStatsMapA, uid);
+ deleteStatsMapTagData(mStatsMapB, uid);
+
+ try {
+ mUidCounterSetMap.deleteEntry(new U32(uid));
+ } catch (ErrnoException e) {
+ logErrorIfNotErrNoent(e, "Failed to delete tag data from uid counter set map");
+ }
+
+ try {
+ mAppUidStatsMap.deleteEntry(new UidStatsMapKey(uid));
+ } catch (ErrnoException e) {
+ logErrorIfNotErrNoent(e, "Failed to delete tag data from app uid stats map");
+ }
+ }
+
+ /**
+ * Clean up {@link #mUidRecorder} after UID is removed.
+ */
+ @GuardedBy("mStatsLock")
+ private void removeUidsLocked(int... uids) {
+ if (LOGV) Log.v(TAG, "removeUidsLocked() for UIDs " + Arrays.toString(uids));
+
+ // Perform one last poll before removing
+ performPollLocked(FLAG_PERSIST_ALL);
+
+ mUidRecorder.removeUidsLocked(uids);
+ mUidTagRecorder.removeUidsLocked(uids);
+
+ // Clear kernel stats associated with UID
+ for (int uid : uids) {
+ deleteKernelTagData(uid);
+ }
+
+ // TODO: Remove the UID's entries from mOpenSessionCallsPerUid and
+ // mOpenSessionCallsPerCaller
+ }
+
+ /**
+ * Clean up {@link #mUidRecorder} after user is removed.
+ */
+ @GuardedBy("mStatsLock")
+ private void removeUserLocked(@NonNull UserHandle userHandle) {
+ if (LOGV) Log.v(TAG, "removeUserLocked() for UserHandle=" + userHandle);
+
+ // Build list of UIDs that we should clean up
+ final ArrayList<Integer> uids = new ArrayList<>();
+ final List<ApplicationInfo> apps = mContext.getPackageManager().getInstalledApplications(
+ PackageManager.MATCH_ANY_USER
+ | PackageManager.MATCH_DISABLED_COMPONENTS);
+ for (ApplicationInfo app : apps) {
+ final int uid = userHandle.getUid(app.uid);
+ uids.add(uid);
+ }
+
+ removeUidsLocked(CollectionUtils.toIntArray(uids));
+ }
+
+ /**
+ * Set the warning and limit to all registered custom network stats providers.
+ * Note that invocation of any interface will be sent to all providers.
+ */
+ public void setStatsProviderWarningAndLimitAsync(
+ @NonNull String iface, long warning, long limit) {
+ PermissionUtils.enforceNetworkStackPermission(mContext);
+ if (LOGV) {
+ Log.v(TAG, "setStatsProviderWarningAndLimitAsync("
+ + iface + "," + warning + "," + limit + ")");
+ }
+ invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.onSetWarningAndLimit(iface,
+ warning, limit));
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter rawWriter, String[] args) {
+ if (!PermissionUtils.checkDumpPermission(mContext, TAG, rawWriter)) return;
+
+ long duration = DateUtils.DAY_IN_MILLIS;
+ final HashSet<String> argSet = new HashSet<String>();
+ for (String arg : args) {
+ argSet.add(arg);
+
+ if (arg.startsWith("--duration=")) {
+ try {
+ duration = Long.parseLong(arg.substring(11));
+ } catch (NumberFormatException ignored) {
+ }
+ }
+ }
+
+ // usage: dumpsys netstats --full --uid --tag --poll --checkin
+ final boolean poll = argSet.contains("--poll") || argSet.contains("poll");
+ final boolean checkin = argSet.contains("--checkin");
+ final boolean fullHistory = argSet.contains("--full") || argSet.contains("full");
+ final boolean includeUid = argSet.contains("--uid") || argSet.contains("detail");
+ final boolean includeTag = argSet.contains("--tag") || argSet.contains("detail");
+
+ final IndentingPrintWriter pw = new IndentingPrintWriter(rawWriter, " ");
+
+ synchronized (mStatsLock) {
+ if (args.length > 0 && "--proto".equals(args[0])) {
+ // In this case ignore all other arguments.
+ dumpProtoLocked(fd);
+ return;
+ }
+
+ if (poll) {
+ performPollLocked(FLAG_PERSIST_ALL | FLAG_PERSIST_FORCE);
+ pw.println("Forced poll");
+ return;
+ }
+
+ if (checkin) {
+ final long end = System.currentTimeMillis();
+ final long start = end - duration;
+
+ pw.print("v1,");
+ pw.print(start / SECOND_IN_MILLIS); pw.print(',');
+ pw.print(end / SECOND_IN_MILLIS); pw.println();
+
+ pw.println("xt");
+ mXtRecorder.dumpCheckin(rawWriter, start, end);
+
+ if (includeUid) {
+ pw.println("uid");
+ mUidRecorder.dumpCheckin(rawWriter, start, end);
+ }
+ if (includeTag) {
+ pw.println("tag");
+ mUidTagRecorder.dumpCheckin(rawWriter, start, end);
+ }
+ return;
+ }
+
+ pw.println("Directory:");
+ pw.increaseIndent();
+ pw.println(mStatsDir);
+ pw.decreaseIndent();
+
+ pw.println("Configs:");
+ pw.increaseIndent();
+ pw.print(NETSTATS_COMBINE_SUBTYPE_ENABLED, mSettings.getCombineSubtypeEnabled());
+ pw.println();
+ pw.print(NETSTATS_STORE_FILES_IN_APEXDATA, mDeps.getStoreFilesInApexData());
+ pw.println();
+ pw.print(NETSTATS_IMPORT_LEGACY_TARGET_ATTEMPTS, mDeps.getImportLegacyTargetAttempts());
+ pw.println();
+ if (mDeps.getStoreFilesInApexData()) {
+ try {
+ pw.print("platform legacy stats import attempts count",
+ mImportLegacyAttemptsCounter.get());
+ pw.println();
+ pw.print("platform legacy stats import successes count",
+ mImportLegacySuccessesCounter.get());
+ pw.println();
+ } catch (IOException e) {
+ pw.println("(failed to dump platform legacy stats import counters)");
+ }
+ }
+
+ pw.decreaseIndent();
+
+ pw.println("Active interfaces:");
+ pw.increaseIndent();
+ for (int i = 0; i < mActiveIfaces.size(); i++) {
+ pw.print("iface", mActiveIfaces.keyAt(i));
+ pw.print("ident", mActiveIfaces.valueAt(i));
+ pw.println();
+ }
+ pw.decreaseIndent();
+
+ pw.println("Active UID interfaces:");
+ pw.increaseIndent();
+ for (int i = 0; i < mActiveUidIfaces.size(); i++) {
+ pw.print("iface", mActiveUidIfaces.keyAt(i));
+ pw.print("ident", mActiveUidIfaces.valueAt(i));
+ pw.println();
+ }
+ pw.decreaseIndent();
+
+ // Get the top openSession callers
+ final HashMap calls;
+ synchronized (mOpenSessionCallsLock) {
+ calls = new HashMap<>(mOpenSessionCallsPerCaller);
+ }
+ final List<Map.Entry<OpenSessionKey, Integer>> list = new ArrayList<>(calls.entrySet());
+ Collections.sort(list,
+ (left, right) -> Integer.compare(left.getValue(), right.getValue()));
+ final int num = list.size();
+ final int end = Math.max(0, num - DUMP_STATS_SESSION_COUNT);
+ pw.println("Top openSession callers:");
+ pw.increaseIndent();
+ for (int j = num - 1; j >= end; j--) {
+ final Map.Entry<OpenSessionKey, Integer> entry = list.get(j);
+ pw.print(entry.getKey()); pw.print("="); pw.println(entry.getValue());
+
+ }
+ pw.decreaseIndent();
+ pw.println();
+
+ pw.println("Stats Providers:");
+ pw.increaseIndent();
+ invokeForAllStatsProviderCallbacks((cb) -> {
+ pw.println(cb.mTag + " Xt:");
+ pw.increaseIndent();
+ pw.print(cb.getCachedStats(STATS_PER_IFACE).toString());
+ pw.decreaseIndent();
+ if (includeUid) {
+ pw.println(cb.mTag + " Uid:");
+ pw.increaseIndent();
+ pw.print(cb.getCachedStats(STATS_PER_UID).toString());
+ pw.decreaseIndent();
+ }
+ });
+ pw.decreaseIndent();
+ pw.println();
+
+ pw.println("Stats Observers:");
+ pw.increaseIndent();
+ mStatsObservers.dump(pw);
+ pw.decreaseIndent();
+ pw.println();
+
+ pw.println("Dev stats:");
+ pw.increaseIndent();
+ mDevRecorder.dumpLocked(pw, fullHistory);
+ pw.decreaseIndent();
+
+ pw.println("Xt stats:");
+ pw.increaseIndent();
+ mXtRecorder.dumpLocked(pw, fullHistory);
+ pw.decreaseIndent();
+
+ if (includeUid) {
+ pw.println("UID stats:");
+ pw.increaseIndent();
+ mUidRecorder.dumpLocked(pw, fullHistory);
+ pw.decreaseIndent();
+ }
+
+ if (includeTag) {
+ pw.println("UID tag stats:");
+ pw.increaseIndent();
+ mUidTagRecorder.dumpLocked(pw, fullHistory);
+ pw.decreaseIndent();
+ }
+ }
+ }
+
+ @GuardedBy("mStatsLock")
+ private void dumpProtoLocked(FileDescriptor fd) {
+ final ProtoOutputStream proto = new ProtoOutputStream(new FileOutputStream(fd));
+
+ // TODO Right now it writes all history. Should it limit to the "since-boot" log?
+
+ dumpInterfaces(proto, NetworkStatsServiceDumpProto.ACTIVE_INTERFACES,
+ mActiveIfaces);
+ dumpInterfaces(proto, NetworkStatsServiceDumpProto.ACTIVE_UID_INTERFACES,
+ mActiveUidIfaces);
+ mDevRecorder.dumpDebugLocked(proto, NetworkStatsServiceDumpProto.DEV_STATS);
+ mXtRecorder.dumpDebugLocked(proto, NetworkStatsServiceDumpProto.XT_STATS);
+ mUidRecorder.dumpDebugLocked(proto, NetworkStatsServiceDumpProto.UID_STATS);
+ mUidTagRecorder.dumpDebugLocked(proto,
+ NetworkStatsServiceDumpProto.UID_TAG_STATS);
+
+ proto.flush();
+ }
+
+ private static void dumpInterfaces(ProtoOutputStream proto, long tag,
+ ArrayMap<String, NetworkIdentitySet> ifaces) {
+ for (int i = 0; i < ifaces.size(); i++) {
+ final long start = proto.start(tag);
+
+ proto.write(NetworkInterfaceProto.INTERFACE, ifaces.keyAt(i));
+ ifaces.valueAt(i).dumpDebug(proto, NetworkInterfaceProto.IDENTITIES);
+
+ proto.end(start);
+ }
+ }
+
+ private NetworkStats readNetworkStatsSummaryDev() {
+ try {
+ return mStatsFactory.readNetworkStatsSummaryDev();
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private NetworkStats readNetworkStatsSummaryXt() {
+ try {
+ return mStatsFactory.readNetworkStatsSummaryXt();
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private NetworkStats readNetworkStatsUidDetail(int uid, String[] ifaces, int tag) {
+ try {
+ return mStatsFactory.readNetworkStatsDetail(uid, ifaces, tag);
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ /**
+ * Return snapshot of current UID statistics, including any
+ * {@link TrafficStats#UID_TETHERING}, video calling data usage, and {@link #mUidOperations}
+ * values.
+ *
+ * @param ifaces A list of interfaces the stats should be restricted to, or
+ * {@link NetworkStats#INTERFACES_ALL}.
+ */
+ private NetworkStats getNetworkStatsUidDetail(String[] ifaces)
+ throws RemoteException {
+ final NetworkStats uidSnapshot = readNetworkStatsUidDetail(UID_ALL, ifaces, TAG_ALL);
+
+ // fold tethering stats and operations into uid snapshot
+ final NetworkStats tetherSnapshot = getNetworkStatsTethering(STATS_PER_UID);
+ tetherSnapshot.filter(UID_ALL, ifaces, TAG_ALL);
+ mStatsFactory.apply464xlatAdjustments(uidSnapshot, tetherSnapshot);
+ uidSnapshot.combineAllValues(tetherSnapshot);
+
+ // get a stale copy of uid stats snapshot provided by providers.
+ final NetworkStats providerStats = getNetworkStatsFromProviders(STATS_PER_UID);
+ providerStats.filter(UID_ALL, ifaces, TAG_ALL);
+ mStatsFactory.apply464xlatAdjustments(uidSnapshot, providerStats);
+ uidSnapshot.combineAllValues(providerStats);
+
+ uidSnapshot.combineAllValues(mUidOperations);
+
+ return uidSnapshot;
+ }
+
+ /**
+ * Return snapshot of current non-offloaded tethering statistics. Will return empty
+ * {@link NetworkStats} if any problems are encountered, or queried by {@code STATS_PER_IFACE}
+ * since it is already included by {@link #nativeGetIfaceStat}.
+ * See {@code OffloadTetheringStatsProvider} for offloaded tethering stats.
+ */
+ // TODO: Remove this by implementing {@link NetworkStatsProvider} for non-offloaded
+ // tethering stats.
+ private @NonNull NetworkStats getNetworkStatsTethering(int how) throws RemoteException {
+ // We only need to return per-UID stats. Per-device stats are already counted by
+ // interface counters.
+ if (how != STATS_PER_UID) {
+ return new NetworkStats(SystemClock.elapsedRealtime(), 0);
+ }
+
+ final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 1);
+ try {
+ final TetherStatsParcel[] tetherStatsParcels = mNetd.tetherGetStats();
+ for (TetherStatsParcel tetherStats : tetherStatsParcels) {
+ try {
+ stats.combineValues(new NetworkStats.Entry(tetherStats.iface, UID_TETHERING,
+ SET_DEFAULT, TAG_NONE, tetherStats.rxBytes, tetherStats.rxPackets,
+ tetherStats.txBytes, tetherStats.txPackets, 0L));
+ } catch (ArrayIndexOutOfBoundsException e) {
+ throw new IllegalStateException("invalid tethering stats " + e);
+ }
+ }
+ } catch (IllegalStateException e) {
+ Log.wtf(TAG, "problem reading network stats", e);
+ }
+ return stats;
+ }
+
+ // TODO: It is copied from ConnectivityService, consider refactor these check permission
+ // functions to a proper util.
+ private boolean checkAnyPermissionOf(String... permissions) {
+ for (String permission : permissions) {
+ if (mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void enforceAnyPermissionOf(String... permissions) {
+ if (!checkAnyPermissionOf(permissions)) {
+ throw new SecurityException("Requires one of the following permissions: "
+ + String.join(", ", permissions) + ".");
+ }
+ }
+
+ /**
+ * Registers a custom provider of {@link android.net.NetworkStats} to combine the network
+ * statistics that cannot be seen by the kernel to system. To unregister, invoke the
+ * {@code unregister()} of the returned callback.
+ *
+ * @param tag a human readable identifier of the custom network stats provider.
+ * @param provider the {@link INetworkStatsProvider} binder corresponding to the
+ * {@link NetworkStatsProvider} to be registered.
+ *
+ * @return a {@link INetworkStatsProviderCallback} binder
+ * interface, which can be used to report events to the system.
+ */
+ public @NonNull INetworkStatsProviderCallback registerNetworkStatsProvider(
+ @NonNull String tag, @NonNull INetworkStatsProvider provider) {
+ enforceAnyPermissionOf(NETWORK_STATS_PROVIDER,
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
+ Objects.requireNonNull(provider, "provider is null");
+ Objects.requireNonNull(tag, "tag is null");
+ final NetworkPolicyManager netPolicyManager = mContext
+ .getSystemService(NetworkPolicyManager.class);
+ try {
+ NetworkStatsProviderCallbackImpl callback = new NetworkStatsProviderCallbackImpl(
+ tag, provider, mStatsProviderSem, mAlertObserver,
+ mStatsProviderCbList, netPolicyManager);
+ mStatsProviderCbList.add(callback);
+ Log.d(TAG, "registerNetworkStatsProvider from " + callback.mTag + " uid/pid="
+ + getCallingUid() + "/" + getCallingPid());
+ return callback;
+ } catch (RemoteException e) {
+ Log.e(TAG, "registerNetworkStatsProvider failed", e);
+ }
+ return null;
+ }
+
+ // Collect stats from local cache of providers.
+ private @NonNull NetworkStats getNetworkStatsFromProviders(int how) {
+ final NetworkStats ret = new NetworkStats(0L, 0);
+ invokeForAllStatsProviderCallbacks((cb) -> ret.combineAllValues(cb.getCachedStats(how)));
+ return ret;
+ }
+
+ @FunctionalInterface
+ private interface ThrowingConsumer<S, T extends Throwable> {
+ void accept(S s) throws T;
+ }
+
+ private void invokeForAllStatsProviderCallbacks(
+ @NonNull ThrowingConsumer<NetworkStatsProviderCallbackImpl, RemoteException> task) {
+ for (final NetworkStatsProviderCallbackImpl cb : mStatsProviderCbList) {
+ try {
+ task.accept(cb);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Fail to broadcast to provider: " + cb.mTag, e);
+ }
+ }
+ }
+
+ private static class NetworkStatsProviderCallbackImpl extends INetworkStatsProviderCallback.Stub
+ implements IBinder.DeathRecipient {
+ @NonNull final String mTag;
+
+ @NonNull final INetworkStatsProvider mProvider;
+ @NonNull private final Semaphore mSemaphore;
+ @NonNull final AlertObserver mAlertObserver;
+ @NonNull final CopyOnWriteArrayList<NetworkStatsProviderCallbackImpl> mStatsProviderCbList;
+ @NonNull final NetworkPolicyManager mNetworkPolicyManager;
+
+ @NonNull private final Object mProviderStatsLock = new Object();
+
+ @GuardedBy("mProviderStatsLock")
+ // Track STATS_PER_IFACE and STATS_PER_UID separately.
+ private final NetworkStats mIfaceStats = new NetworkStats(0L, 0);
+ @GuardedBy("mProviderStatsLock")
+ private final NetworkStats mUidStats = new NetworkStats(0L, 0);
+
+ NetworkStatsProviderCallbackImpl(
+ @NonNull String tag, @NonNull INetworkStatsProvider provider,
+ @NonNull Semaphore semaphore,
+ @NonNull AlertObserver alertObserver,
+ @NonNull CopyOnWriteArrayList<NetworkStatsProviderCallbackImpl> cbList,
+ @NonNull NetworkPolicyManager networkPolicyManager)
+ throws RemoteException {
+ mTag = tag;
+ mProvider = provider;
+ mProvider.asBinder().linkToDeath(this, 0);
+ mSemaphore = semaphore;
+ mAlertObserver = alertObserver;
+ mStatsProviderCbList = cbList;
+ mNetworkPolicyManager = networkPolicyManager;
+ }
+
+ @NonNull
+ public NetworkStats getCachedStats(int how) {
+ synchronized (mProviderStatsLock) {
+ NetworkStats stats;
+ switch (how) {
+ case STATS_PER_IFACE:
+ stats = mIfaceStats;
+ break;
+ case STATS_PER_UID:
+ stats = mUidStats;
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid type: " + how);
+ }
+ // Callers might be able to mutate the returned object. Return a defensive copy
+ // instead of local reference.
+ return stats.clone();
+ }
+ }
+
+ @Override
+ public void notifyStatsUpdated(int token, @Nullable NetworkStats ifaceStats,
+ @Nullable NetworkStats uidStats) {
+ // TODO: 1. Use token to map ifaces to correct NetworkIdentity.
+ // 2. Store the difference and store it directly to the recorder.
+ synchronized (mProviderStatsLock) {
+ if (ifaceStats != null) mIfaceStats.combineAllValues(ifaceStats);
+ if (uidStats != null) mUidStats.combineAllValues(uidStats);
+ }
+ mSemaphore.release();
+ }
+
+ @Override
+ public void notifyAlertReached() throws RemoteException {
+ // This binder object can only have been obtained by a process that holds
+ // NETWORK_STATS_PROVIDER. Thus, no additional permission check is required.
+ BinderUtils.withCleanCallingIdentity(() ->
+ mAlertObserver.onQuotaLimitReached(LIMIT_GLOBAL_ALERT, null /* unused */));
+ }
+
+ @Override
+ public void notifyWarningReached() {
+ Log.d(TAG, mTag + ": notifyWarningReached");
+ BinderUtils.withCleanCallingIdentity(() ->
+ mNetworkPolicyManager.notifyStatsProviderWarningReached());
+ }
+
+ @Override
+ public void notifyLimitReached() {
+ Log.d(TAG, mTag + ": notifyLimitReached");
+ BinderUtils.withCleanCallingIdentity(() ->
+ mNetworkPolicyManager.notifyStatsProviderLimitReached());
+ }
+
+ @Override
+ public void binderDied() {
+ Log.d(TAG, mTag + ": binderDied");
+ mStatsProviderCbList.remove(this);
+ }
+
+ @Override
+ public void unregister() {
+ Log.d(TAG, mTag + ": unregister");
+ mStatsProviderCbList.remove(this);
+ }
+
+ }
+
+ private void assertSystemReady() {
+ if (!mSystemReady) {
+ throw new IllegalStateException("System not ready");
+ }
+ }
+
+ private class DropBoxNonMonotonicObserver implements NonMonotonicObserver<String> {
+ @Override
+ public void foundNonMonotonic(NetworkStats left, int leftIndex, NetworkStats right,
+ int rightIndex, String cookie) {
+ Log.w(TAG, "Found non-monotonic values; saving to dropbox");
+
+ // record error for debugging
+ final StringBuilder builder = new StringBuilder();
+ builder.append("found non-monotonic " + cookie + " values at left[" + leftIndex
+ + "] - right[" + rightIndex + "]\n");
+ builder.append("left=").append(left).append('\n');
+ builder.append("right=").append(right).append('\n');
+
+ mContext.getSystemService(DropBoxManager.class).addText(TAG_NETSTATS_ERROR,
+ builder.toString());
+ }
+
+ @Override
+ public void foundNonMonotonic(
+ NetworkStats stats, int statsIndex, String cookie) {
+ Log.w(TAG, "Found non-monotonic values; saving to dropbox");
+
+ final StringBuilder builder = new StringBuilder();
+ builder.append("Found non-monotonic " + cookie + " values at [" + statsIndex + "]\n");
+ builder.append("stats=").append(stats).append('\n');
+
+ mContext.getSystemService(DropBoxManager.class).addText(TAG_NETSTATS_ERROR,
+ builder.toString());
+ }
+ }
+
+ /**
+ * Default external settings that read from
+ * {@link android.provider.Settings.Global}.
+ */
+ private static class DefaultNetworkStatsSettings implements NetworkStatsSettings {
+ DefaultNetworkStatsSettings() {}
+
+ @Override
+ public long getPollInterval() {
+ return 30 * MINUTE_IN_MILLIS;
+ }
+ @Override
+ public long getPollDelay() {
+ return DEFAULT_PERFORM_POLL_DELAY_MS;
+ }
+ @Override
+ public long getGlobalAlertBytes(long def) {
+ return def;
+ }
+ @Override
+ public boolean getSampleEnabled() {
+ return true;
+ }
+ @Override
+ public boolean getAugmentEnabled() {
+ return true;
+ }
+ @Override
+ public boolean getCombineSubtypeEnabled() {
+ return false;
+ }
+ @Override
+ public Config getDevConfig() {
+ return new Config(HOUR_IN_MILLIS, 15 * DAY_IN_MILLIS, 90 * DAY_IN_MILLIS);
+ }
+ @Override
+ public Config getXtConfig() {
+ return getDevConfig();
+ }
+ @Override
+ public Config getUidConfig() {
+ return new Config(2 * HOUR_IN_MILLIS, 15 * DAY_IN_MILLIS, 90 * DAY_IN_MILLIS);
+ }
+ @Override
+ public Config getUidTagConfig() {
+ return new Config(2 * HOUR_IN_MILLIS, 5 * DAY_IN_MILLIS, 15 * DAY_IN_MILLIS);
+ }
+ @Override
+ public long getDevPersistBytes(long def) {
+ return def;
+ }
+ @Override
+ public long getXtPersistBytes(long def) {
+ return def;
+ }
+ @Override
+ public long getUidPersistBytes(long def) {
+ return def;
+ }
+ @Override
+ public long getUidTagPersistBytes(long def) {
+ return def;
+ }
+ }
+
+ private static native long nativeGetTotalStat(int type);
+ private static native long nativeGetIfaceStat(String iface, int type);
+ private static native long nativeGetUidStat(int uid, int type);
+}
diff --git a/service-t/src/com/android/server/net/NetworkStatsSubscriptionsMonitor.java b/service-t/src/com/android/server/net/NetworkStatsSubscriptionsMonitor.java
new file mode 100644
index 0000000..65ccd20
--- /dev/null
+++ b/service-t/src/com/android/server/net/NetworkStatsSubscriptionsMonitor.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net;
+
+import static android.app.usage.NetworkStatsManager.NETWORK_TYPE_5G_NSA;
+import static android.app.usage.NetworkStatsManager.getCollapsedRatType;
+import static android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED;
+import static android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA;
+import static android.telephony.TelephonyManager.NETWORK_TYPE_LTE;
+
+import android.annotation.NonNull;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.os.Build;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyCallback;
+import android.telephony.TelephonyDisplayInfo;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
+
+/**
+ * Helper class that watches for events that are triggered per subscription.
+ */
+@TargetApi(Build.VERSION_CODES.TIRAMISU)
+public class NetworkStatsSubscriptionsMonitor extends
+ SubscriptionManager.OnSubscriptionsChangedListener {
+
+ /**
+ * Interface that this monitor uses to delegate event handling to NetworkStatsService.
+ */
+ public interface Delegate {
+ /**
+ * Notify that the collapsed RAT type has been changed for any subscription. The method
+ * will also be triggered for any existing sub when start and stop monitoring.
+ *
+ * @param subscriberId IMSI of the subscription.
+ * @param collapsedRatType collapsed RAT type.
+ * @see android.app.usage.NetworkStatsManager#getCollapsedRatType(int).
+ */
+ void onCollapsedRatTypeChanged(@NonNull String subscriberId, int collapsedRatType);
+ }
+ private final Delegate mDelegate;
+
+ /**
+ * Receivers that watches for {@link TelephonyDisplayInfo} changes for each subscription, to
+ * monitor the transitioning between Radio Access Technology(RAT) types for each sub.
+ */
+ @NonNull
+ private final CopyOnWriteArrayList<RatTypeListener> mRatListeners =
+ new CopyOnWriteArrayList<>();
+
+ @NonNull
+ private final SubscriptionManager mSubscriptionManager;
+ @NonNull
+ private final TelephonyManager mTeleManager;
+
+ @NonNull
+ private final Executor mExecutor;
+
+ NetworkStatsSubscriptionsMonitor(@NonNull Context context,
+ @NonNull Executor executor, @NonNull Delegate delegate) {
+ super();
+ mSubscriptionManager = (SubscriptionManager) context.getSystemService(
+ Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+ mTeleManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ mExecutor = executor;
+ mDelegate = delegate;
+ }
+
+ @Override
+ public void onSubscriptionsChanged() {
+ // Collect active subId list, hidden subId such as opportunistic subscriptions are
+ // also needed to track CBRS.
+ final List<Integer> newSubs = getActiveSubIdList(mSubscriptionManager);
+
+ // IMSI is needed for every newly added sub. Listener stores subscriberId into it to
+ // prevent binder call to telephony when querying RAT. Keep listener registration with empty
+ // IMSI is meaningless since the RAT type changed is ambiguous for multi-SIM if reported
+ // with empty IMSI. So filter the subs w/o a valid IMSI to prevent such registration.
+ final List<Pair<Integer, String>> filteredNewSubs = new ArrayList<>();
+ for (final int subId : newSubs) {
+ final String subscriberId =
+ mTeleManager.createForSubscriptionId(subId).getSubscriberId();
+ if (!TextUtils.isEmpty(subscriberId)) {
+ filteredNewSubs.add(new Pair(subId, subscriberId));
+ }
+ }
+
+ for (final Pair<Integer, String> sub : filteredNewSubs) {
+ // Fully match listener with subId and IMSI, since in some rare cases, IMSI might be
+ // suddenly change regardless of subId, such as switch IMSI feature in modem side.
+ // If that happens, register new listener with new IMSI and remove old one later.
+ if (CollectionUtils.any(mRatListeners, it -> it.equalsKey(sub.first, sub.second))) {
+ continue;
+ }
+
+ final RatTypeListener listener = new RatTypeListener(this, sub.first, sub.second);
+ mRatListeners.add(listener);
+
+ // Register listener to the telephony manager that associated with specific sub.
+ mTeleManager.createForSubscriptionId(sub.first)
+ .registerTelephonyCallback(mExecutor, listener);
+ Log.d(NetworkStatsService.TAG, "RAT type listener registered for sub " + sub.first);
+ }
+
+ for (final RatTypeListener listener : new ArrayList<>(mRatListeners)) {
+ // If there is no subId and IMSI matched the listener, removes it.
+ if (!CollectionUtils.any(filteredNewSubs,
+ it -> listener.equalsKey(it.first, it.second))) {
+ handleRemoveRatTypeListener(listener);
+ }
+ }
+ }
+
+ @NonNull
+ private List<Integer> getActiveSubIdList(@NonNull SubscriptionManager subscriptionManager) {
+ final ArrayList<Integer> ret = new ArrayList<>();
+ final int[] ids = subscriptionManager.getCompleteActiveSubscriptionIdList();
+ for (int id : ids) ret.add(id);
+ return ret;
+ }
+
+ /**
+ * Get a collapsed RatType for the given subscriberId.
+ *
+ * @param subscriberId the target subscriberId
+ * @return collapsed RatType for the given subscriberId
+ */
+ public int getRatTypeForSubscriberId(@NonNull String subscriberId) {
+ final int index = CollectionUtils.indexOf(mRatListeners,
+ it -> TextUtils.equals(subscriberId, it.mSubscriberId));
+ return index != -1 ? mRatListeners.get(index).mLastCollapsedRatType
+ : TelephonyManager.NETWORK_TYPE_UNKNOWN;
+ }
+
+ /**
+ * Start monitoring events that triggered per subscription.
+ */
+ public void start() {
+ mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor, this);
+ }
+
+ /**
+ * Unregister subscription changes and all listeners for each subscription.
+ */
+ public void stop() {
+ mSubscriptionManager.removeOnSubscriptionsChangedListener(this);
+
+ for (final RatTypeListener listener : new ArrayList<>(mRatListeners)) {
+ handleRemoveRatTypeListener(listener);
+ }
+ }
+
+ private void handleRemoveRatTypeListener(@NonNull RatTypeListener listener) {
+ mTeleManager.createForSubscriptionId(listener.mSubId)
+ .unregisterTelephonyCallback(listener);
+ Log.d(NetworkStatsService.TAG, "RAT type listener unregistered for sub " + listener.mSubId);
+ mRatListeners.remove(listener);
+
+ // Removal of subscriptions doesn't generate RAT changed event, fire it for every
+ // RatTypeListener.
+ mDelegate.onCollapsedRatTypeChanged(
+ listener.mSubscriberId, TelephonyManager.NETWORK_TYPE_UNKNOWN);
+ }
+
+ static class RatTypeListener extends TelephonyCallback
+ implements TelephonyCallback.DisplayInfoListener {
+ // Unique id for the subscription. See {@link SubscriptionInfo#getSubscriptionId}.
+ @NonNull
+ private final int mSubId;
+
+ // IMSI to identifying the corresponding network from {@link NetworkState}.
+ // See {@link TelephonyManager#getSubscriberId}.
+ @NonNull
+ private final String mSubscriberId;
+
+ private volatile int mLastCollapsedRatType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+ @NonNull
+ private final NetworkStatsSubscriptionsMonitor mMonitor;
+
+ RatTypeListener(@NonNull NetworkStatsSubscriptionsMonitor monitor, int subId,
+ @NonNull String subscriberId) {
+ mSubId = subId;
+ mSubscriberId = subscriberId;
+ mMonitor = monitor;
+ }
+
+ @Override
+ public void onDisplayInfoChanged(TelephonyDisplayInfo displayInfo) {
+ // In 5G SA (Stand Alone) mode, the primary cell itself will be 5G hence telephony
+ // would report RAT = 5G_NR.
+ // However, in 5G NSA (Non Stand Alone) mode, the primary cell is still LTE and
+ // network allocates a secondary 5G cell so telephony reports RAT = LTE along with
+ // NR state as connected. In such case, attributes the data usage to NR.
+ // See b/160727498.
+ final boolean is5GNsa = displayInfo.getNetworkType() == NETWORK_TYPE_LTE
+ && (displayInfo.getOverrideNetworkType() == OVERRIDE_NETWORK_TYPE_NR_NSA
+ || displayInfo.getOverrideNetworkType() == OVERRIDE_NETWORK_TYPE_NR_ADVANCED);
+
+ final int networkType =
+ (is5GNsa ? NETWORK_TYPE_5G_NSA : displayInfo.getNetworkType());
+ final int collapsedRatType = getCollapsedRatType(networkType);
+ if (collapsedRatType == mLastCollapsedRatType) return;
+
+ if (NetworkStatsService.LOGD) {
+ Log.d(NetworkStatsService.TAG, "subtype changed for sub(" + mSubId + "): "
+ + mLastCollapsedRatType + " -> " + collapsedRatType);
+ }
+ mLastCollapsedRatType = collapsedRatType;
+ mMonitor.mDelegate.onCollapsedRatTypeChanged(mSubscriberId, mLastCollapsedRatType);
+ }
+
+ @VisibleForTesting
+ public int getSubId() {
+ return mSubId;
+ }
+
+ boolean equalsKey(int subId, @NonNull String subscriberId) {
+ return mSubId == subId && TextUtils.equals(mSubscriberId, subscriberId);
+ }
+ }
+}
diff --git a/service-t/src/com/android/server/net/PersistentInt.java b/service-t/src/com/android/server/net/PersistentInt.java
new file mode 100644
index 0000000..c212b77
--- /dev/null
+++ b/service-t/src/com/android/server/net/PersistentInt.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.AtomicFile;
+import android.util.SystemConfigFileCommitEventLogger;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * A simple integer backed by an on-disk {@link AtomicFile}. Not thread-safe.
+ */
+public class PersistentInt {
+ private final String mPath;
+ private final AtomicFile mFile;
+
+ /**
+ * Constructs a new {@code PersistentInt}. The counter is set to 0 if the file does not exist.
+ * Before returning, the constructor checks that the file is readable and writable. This
+ * indicates that in the future {@link #get} and {@link #set} are likely to succeed,
+ * though other events (data corruption, other code deleting the file, etc.) may cause these
+ * calls to fail in the future.
+ *
+ * @param path the path of the file to use.
+ * @param logger the logger
+ * @throws IOException the counter could not be read or written
+ */
+ public PersistentInt(@NonNull String path, @Nullable SystemConfigFileCommitEventLogger logger)
+ throws IOException {
+ mPath = path;
+ mFile = new AtomicFile(new File(path), logger);
+ checkReadWrite();
+ }
+
+ private void checkReadWrite() throws IOException {
+ int value;
+ try {
+ value = get();
+ } catch (FileNotFoundException e) {
+ // Counter does not exist. Attempt to initialize to 0.
+ // Note that we cannot tell here if the file does not exist or if opening it failed,
+ // because in Java both of those throw FileNotFoundException.
+ value = 0;
+ }
+ set(value);
+ get();
+ // No exceptions? Good.
+ }
+
+ /**
+ * Gets the current value.
+ *
+ * @return the current value of the counter.
+ * @throws IOException if reading the value failed.
+ */
+ public int get() throws IOException {
+ try (FileInputStream fin = mFile.openRead();
+ DataInputStream din = new DataInputStream(fin)) {
+ return din.readInt();
+ }
+ }
+
+ /**
+ * Sets the current value.
+ * @param value the value to set
+ * @throws IOException if writing the value failed.
+ */
+ public void set(int value) throws IOException {
+ FileOutputStream fout = null;
+ try {
+ fout = mFile.startWrite();
+ DataOutputStream dout = new DataOutputStream(fout);
+ dout.writeInt(value);
+ mFile.finishWrite(fout);
+ } catch (IOException e) {
+ if (fout != null) {
+ mFile.failWrite(fout);
+ }
+ throw e;
+ }
+ }
+
+ public String getPath() {
+ return mPath;
+ }
+}
diff --git a/service-t/src/com/android/server/net/StatsMapKey.java b/service-t/src/com/android/server/net/StatsMapKey.java
new file mode 100644
index 0000000..ea8d836
--- /dev/null
+++ b/service-t/src/com/android/server/net/StatsMapKey.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+/**
+ * Key for both stats maps.
+ */
+public class StatsMapKey extends Struct {
+ @Field(order = 0, type = Type.U32)
+ public final long uid;
+
+ @Field(order = 1, type = Type.U32)
+ public final long tag;
+
+ @Field(order = 2, type = Type.U32)
+ public final long counterSet;
+
+ @Field(order = 3, type = Type.U32)
+ public final long ifaceIndex;
+
+ public StatsMapKey(final long uid, final long tag, final long counterSet,
+ final long ifaceIndex) {
+ this.uid = uid;
+ this.tag = tag;
+ this.counterSet = counterSet;
+ this.ifaceIndex = ifaceIndex;
+ }
+}
diff --git a/service-t/src/com/android/server/net/StatsMapValue.java b/service-t/src/com/android/server/net/StatsMapValue.java
new file mode 100644
index 0000000..48f26ce
--- /dev/null
+++ b/service-t/src/com/android/server/net/StatsMapValue.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+/**
+ * Value used for both stats maps and uid stats map.
+ */
+public class StatsMapValue extends Struct {
+ @Field(order = 0, type = Type.U63)
+ public final long rxPackets;
+
+ @Field(order = 1, type = Type.U63)
+ public final long rxBytes;
+
+ @Field(order = 2, type = Type.U63)
+ public final long txPackets;
+
+ @Field(order = 3, type = Type.U63)
+ public final long txBytes;
+
+ public StatsMapValue(final long rxPackets, final long rxBytes, final long txPackets,
+ final long txBytes) {
+ this.rxPackets = rxPackets;
+ this.rxBytes = rxBytes;
+ this.txPackets = txPackets;
+ this.txBytes = txBytes;
+ }
+}
diff --git a/service-t/src/com/android/server/net/UidStatsMapKey.java b/service-t/src/com/android/server/net/UidStatsMapKey.java
new file mode 100644
index 0000000..2849f94
--- /dev/null
+++ b/service-t/src/com/android/server/net/UidStatsMapKey.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+/**
+ * Key for uid stats map.
+ */
+public class UidStatsMapKey extends Struct {
+ @Field(order = 0, type = Type.U32)
+ public final long uid;
+
+ public UidStatsMapKey(final long uid) {
+ this.uid = uid;
+ }
+}
diff --git a/service/Android.bp b/service/Android.bp
index 0e6fe92..91b9d1c 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -19,6 +19,54 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
+aidl_interface {
+ name: "connectivity_native_aidl_interface",
+ local_include_dir: "binder",
+ vendor_available: true,
+ srcs: [
+ "binder/android/net/connectivity/aidl/*.aidl",
+ ],
+ backend: {
+ java: {
+ apex_available: [
+ "com.android.tethering",
+ ],
+ min_sdk_version: "30",
+ },
+ ndk: {
+ apex_available: [
+ "com.android.tethering",
+ ],
+ min_sdk_version: "30",
+ },
+ },
+ versions: ["1"],
+
+}
+
+cc_library_static {
+ name: "connectivity_native_aidl_interface-lateststable-ndk",
+ min_sdk_version: "30",
+ whole_static_libs: [
+ "connectivity_native_aidl_interface-V1-ndk",
+ ],
+ apex_available: [
+ "com.android.tethering",
+ ],
+}
+
+java_library {
+ name: "connectivity_native_aidl_interface-lateststable-java",
+ sdk_version: "system_current",
+ min_sdk_version: "30",
+ static_libs: [
+ "connectivity_native_aidl_interface-V1-java",
+ ],
+ apex_available: [
+ "com.android.tethering",
+ ],
+}
+
// The library name match the service-connectivity jarjar rules that put the JNI utils in the
// android.net.connectivity.com.android.net.module.util package.
cc_library_shared {
@@ -35,6 +83,7 @@
],
static_libs: [
"libnet_utils_device_common_bpfjni",
+ "libnet_utils_device_common_bpfutils",
],
shared_libs: [
"liblog",
@@ -109,6 +158,7 @@
static_libs: [
// Do not add libs here if they are already included
// in framework-connectivity
+ "connectivity_native_aidl_interface-lateststable-java",
"dnsresolver_aidl_interface-V9-java",
"modules-utils-shell-command-handler",
"net-utils-device-common",
@@ -119,7 +169,7 @@
"networkstack-client",
"PlatformProperties",
"service-connectivity-protos",
- "NetworkStackApiCurrentShims",
+ "NetworkStackApiStableShims",
],
apex_available: [
"com.android.tethering",
@@ -166,6 +216,11 @@
apex_available: [
"com.android.tethering",
],
+ optimize: {
+ enabled: true,
+ shrink: true,
+ proguard_flags_files: ["proguard.flags"],
+ },
lint: { strict_updatability_linting: true },
}
diff --git a/service/ServiceConnectivityResources/Android.bp b/service/ServiceConnectivityResources/Android.bp
index f491cc7..02b2875 100644
--- a/service/ServiceConnectivityResources/Android.bp
+++ b/service/ServiceConnectivityResources/Android.bp
@@ -23,6 +23,7 @@
name: "ServiceConnectivityResources",
sdk_version: "module_30",
min_sdk_version: "30",
+ target_sdk_version: "33",
resource_dirs: [
"res",
],
diff --git a/service/ServiceConnectivityResources/res/values-af/strings.xml b/service/ServiceConnectivityResources/res/values-af/strings.xml
index 550ab8a..086c6e3 100644
--- a/service/ServiceConnectivityResources/res/values-af/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-af/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Stelselkonnektiwiteithulpbronne"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Meld aan by Wi-Fi-netwerk"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Meld by netwerk aan"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Stelselkonnektiwiteithulpbronne"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Meld aan by Wi-Fi-netwerk"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Meld by netwerk aan"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> het geen internettoegang nie"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Tik vir opsies"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Selnetwerk het nie internettoegang nie"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Netwerk het nie internettoegang nie"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Daar kan nie by private DNS-bediener ingegaan word nie"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> het beperkte konnektiwiteit"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Tik om in elk geval te koppel"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Het oorgeskakel na <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"Toestel gebruik <xliff:g id="NEW_NETWORK">%1$s</xliff:g> wanneer <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> geen internettoegang het nie. Heffings kan geld."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Het oorgeskakel van <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> na <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> het geen internettoegang nie"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Tik vir opsies"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Selnetwerk het nie internettoegang nie"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Netwerk het nie internettoegang nie"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Daar kan nie by private DNS-bediener ingegaan word nie"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> het beperkte konnektiwiteit"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Tik om in elk geval te koppel"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Het oorgeskakel na <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"Toestel gebruik <xliff:g id="NEW_NETWORK">%1$s</xliff:g> wanneer <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> geen internettoegang het nie. Heffings kan geld."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Het oorgeskakel van <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> na <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"mobiele data"</item>
- <item msgid="6341719431034774569">"Wi-fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"mobiele data"</item>
+ <item msgid="5624324321165953608">"Wi-fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"\'n onbekende netwerktipe"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"\'n onbekende netwerktipe"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-am/strings.xml b/service/ServiceConnectivityResources/res/values-am/strings.xml
index 7f1a9db..886b353 100644
--- a/service/ServiceConnectivityResources/res/values-am/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-am/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"የስርዓት ግንኙነት መርጃዎች"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"ወደ Wi-Fi አውታረ መረብ በመለያ ግባ"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"ወደ አውታረ መረብ በመለያ ይግቡ"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"የስርዓት ግንኙነት መርጃዎች"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"ወደ Wi-Fi አውታረ መረብ በመለያ ግባ"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"ወደ አውታረ መረብ በመለያ ይግቡ"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ምንም የበይነ መረብ መዳረሻ የለም"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"ለአማራጮች መታ ያድርጉ"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"የተንቀሳቃሽ ስልክ አውታረ መረብ የበይነመረብ መዳረሻ የለውም"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"አውታረ መረብ የበይነመረብ መዳረሻ የለውም"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"የግል ዲኤንኤስ አገልጋይ ሊደረስበት አይችልም"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> የተገደበ ግንኙነት አለው"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"ለማንኛውም ለማገናኘት መታ ያድርጉ"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"ወደ <xliff:g id="NETWORK_TYPE">%1$s</xliff:g> ተቀይሯል"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> ምንም ዓይነት የበይነመረብ ግንኙነት በማይኖረው ጊዜ መሣሪያዎች <xliff:g id="NEW_NETWORK">%1$s</xliff:g>ን ይጠቀማሉ። ክፍያዎች ተፈጻሚ ሊሆኑ ይችላሉ።"</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"ከ<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> ወደ <xliff:g id="NEW_NETWORK">%2$s</xliff:g> ተቀይሯል"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ምንም የበይነ መረብ መዳረሻ የለም"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"ለአማራጮች መታ ያድርጉ"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"የተንቀሳቃሽ ስልክ አውታረ መረብ የበይነመረብ መዳረሻ የለውም"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"አውታረ መረብ የበይነመረብ መዳረሻ የለውም"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"የግል ዲኤንኤስ አገልጋይ ሊደረስበት አይችልም"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> የተገደበ ግንኙነት አለው"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"ለማንኛውም ለማገናኘት መታ ያድርጉ"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"ወደ <xliff:g id="NETWORK_TYPE">%1$s</xliff:g> ተቀይሯል"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> ምንም ዓይነት የበይነመረብ ግንኙነት በማይኖረው ጊዜ መሣሪያዎች <xliff:g id="NEW_NETWORK">%1$s</xliff:g>ን ይጠቀማሉ። ክፍያዎች ተፈጻሚ ሊሆኑ ይችላሉ።"</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"ከ<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> ወደ <xliff:g id="NEW_NETWORK">%2$s</xliff:g> ተቀይሯል"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"የተንቀሳቃሽ ስልክ ውሂብ"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"ብሉቱዝ"</item>
- <item msgid="1160736166977503463">"ኢተርኔት"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"የተንቀሳቃሽ ስልክ ውሂብ"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"ብሉቱዝ"</item>
+ <item msgid="346574747471703768">"ኢተርኔት"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"አንድ ያልታወቀ አውታረ መረብ ዓይነት"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"አንድ ያልታወቀ አውታረ መረብ ዓይነት"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-ar/strings.xml b/service/ServiceConnectivityResources/res/values-ar/strings.xml
index b7a62c5..07d9c2e 100644
--- a/service/ServiceConnectivityResources/res/values-ar/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-ar/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"مصادر إمكانية اتصال الخادم"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"تسجيل الدخول إلى شبكة Wi-Fi"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"تسجيل الدخول إلى الشبكة"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"مصادر إمكانية اتصال الخادم"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"تسجيل الدخول إلى شبكة Wi-Fi"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"تسجيل الدخول إلى الشبكة"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"لا يتوفّر في <xliff:g id="NETWORK_SSID">%1$s</xliff:g> إمكانية الاتصال بالإنترنت."</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"انقر للحصول على الخيارات."</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"شبكة الجوّال هذه غير متصلة بالإنترنت"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"الشبكة غير متصلة بالإنترنت"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"لا يمكن الوصول إلى خادم أسماء نظام نطاقات خاص"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"إمكانية اتصال <xliff:g id="NETWORK_SSID">%1$s</xliff:g> محدودة."</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"يمكنك النقر للاتصال على أي حال."</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"تم التبديل إلى <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"يستخدم الجهاز <xliff:g id="NEW_NETWORK">%1$s</xliff:g> عندما لا يتوفر اتصال بالإنترنت في شبكة <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g>، ويمكن أن يتم فرض رسوم مقابل ذلك."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"تم التبديل من <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> إلى <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"لا يتوفّر في <xliff:g id="NETWORK_SSID">%1$s</xliff:g> إمكانية الاتصال بالإنترنت."</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"انقر للحصول على الخيارات."</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"شبكة الجوّال هذه غير متصلة بالإنترنت"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"الشبكة غير متصلة بالإنترنت"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"لا يمكن الوصول إلى خادم أسماء نظام نطاقات خاص"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"إمكانية اتصال <xliff:g id="NETWORK_SSID">%1$s</xliff:g> محدودة."</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"يمكنك النقر للاتصال على أي حال."</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"تم التبديل إلى <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"يستخدم الجهاز <xliff:g id="NEW_NETWORK">%1$s</xliff:g> عندما لا يتوفر اتصال بالإنترنت في شبكة <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g>، ويمكن أن يتم فرض رسوم مقابل ذلك."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"تم التبديل من <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> إلى <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"بيانات الجوّال"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"بلوتوث"</item>
- <item msgid="1160736166977503463">"إيثرنت"</item>
- <item msgid="7347618872551558605">"شبكة افتراضية خاصة (VPN)"</item>
+ <item msgid="3004933964374161223">"بيانات الجوّال"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"بلوتوث"</item>
+ <item msgid="346574747471703768">"إيثرنت"</item>
+ <item msgid="5734728378097476003">"شبكة افتراضية خاصة (VPN)"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"نوع شبكة غير معروف"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"نوع شبكة غير معروف"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-as/strings.xml b/service/ServiceConnectivityResources/res/values-as/strings.xml
index cf8e6ac..e753cb3 100644
--- a/service/ServiceConnectivityResources/res/values-as/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-as/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"ছিষ্টেম সংযোগৰ উৎস"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"ৱাই-ফাই নেটৱৰ্কত ছাইন ইন কৰক"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"নেটৱৰ্কত ছাইন ইন কৰক"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"ছিষ্টেম সংযোগৰ উৎস"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"ৱাই-ফাই নেটৱৰ্কত ছাইন ইন কৰক"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"নেটৱৰ্কত ছাইন ইন কৰক"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>ৰ ইণ্টাৰনেটৰ এক্সেছ নাই"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"অধিক বিকল্পৰ বাবে টিপক"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"ম’বাইল নেটৱৰ্কৰ কোনো ইণ্টাৰনেটৰ এক্সেছ নাই"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"নেটৱৰ্কৰ কোনো ইণ্টাৰনেটৰ এক্সেছ নাই"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"ব্যক্তিগত DNS ছাৰ্ভাৰ এক্সেছ কৰিব নোৱাৰি"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>ৰ সকলো সেৱাৰ এক্সেছ নাই"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"যিকোনো প্ৰকাৰে সংযোগ কৰিবলৈ টিপক"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g>লৈ সলনি কৰা হ’ল"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"যেতিয়া <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g>ত ইণ্টাৰনেট নাথাকে, তেতিয়া ডিভাইচে <xliff:g id="NEW_NETWORK">%1$s</xliff:g>ক ব্যৱহাৰ কৰে। মাচুল প্ৰযোজ্য হ\'ব পাৰে।"</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g>ৰ পৰা <xliff:g id="NEW_NETWORK">%2$s</xliff:g> লৈ সলনি কৰা হ’ল"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>ৰ ইণ্টাৰনেটৰ এক্সেছ নাই"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"অধিক বিকল্পৰ বাবে টিপক"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"ম’বাইল নেটৱৰ্কৰ কোনো ইণ্টাৰনেটৰ এক্সেছ নাই"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"নেটৱৰ্কৰ কোনো ইণ্টাৰনেটৰ এক্সেছ নাই"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"ব্যক্তিগত DNS ছাৰ্ভাৰ এক্সেছ কৰিব নোৱাৰি"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>ৰ সকলো সেৱাৰ এক্সেছ নাই"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"যিকোনো প্ৰকাৰে সংযোগ কৰিবলৈ টিপক"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g>লৈ সলনি কৰা হ’ল"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"যেতিয়া <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g>ত ইণ্টাৰনেট নাথাকে, তেতিয়া ডিভাইচে <xliff:g id="NEW_NETWORK">%1$s</xliff:g>ক ব্যৱহাৰ কৰে। মাচুল প্ৰযোজ্য হ\'ব পাৰে।"</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g>ৰ পৰা <xliff:g id="NEW_NETWORK">%2$s</xliff:g> লৈ সলনি কৰা হ’ল"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"ম’বাইল ডেটা"</item>
- <item msgid="6341719431034774569">"ৱাই-ফাই"</item>
- <item msgid="5081440868800877512">"ব্লুটুথ"</item>
- <item msgid="1160736166977503463">"ইথাৰনেট"</item>
- <item msgid="7347618872551558605">"ভিপিএন"</item>
+ <item msgid="3004933964374161223">"ম’বাইল ডেটা"</item>
+ <item msgid="5624324321165953608">"ৱাই-ফাই"</item>
+ <item msgid="5667906231066981731">"ব্লুটুথ"</item>
+ <item msgid="346574747471703768">"ইথাৰনেট"</item>
+ <item msgid="5734728378097476003">"ভিপিএন"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"অজ্ঞাত প্ৰকাৰৰ নেটৱৰ্ক"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"অজ্ঞাত প্ৰকাৰৰ নেটৱৰ্ক"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-az/strings.xml b/service/ServiceConnectivityResources/res/values-az/strings.xml
index 7e927ed..f33a3e3 100644
--- a/service/ServiceConnectivityResources/res/values-az/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-az/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Sistem Bağlantı Resursları"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Wi-Fi şəbəkəsinə daxil ol"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Şəbəkəyə daxil olun"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Sistem Bağlantı Resursları"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Wi-Fi şəbəkəsinə daxil ol"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Şəbəkəyə daxil olun"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> üçün internet girişi əlçatan deyil"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Seçimlər üçün tıklayın"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Mobil şəbəkənin internetə girişi yoxdur"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Şəbəkənin internetə girişi yoxdur"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Özəl DNS serverinə giriş mümkün deyil"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> bağlantını məhdudlaşdırdı"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"İstənilən halda klikləyin"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g> şəbəkə növünə keçirildi"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> şəbəkəsinin internetə girişi olmadıqda, cihaz <xliff:g id="NEW_NETWORK">%1$s</xliff:g> şəbəkəsini istifadə edir. Xidmət haqqı tutula bilər."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> şəbəkəsindən <xliff:g id="NEW_NETWORK">%2$s</xliff:g> şəbəkəsinə keçirildi"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> üçün internet girişi əlçatan deyil"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Seçimlər üçün tıklayın"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Mobil şəbəkənin internetə girişi yoxdur"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Şəbəkənin internetə girişi yoxdur"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Özəl DNS serverinə giriş mümkün deyil"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> bağlantını məhdudlaşdırdı"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"İstənilən halda klikləyin"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g> şəbəkə növünə keçirildi"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> şəbəkəsinin internetə girişi olmadıqda, cihaz <xliff:g id="NEW_NETWORK">%1$s</xliff:g> şəbəkəsini istifadə edir. Xidmət haqqı tutula bilər."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> şəbəkəsindən <xliff:g id="NEW_NETWORK">%2$s</xliff:g> şəbəkəsinə keçirildi"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"mobil data"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"mobil data"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"naməlum şəbəkə növü"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"naməlum şəbəkə növü"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-b+sr+Latn/strings.xml b/service/ServiceConnectivityResources/res/values-b+sr+Latn/strings.xml
index 3f1b976..7398e7c 100644
--- a/service/ServiceConnectivityResources/res/values-b+sr+Latn/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-b+sr+Latn/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Resursi za povezivanje sa sistemom"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Prijavljivanje na WiFi mrežu"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Prijavite se na mrežu"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Resursi za povezivanje sa sistemom"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Prijavljivanje na WiFi mrežu"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Prijavite se na mrežu"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> nema pristup internetu"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Dodirnite za opcije"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Mobilna mreža nema pristup internetu"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Mreža nema pristup internetu"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Pristup privatnom DNS serveru nije uspeo"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ima ograničenu vezu"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Dodirnite da biste se ipak povezali"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Prešli ste na tip mreže <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"Uređaj koristi tip mreže <xliff:g id="NEW_NETWORK">%1$s</xliff:g> kada tip mreže <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> nema pristup internetu. Možda će se naplaćivati troškovi."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Prešli ste sa tipa mreže <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> na tip mreže <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> nema pristup internetu"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Dodirnite za opcije"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Mobilna mreža nema pristup internetu"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Mreža nema pristup internetu"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Pristup privatnom DNS serveru nije uspeo"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ima ograničenu vezu"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Dodirnite da biste se ipak povezali"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Prešli ste na tip mreže <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"Uređaj koristi tip mreže <xliff:g id="NEW_NETWORK">%1$s</xliff:g> kada tip mreže <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> nema pristup internetu. Možda će se naplaćivati troškovi."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Prešli ste sa tipa mreže <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> na tip mreže <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"mobilni podaci"</item>
- <item msgid="6341719431034774569">"WiFi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Eternet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"mobilni podaci"</item>
+ <item msgid="5624324321165953608">"WiFi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Eternet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"nepoznat tip mreže"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"nepoznat tip mreže"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-be/strings.xml b/service/ServiceConnectivityResources/res/values-be/strings.xml
index 21edf24..3459cc7 100644
--- a/service/ServiceConnectivityResources/res/values-be/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-be/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Рэсурсы для падключэння да сістэмы"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Уваход у сетку Wi-Fi"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Увайдзіце ў сетку"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Рэсурсы для падключэння да сістэмы"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Уваход у сетку Wi-Fi"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Увайдзіце ў сетку"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> не мае доступу ў інтэрнэт"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Дакраніцеся, каб убачыць параметры"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Мабільная сетка не мае доступу ў інтэрнэт"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Сетка не мае доступу ў інтэрнэт"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Не ўдалося атрымаць доступ да прыватнага DNS-сервера"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> мае абмежаваную магчымасць падключэння"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Націсніце, каб падключыцца"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Выкананы пераход да <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"Прылада выкарыстоўвае сетку <xliff:g id="NEW_NETWORK">%1$s</xliff:g>, калі ў сетцы <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> няма доступу да інтэрнэту. Можа спаганяцца плата."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Выкананы пераход з <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> да <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> не мае доступу ў інтэрнэт"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Дакраніцеся, каб убачыць параметры"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Мабільная сетка не мае доступу ў інтэрнэт"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Сетка не мае доступу ў інтэрнэт"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Не ўдалося атрымаць доступ да прыватнага DNS-сервера"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> мае абмежаваную магчымасць падключэння"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Націсніце, каб падключыцца"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Выкананы пераход да <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"Прылада выкарыстоўвае сетку <xliff:g id="NEW_NETWORK">%1$s</xliff:g>, калі ў сетцы <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> няма доступу да інтэрнэту. Можа спаганяцца плата."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Выкананы пераход з <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> да <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"мабільная перадача даных"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"мабільная перадача даных"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"невядомы тып сеткі"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"невядомы тып сеткі"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-bg/strings.xml b/service/ServiceConnectivityResources/res/values-bg/strings.xml
index c3c2d72..b4ae618 100644
--- a/service/ServiceConnectivityResources/res/values-bg/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-bg/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Ресурси за свързаността на системата"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Влизане в Wi-Fi мрежа"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Вход в мрежата"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Ресурси за свързаността на системата"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Влизане в Wi-Fi мрежа"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Вход в мрежата"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> няма достъп до интернет"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Докоснете за опции"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Мобилната мрежа няма достъп до интернет"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Мрежата няма достъп до интернет"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Не може да се осъществи достъп до частния DNS сървър"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> има ограничена свързаност"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Докоснете, за да се свържете въпреки това"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Превключи се към <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"Устройството използва <xliff:g id="NEW_NETWORK">%1$s</xliff:g>, когато <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> няма достъп до интернет. Възможно е да бъдете таксувани."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Превключи се от <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> към <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> няма достъп до интернет"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Докоснете за опции"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Мобилната мрежа няма достъп до интернет"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Мрежата няма достъп до интернет"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Не може да се осъществи достъп до частния DNS сървър"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> има ограничена свързаност"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Докоснете, за да се свържете въпреки това"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Превключи се към <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"Устройството използва <xliff:g id="NEW_NETWORK">%1$s</xliff:g>, когато <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> няма достъп до интернет. Възможно е да бъдете таксувани."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Превключи се от <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> към <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"мобилни данни"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"мобилни данни"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"неизвестен тип мрежа"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"неизвестен тип мрежа"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-bn/strings.xml b/service/ServiceConnectivityResources/res/values-bn/strings.xml
index 0f693bd..3b32973 100644
--- a/service/ServiceConnectivityResources/res/values-bn/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-bn/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"সিস্টেম কানেক্টিভিটি রিসোর্সেস"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"ওয়াই-ফাই নেটওয়ার্কে সাইন-ইন করুন"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"নেটওয়ার্কে সাইন-ইন করুন"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"সিস্টেম কানেক্টিভিটি রিসোর্সেস"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"ওয়াই-ফাই নেটওয়ার্কে সাইন-ইন করুন"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"নেটওয়ার্কে সাইন-ইন করুন"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>-এর ইন্টারনেটে অ্যাক্সেস নেই"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"বিকল্পগুলির জন্য আলতো চাপুন"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"মোবাইল নেটওয়ার্কে কোনও ইন্টারনেট অ্যাক্সেস নেই"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"নেটওয়ার্কে কোনও ইন্টারনেট অ্যাক্সেস নেই"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"ব্যক্তিগত ডিএনএস সার্ভার অ্যাক্সেস করা যাবে না"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>-এর সীমিত কানেক্টিভিটি আছে"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"তবুও কানেক্ট করতে ট্যাপ করুন"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g> এ পাল্টানো হয়েছে"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> এ ইন্টারনেট অ্যাক্সেস না থাকলে <xliff:g id="NEW_NETWORK">%1$s</xliff:g> ব্যবহার করা হয়৷ ডেটা চার্জ প্রযোজ্য৷"</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> থেকে <xliff:g id="NEW_NETWORK">%2$s</xliff:g> এ পাল্টানো হয়েছে"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>-এর ইন্টারনেটে অ্যাক্সেস নেই"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"বিকল্পগুলির জন্য আলতো চাপুন"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"মোবাইল নেটওয়ার্কে কোনও ইন্টারনেট অ্যাক্সেস নেই"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"নেটওয়ার্কে কোনও ইন্টারনেট অ্যাক্সেস নেই"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"ব্যক্তিগত ডিএনএস সার্ভার অ্যাক্সেস করা যাবে না"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>-এর সীমিত কানেক্টিভিটি আছে"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"তবুও কানেক্ট করতে ট্যাপ করুন"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g> এ পাল্টানো হয়েছে"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> এ ইন্টারনেট অ্যাক্সেস না থাকলে <xliff:g id="NEW_NETWORK">%1$s</xliff:g> ব্যবহার করা হয়৷ ডেটা চার্জ প্রযোজ্য৷"</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> থেকে <xliff:g id="NEW_NETWORK">%2$s</xliff:g> এ পাল্টানো হয়েছে"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"মোবাইল ডেটা"</item>
- <item msgid="6341719431034774569">"ওয়াই-ফাই"</item>
- <item msgid="5081440868800877512">"ব্লুটুথ"</item>
- <item msgid="1160736166977503463">"ইথারনেট"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"মোবাইল ডেটা"</item>
+ <item msgid="5624324321165953608">"ওয়াই-ফাই"</item>
+ <item msgid="5667906231066981731">"ব্লুটুথ"</item>
+ <item msgid="346574747471703768">"ইথারনেট"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"এই নেটওয়ার্কের ধরন অজানা"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"এই নেটওয়ার্কের ধরন অজানা"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-bs/strings.xml b/service/ServiceConnectivityResources/res/values-bs/strings.xml
index 33d6ed9..0bc0a7c 100644
--- a/service/ServiceConnectivityResources/res/values-bs/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-bs/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Izvori povezivosti sistema"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Prijavljivanje na WiFi mrežu"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Prijava na mrežu"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Izvori povezivosti sistema"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Prijavljivanje na WiFi mrežu"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Prijava na mrežu"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"Mreža <xliff:g id="NETWORK_SSID">%1$s</xliff:g> nema pristup internetu"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Dodirnite za opcije"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Mobilna mreža nema pristup internetu"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Mreža nema pristup internetu"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Nije moguće pristupiti privatnom DNS serveru"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"Mreža <xliff:g id="NETWORK_SSID">%1$s</xliff:g> ima ograničenu povezivost"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Dodirnite da se ipak povežete"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Prebačeno na: <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"Kada <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> nema pristup internetu, uređaj koristi mrežu <xliff:g id="NEW_NETWORK">%1$s</xliff:g>. Moguća je naplata usluge."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Prebačeno iz mreže <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> u <xliff:g id="NEW_NETWORK">%2$s</xliff:g> mrežu"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"Mreža <xliff:g id="NETWORK_SSID">%1$s</xliff:g> nema pristup internetu"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Dodirnite za opcije"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Mobilna mreža nema pristup internetu"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Mreža nema pristup internetu"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Nije moguće pristupiti privatnom DNS serveru"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"Mreža <xliff:g id="NETWORK_SSID">%1$s</xliff:g> ima ograničenu povezivost"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Dodirnite da se ipak povežete"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Prebačeno na: <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"Kada <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> nema pristup internetu, uređaj koristi mrežu <xliff:g id="NEW_NETWORK">%1$s</xliff:g>. Moguća je naplata usluge."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Prebačeno iz mreže <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> u <xliff:g id="NEW_NETWORK">%2$s</xliff:g> mrežu"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"prijenos podataka na mobilnoj mreži"</item>
- <item msgid="6341719431034774569">"WiFi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"prijenos podataka na mobilnoj mreži"</item>
+ <item msgid="5624324321165953608">"WiFi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"nepoznata vrsta mreže"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"nepoznata vrsta mreže"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-ca/strings.xml b/service/ServiceConnectivityResources/res/values-ca/strings.xml
index 04f6bd2..22b9dbd 100644
--- a/service/ServiceConnectivityResources/res/values-ca/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-ca/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Recursos de connectivitat del sistema"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Inicia la sessió a la xarxa Wi-Fi"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Inicia la sessió a la xarxa"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Recursos de connectivitat del sistema"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Inicia la sessió a la xarxa Wi-Fi"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Inicia la sessió a la xarxa"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> no té accés a Internet"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Toca per veure les opcions"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"La xarxa mòbil no té accés a Internet"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"La xarxa no té accés a Internet"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"No es pot accedir al servidor DNS privat"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> té una connectivitat limitada"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Toca per connectar igualment"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Actualment en ús: <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"El dispositiu utilitza <xliff:g id="NEW_NETWORK">%1$s</xliff:g> en cas que <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> no tingui accés a Internet. És possible que s\'hi apliquin càrrecs."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Abans es feia servir la xarxa <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g>; ara s\'utilitza <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> no té accés a Internet"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Toca per veure les opcions"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"La xarxa mòbil no té accés a Internet"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"La xarxa no té accés a Internet"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"No es pot accedir al servidor DNS privat"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> té una connectivitat limitada"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Toca per connectar igualment"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Actualment en ús: <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"El dispositiu utilitza <xliff:g id="NEW_NETWORK">%1$s</xliff:g> en cas que <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> no tingui accés a Internet. És possible que s\'hi apliquin càrrecs."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Abans es feia servir la xarxa <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g>; ara s\'utilitza <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"dades mòbils"</item>
- <item msgid="6341719431034774569">"Wi‑Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"dades mòbils"</item>
+ <item msgid="5624324321165953608">"Wi‑Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"un tipus de xarxa desconegut"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"un tipus de xarxa desconegut"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-cs/strings.xml b/service/ServiceConnectivityResources/res/values-cs/strings.xml
index 6309e78..ccf21ee 100644
--- a/service/ServiceConnectivityResources/res/values-cs/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-cs/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Zdroje pro připojení systému"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Přihlásit se k síti Wi-Fi"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Přihlásit se k síti"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Zdroje pro připojení systému"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Přihlásit se k síti Wi-Fi"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Přihlásit se k síti"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"Síť <xliff:g id="NETWORK_SSID">%1$s</xliff:g> nemá přístup k internetu"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Klepnutím zobrazíte možnosti"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Mobilní síť nemá přístup k internetu"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Síť nemá přístup k internetu"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Nelze získat přístup k soukromému serveru DNS"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"Síť <xliff:g id="NETWORK_SSID">%1$s</xliff:g> umožňuje jen omezené připojení"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Klepnutím se i přesto připojíte"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Přechod na síť <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"Když síť <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> nebude mít přístup k internetu, zařízení použije síť <xliff:g id="NEW_NETWORK">%1$s</xliff:g>. Mohou být účtovány poplatky."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Přechod ze sítě <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> na síť <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"Síť <xliff:g id="NETWORK_SSID">%1$s</xliff:g> nemá přístup k internetu"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Klepnutím zobrazíte možnosti"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Mobilní síť nemá přístup k internetu"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Síť nemá přístup k internetu"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Nelze získat přístup k soukromému serveru DNS"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"Síť <xliff:g id="NETWORK_SSID">%1$s</xliff:g> umožňuje jen omezené připojení"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Klepnutím se i přesto připojíte"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Přechod na síť <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"Když síť <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> nebude mít přístup k internetu, zařízení použije síť <xliff:g id="NEW_NETWORK">%1$s</xliff:g>. Mohou být účtovány poplatky."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Přechod ze sítě <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> na síť <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"mobilní data"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"mobilní data"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"neznámý typ sítě"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"neznámý typ sítě"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-da/strings.xml b/service/ServiceConnectivityResources/res/values-da/strings.xml
index 57c58af..a33143e 100644
--- a/service/ServiceConnectivityResources/res/values-da/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-da/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Systemets forbindelsesressourcer"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Log ind på Wi-Fi-netværk"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Log ind på netværk"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Systemets forbindelsesressourcer"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Log ind på Wi-Fi-netværk"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Log ind på netværk"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> har ingen internetforbindelse"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Tryk for at se valgmuligheder"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Mobilnetværket har ingen internetadgang"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Netværket har ingen internetadgang"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Der er ikke adgang til den private DNS-server"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> har begrænset forbindelse"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Tryk for at oprette forbindelse alligevel"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Der blev skiftet til <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"Enheden benytter <xliff:g id="NEW_NETWORK">%1$s</xliff:g>, når der ikke er internetadgang via <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g>. Der opkræves muligvis betaling."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Der blev skiftet fra <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> til <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> har ingen internetforbindelse"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Tryk for at se valgmuligheder"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Mobilnetværket har ingen internetadgang"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Netværket har ingen internetadgang"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Der er ikke adgang til den private DNS-server"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> har begrænset forbindelse"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Tryk for at oprette forbindelse alligevel"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Der blev skiftet til <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"Enheden benytter <xliff:g id="NEW_NETWORK">%1$s</xliff:g>, når der ikke er internetadgang via <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g>. Der opkræves muligvis betaling."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Der blev skiftet fra <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> til <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"mobildata"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"mobildata"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"en ukendt netværkstype"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"en ukendt netværkstype"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-de/strings.xml b/service/ServiceConnectivityResources/res/values-de/strings.xml
index d0c2551..96cc7d2 100644
--- a/service/ServiceConnectivityResources/res/values-de/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-de/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Systemverbindungsressourcen"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"In WLAN anmelden"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Im Netzwerk anmelden"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Systemverbindungsressourcen"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"In WLAN anmelden"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Im Netzwerk anmelden"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> hat keinen Internetzugriff"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Für Optionen tippen"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Mobiles Netzwerk hat keinen Internetzugriff"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Netzwerk hat keinen Internetzugriff"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Auf den privaten DNS-Server kann nicht zugegriffen werden"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"Schlechte Verbindung mit <xliff:g id="NETWORK_SSID">%1$s</xliff:g>"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Tippen, um die Verbindung trotzdem herzustellen"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Zu <xliff:g id="NETWORK_TYPE">%1$s</xliff:g> gewechselt"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"Auf dem Gerät werden <xliff:g id="NEW_NETWORK">%1$s</xliff:g> genutzt, wenn über <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> kein Internet verfügbar ist. Eventuell fallen Gebühren an."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Von \"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g>\" zu \"<xliff:g id="NEW_NETWORK">%2$s</xliff:g>\" gewechselt"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> hat keinen Internetzugriff"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Für Optionen tippen"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Mobiles Netzwerk hat keinen Internetzugriff"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Netzwerk hat keinen Internetzugriff"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Auf den privaten DNS-Server kann nicht zugegriffen werden"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"Schlechte Verbindung mit <xliff:g id="NETWORK_SSID">%1$s</xliff:g>"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Tippen, um die Verbindung trotzdem herzustellen"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Zu <xliff:g id="NETWORK_TYPE">%1$s</xliff:g> gewechselt"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"Auf dem Gerät werden <xliff:g id="NEW_NETWORK">%1$s</xliff:g> genutzt, wenn über <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> kein Internet verfügbar ist. Eventuell fallen Gebühren an."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Von \"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g>\" zu \"<xliff:g id="NEW_NETWORK">%2$s</xliff:g>\" gewechselt"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"Mobile Daten"</item>
- <item msgid="6341719431034774569">"WLAN"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"Mobile Daten"</item>
+ <item msgid="5624324321165953608">"WLAN"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"ein unbekannter Netzwerktyp"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"ein unbekannter Netzwerktyp"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-el/strings.xml b/service/ServiceConnectivityResources/res/values-el/strings.xml
index 1c2838d..b5f319d 100644
--- a/service/ServiceConnectivityResources/res/values-el/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-el/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Πόροι συνδεσιμότητας συστήματος"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Συνδεθείτε στο δίκτυο Wi-Fi"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Σύνδεση στο δίκτυο"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Πόροι συνδεσιμότητας συστήματος"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Συνδεθείτε στο δίκτυο Wi-Fi"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Σύνδεση στο δίκτυο"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"Η εφαρμογή <xliff:g id="NETWORK_SSID">%1$s</xliff:g> δεν έχει πρόσβαση στο διαδίκτυο"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Πατήστε για να δείτε τις επιλογές"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Το δίκτυο κινητής τηλεφωνίας δεν έχει πρόσβαση στο διαδίκτυο."</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Το δίκτυο δεν έχει πρόσβαση στο διαδίκτυο."</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Δεν είναι δυνατή η πρόσβαση στον ιδιωτικό διακομιστή DNS."</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"Το δίκτυο <xliff:g id="NETWORK_SSID">%1$s</xliff:g> έχει περιορισμένη συνδεσιμότητα"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Πατήστε για σύνδεση ούτως ή άλλως"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Μετάβαση σε δίκτυο <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"Η συσκευή χρησιμοποιεί το δίκτυο <xliff:g id="NEW_NETWORK">%1$s</xliff:g> όταν το δίκτυο <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> δεν έχει πρόσβαση στο διαδίκτυο. Μπορεί να ισχύουν χρεώσεις."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Μετάβαση από το δίκτυο <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> στο δίκτυο <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"Η εφαρμογή <xliff:g id="NETWORK_SSID">%1$s</xliff:g> δεν έχει πρόσβαση στο διαδίκτυο"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Πατήστε για να δείτε τις επιλογές"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Το δίκτυο κινητής τηλεφωνίας δεν έχει πρόσβαση στο διαδίκτυο."</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Το δίκτυο δεν έχει πρόσβαση στο διαδίκτυο."</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Δεν είναι δυνατή η πρόσβαση στον ιδιωτικό διακομιστή DNS."</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"Το δίκτυο <xliff:g id="NETWORK_SSID">%1$s</xliff:g> έχει περιορισμένη συνδεσιμότητα"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Πατήστε για σύνδεση ούτως ή άλλως"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Μετάβαση σε δίκτυο <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"Η συσκευή χρησιμοποιεί το δίκτυο <xliff:g id="NEW_NETWORK">%1$s</xliff:g> όταν το δίκτυο <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> δεν έχει πρόσβαση στο διαδίκτυο. Μπορεί να ισχύουν χρεώσεις."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Μετάβαση από το δίκτυο <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> στο δίκτυο <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"δεδομένα κινητής τηλεφωνίας"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"δεδομένα κινητής τηλεφωνίας"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"άγνωστος τύπος δικτύου"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"άγνωστος τύπος δικτύου"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-en-rAU/strings.xml b/service/ServiceConnectivityResources/res/values-en-rAU/strings.xml
index db5ad70..c490cf8 100644
--- a/service/ServiceConnectivityResources/res/values-en-rAU/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-en-rAU/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"System connectivity resources"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Sign in to a Wi-Fi network"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Sign in to network"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"System connectivity resources"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Sign in to a Wi-Fi network"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Sign in to network"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> has no Internet access"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Tap for options"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Mobile network has no Internet access"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Network has no Internet access"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Private DNS server cannot be accessed"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> has limited connectivity"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Tap to connect anyway"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Switched to <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"Device uses <xliff:g id="NEW_NETWORK">%1$s</xliff:g> when <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> has no Internet access. Charges may apply."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Switched from <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> to <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> has no Internet access"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Tap for options"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Mobile network has no Internet access"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Network has no Internet access"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Private DNS server cannot be accessed"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> has limited connectivity"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Tap to connect anyway"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Switched to <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"Device uses <xliff:g id="NEW_NETWORK">%1$s</xliff:g> when <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> has no Internet access. Charges may apply."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Switched from <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> to <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"mobile data"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"mobile data"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"an unknown network type"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"an unknown network type"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-en-rCA/strings.xml b/service/ServiceConnectivityResources/res/values-en-rCA/strings.xml
index db5ad70..c490cf8 100644
--- a/service/ServiceConnectivityResources/res/values-en-rCA/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-en-rCA/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"System connectivity resources"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Sign in to a Wi-Fi network"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Sign in to network"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"System connectivity resources"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Sign in to a Wi-Fi network"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Sign in to network"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> has no Internet access"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Tap for options"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Mobile network has no Internet access"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Network has no Internet access"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Private DNS server cannot be accessed"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> has limited connectivity"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Tap to connect anyway"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Switched to <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"Device uses <xliff:g id="NEW_NETWORK">%1$s</xliff:g> when <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> has no Internet access. Charges may apply."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Switched from <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> to <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> has no Internet access"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Tap for options"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Mobile network has no Internet access"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Network has no Internet access"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Private DNS server cannot be accessed"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> has limited connectivity"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Tap to connect anyway"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Switched to <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"Device uses <xliff:g id="NEW_NETWORK">%1$s</xliff:g> when <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> has no Internet access. Charges may apply."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Switched from <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> to <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"mobile data"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"mobile data"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"an unknown network type"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"an unknown network type"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-en-rGB/strings.xml b/service/ServiceConnectivityResources/res/values-en-rGB/strings.xml
index db5ad70..c490cf8 100644
--- a/service/ServiceConnectivityResources/res/values-en-rGB/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-en-rGB/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"System connectivity resources"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Sign in to a Wi-Fi network"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Sign in to network"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"System connectivity resources"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Sign in to a Wi-Fi network"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Sign in to network"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> has no Internet access"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Tap for options"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Mobile network has no Internet access"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Network has no Internet access"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Private DNS server cannot be accessed"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> has limited connectivity"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Tap to connect anyway"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Switched to <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"Device uses <xliff:g id="NEW_NETWORK">%1$s</xliff:g> when <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> has no Internet access. Charges may apply."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Switched from <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> to <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> has no Internet access"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Tap for options"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Mobile network has no Internet access"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Network has no Internet access"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Private DNS server cannot be accessed"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> has limited connectivity"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Tap to connect anyway"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Switched to <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"Device uses <xliff:g id="NEW_NETWORK">%1$s</xliff:g> when <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> has no Internet access. Charges may apply."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Switched from <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> to <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"mobile data"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"mobile data"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"an unknown network type"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"an unknown network type"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-en-rIN/strings.xml b/service/ServiceConnectivityResources/res/values-en-rIN/strings.xml
index db5ad70..c490cf8 100644
--- a/service/ServiceConnectivityResources/res/values-en-rIN/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-en-rIN/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"System connectivity resources"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Sign in to a Wi-Fi network"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Sign in to network"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"System connectivity resources"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Sign in to a Wi-Fi network"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Sign in to network"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> has no Internet access"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Tap for options"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Mobile network has no Internet access"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Network has no Internet access"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Private DNS server cannot be accessed"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> has limited connectivity"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Tap to connect anyway"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Switched to <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"Device uses <xliff:g id="NEW_NETWORK">%1$s</xliff:g> when <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> has no Internet access. Charges may apply."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Switched from <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> to <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> has no Internet access"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Tap for options"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Mobile network has no Internet access"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Network has no Internet access"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Private DNS server cannot be accessed"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> has limited connectivity"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Tap to connect anyway"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Switched to <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"Device uses <xliff:g id="NEW_NETWORK">%1$s</xliff:g> when <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> has no Internet access. Charges may apply."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Switched from <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> to <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"mobile data"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"mobile data"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"an unknown network type"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"an unknown network type"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-en-rXC/strings.xml b/service/ServiceConnectivityResources/res/values-en-rXC/strings.xml
index 2602bfa..67c3659 100644
--- a/service/ServiceConnectivityResources/res/values-en-rXC/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-en-rXC/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"System Connectivity Resources"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Sign in to Wi-Fi network"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Sign in to network"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"System Connectivity Resources"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Sign in to Wi-Fi network"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Sign in to network"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> has no internet access"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Tap for options"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Mobile network has no internet access"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Network has no internet access"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Private DNS server cannot be accessed"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> has limited connectivity"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Tap to connect anyway"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Switched to <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"Device uses <xliff:g id="NEW_NETWORK">%1$s</xliff:g> when <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> has no internet access. Charges may apply."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Switched from <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> to <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> has no internet access"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Tap for options"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Mobile network has no internet access"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Network has no internet access"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Private DNS server cannot be accessed"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> has limited connectivity"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Tap to connect anyway"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Switched to <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"Device uses <xliff:g id="NEW_NETWORK">%1$s</xliff:g> when <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> has no internet access. Charges may apply."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Switched from <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> to <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"mobile data"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"mobile data"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"an unknown network type"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"an unknown network type"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-es-rUS/strings.xml b/service/ServiceConnectivityResources/res/values-es-rUS/strings.xml
index e5f1833..fdca468 100644
--- a/service/ServiceConnectivityResources/res/values-es-rUS/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-es-rUS/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Recursos de conectividad del sistema"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Accede a una red Wi-Fi."</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Acceder a la red"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Recursos de conectividad del sistema"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Accede a una red Wi-Fi."</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Acceder a la red"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>no tiene acceso a Internet"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Presiona para ver opciones"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"La red móvil no tiene acceso a Internet"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"La red no tiene acceso a Internet"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"No se puede acceder al servidor DNS privado"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> tiene conectividad limitada"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Presiona para conectarte de todas formas"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Se cambió a <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"El dispositivo usa <xliff:g id="NEW_NETWORK">%1$s</xliff:g> cuando <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> no tiene acceso a Internet. Es posible que se apliquen cargos."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Se cambió de <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> a <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>no tiene acceso a Internet"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Presiona para ver opciones"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"La red móvil no tiene acceso a Internet"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"La red no tiene acceso a Internet"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"No se puede acceder al servidor DNS privado"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> tiene conectividad limitada"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Presiona para conectarte de todas formas"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Se cambió a <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"El dispositivo usa <xliff:g id="NEW_NETWORK">%1$s</xliff:g> cuando <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> no tiene acceso a Internet. Es posible que se apliquen cargos."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Se cambió de <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> a <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"Datos móviles"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"Datos móviles"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"un tipo de red desconocido"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"un tipo de red desconocido"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-es/strings.xml b/service/ServiceConnectivityResources/res/values-es/strings.xml
index e4f4307..f4a7e3d 100644
--- a/service/ServiceConnectivityResources/res/values-es/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-es/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Recursos de conectividad del sistema"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Iniciar sesión en red Wi-Fi"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Iniciar sesión en la red"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Recursos de conectividad del sistema"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Iniciar sesión en red Wi-Fi"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Iniciar sesión en la red"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> no tiene acceso a Internet"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Toca para ver opciones"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"La red móvil no tiene acceso a Internet"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"La red no tiene acceso a Internet"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"No se ha podido acceder al servidor DNS privado"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> tiene una conectividad limitada"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Toca para conectarte de todas formas"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Se ha cambiado a <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"El dispositivo utiliza <xliff:g id="NEW_NETWORK">%1$s</xliff:g> cuando <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> no tiene acceso a Internet. Es posible que se apliquen cargos."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Se ha cambiado de <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> a <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> no tiene acceso a Internet"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Toca para ver opciones"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"La red móvil no tiene acceso a Internet"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"La red no tiene acceso a Internet"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"No se ha podido acceder al servidor DNS privado"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> tiene una conectividad limitada"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Toca para conectarte de todas formas"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Se ha cambiado a <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"El dispositivo utiliza <xliff:g id="NEW_NETWORK">%1$s</xliff:g> cuando <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> no tiene acceso a Internet. Es posible que se apliquen cargos."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Se ha cambiado de <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> a <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"datos móviles"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"datos móviles"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"un tipo de red desconocido"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"un tipo de red desconocido"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-et/strings.xml b/service/ServiceConnectivityResources/res/values-et/strings.xml
index cec408f..cf997b3 100644
--- a/service/ServiceConnectivityResources/res/values-et/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-et/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Süsteemi ühenduvuse allikad"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Logi sisse WiFi-võrku"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Võrku sisselogimine"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Süsteemi ühenduvuse allikad"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Logi sisse WiFi-võrku"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Võrku sisselogimine"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"Võrgul <xliff:g id="NETWORK_SSID">%1$s</xliff:g> puudub Interneti-ühendus"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Puudutage valikute nägemiseks"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Mobiilsidevõrgul puudub Interneti-ühendus"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Võrgul puudub Interneti-ühendus"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Privaatsele DNS-serverile ei pääse juurde"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"Võrgu <xliff:g id="NETWORK_SSID">%1$s</xliff:g> ühendus on piiratud"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Puudutage, kui soovite siiski ühenduse luua"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Lülitati võrgule <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"Seade kasutab võrku <xliff:g id="NEW_NETWORK">%1$s</xliff:g>, kui võrgul <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> puudub juurdepääs Internetile. Rakenduda võivad tasud."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Lülitati võrgult <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> võrgule <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"Võrgul <xliff:g id="NETWORK_SSID">%1$s</xliff:g> puudub Interneti-ühendus"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Puudutage valikute nägemiseks"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Mobiilsidevõrgul puudub Interneti-ühendus"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Võrgul puudub Interneti-ühendus"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Privaatsele DNS-serverile ei pääse juurde"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"Võrgu <xliff:g id="NETWORK_SSID">%1$s</xliff:g> ühendus on piiratud"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Puudutage, kui soovite siiski ühenduse luua"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Lülitati võrgule <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"Seade kasutab võrku <xliff:g id="NEW_NETWORK">%1$s</xliff:g>, kui võrgul <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> puudub juurdepääs Internetile. Rakenduda võivad tasud."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Lülitati võrgult <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> võrgule <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"mobiilne andmeside"</item>
- <item msgid="6341719431034774569">"WiFi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"mobiilne andmeside"</item>
+ <item msgid="5624324321165953608">"WiFi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"tundmatu võrgutüüp"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"tundmatu võrgutüüp"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-eu/strings.xml b/service/ServiceConnectivityResources/res/values-eu/strings.xml
index f3ee9b1..2c4e431 100644
--- a/service/ServiceConnectivityResources/res/values-eu/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-eu/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Sistemaren konexio-baliabideak"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Hasi saioa Wi-Fi sarean"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Hasi saioa sarean"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Sistemaren konexio-baliabideak"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Hasi saioa Wi-Fi sarean"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Hasi saioa sarean"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"Ezin da konektatu Internetera <xliff:g id="NETWORK_SSID">%1$s</xliff:g> sarearen bidez"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Sakatu aukerak ikusteko"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Sare mugikorra ezin da konektatu Internetera"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Sarea ezin da konektatu Internetera"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Ezin da atzitu DNS zerbitzari pribatua"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> sareak konektagarritasun murriztua du"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Sakatu hala ere konektatzeko"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g> erabiltzen ari zara orain"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> Internetera konektatzeko gauza ez denean, <xliff:g id="NEW_NETWORK">%1$s</xliff:g> erabiltzen du gailuak. Agian kostuak ordaindu beharko dituzu."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> erabiltzen ari zinen, baina <xliff:g id="NEW_NETWORK">%2$s</xliff:g> erabiltzen ari zara orain"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"Ezin da konektatu Internetera <xliff:g id="NETWORK_SSID">%1$s</xliff:g> sarearen bidez"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Sakatu aukerak ikusteko"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Sare mugikorra ezin da konektatu Internetera"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Sarea ezin da konektatu Internetera"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Ezin da atzitu DNS zerbitzari pribatua"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> sareak konektagarritasun murriztua du"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Sakatu hala ere konektatzeko"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g> erabiltzen ari zara orain"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> Internetera konektatzeko gauza ez denean, <xliff:g id="NEW_NETWORK">%1$s</xliff:g> erabiltzen du gailuak. Baliteke zerbait ordaindu behar izatea."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> erabiltzen ari zinen, baina <xliff:g id="NEW_NETWORK">%2$s</xliff:g> erabiltzen ari zara orain"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"datu-konexioa"</item>
- <item msgid="6341719431034774569">"Wifia"</item>
- <item msgid="5081440868800877512">"Bluetooth-a"</item>
- <item msgid="1160736166977503463">"Ethernet-a"</item>
- <item msgid="7347618872551558605">"VPNa"</item>
+ <item msgid="3004933964374161223">"datu-konexioa"</item>
+ <item msgid="5624324321165953608">"Wifia"</item>
+ <item msgid="5667906231066981731">"Bluetooth-a"</item>
+ <item msgid="346574747471703768">"Ethernet-a"</item>
+ <item msgid="5734728378097476003">"VPNa"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"sare mota ezezaguna"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"sare mota ezezaguna"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-fa/strings.xml b/service/ServiceConnectivityResources/res/values-fa/strings.xml
index 0c5b147..296ce8e 100644
--- a/service/ServiceConnectivityResources/res/values-fa/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-fa/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"منابع اتصال سیستم"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"ورود به شبکه Wi-Fi"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"ورود به سیستم شبکه"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"منابع اتصال سیستم"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"ورود به شبکه Wi-Fi"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"ورود به سیستم شبکه"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> به اینترنت دسترسی ندارد"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"برای گزینهها ضربه بزنید"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"شبکه تلفن همراه به اینترنت دسترسی ندارد"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"شبکه به اینترنت دسترسی ندارد"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"سرور DNS خصوصی قابل دسترسی نیست"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> اتصال محدودی دارد"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"بههرصورت، برای اتصال ضربه بزنید"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"به <xliff:g id="NETWORK_TYPE">%1$s</xliff:g> تغییر کرد"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"وقتی <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> به اینترنت دسترسی نداشته باشد، دستگاه از <xliff:g id="NEW_NETWORK">%1$s</xliff:g> استفاده میکند. ممکن است هزینههایی اعمال شود."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"از <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> به <xliff:g id="NEW_NETWORK">%2$s</xliff:g> تغییر کرد"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> به اینترنت دسترسی ندارد"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"برای گزینهها ضربه بزنید"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"شبکه تلفن همراه به اینترنت دسترسی ندارد"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"شبکه به اینترنت دسترسی ندارد"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"سرور DNS خصوصی قابل دسترسی نیست"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> اتصال محدودی دارد"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"بههرصورت، برای اتصال ضربه بزنید"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"به <xliff:g id="NETWORK_TYPE">%1$s</xliff:g> تغییر کرد"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"وقتی <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> به اینترنت دسترسی نداشته باشد، دستگاه از <xliff:g id="NEW_NETWORK">%1$s</xliff:g> استفاده میکند. ممکن است هزینههایی اعمال شود."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"از <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> به <xliff:g id="NEW_NETWORK">%2$s</xliff:g> تغییر کرد"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"داده تلفن همراه"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"بلوتوث"</item>
- <item msgid="1160736166977503463">"اترنت"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"داده تلفن همراه"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"بلوتوث"</item>
+ <item msgid="346574747471703768">"اترنت"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"نوع شبکه نامشخص"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"نوع شبکه نامشخص"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-fi/strings.xml b/service/ServiceConnectivityResources/res/values-fi/strings.xml
index 84c0034..07d2907 100644
--- a/service/ServiceConnectivityResources/res/values-fi/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-fi/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Järjestelmän yhteysresurssit"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Kirjaudu Wi-Fi-verkkoon"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Kirjaudu verkkoon"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Järjestelmän yhteysresurssit"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Kirjaudu Wi-Fi-verkkoon"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Kirjaudu verkkoon"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ei ole yhteydessä internetiin"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Näytä vaihtoehdot napauttamalla."</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Mobiiliverkko ei ole yhteydessä internetiin"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Verkko ei ole yhteydessä internetiin"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Ei pääsyä yksityiselle DNS-palvelimelle"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> toimii rajoitetulla yhteydellä"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Yhdistä napauttamalla"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g> otettiin käyttöön"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"<xliff:g id="NEW_NETWORK">%1$s</xliff:g> otetaan käyttöön, kun <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> ei voi muodostaa yhteyttä internetiin. Veloitukset ovat mahdollisia."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> poistettiin käytöstä ja <xliff:g id="NEW_NETWORK">%2$s</xliff:g> otettiin käyttöön."</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ei ole yhteydessä internetiin"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Näytä vaihtoehdot napauttamalla."</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Mobiiliverkko ei ole yhteydessä internetiin"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Verkko ei ole yhteydessä internetiin"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Ei pääsyä yksityiselle DNS-palvelimelle"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> toimii rajoitetulla yhteydellä"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Yhdistä napauttamalla"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g> otettiin käyttöön"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"<xliff:g id="NEW_NETWORK">%1$s</xliff:g> otetaan käyttöön, kun <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> ei voi muodostaa yhteyttä internetiin. Veloitukset ovat mahdollisia."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> poistettiin käytöstä ja <xliff:g id="NEW_NETWORK">%2$s</xliff:g> otettiin käyttöön."</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"mobiilidata"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"mobiilidata"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"tuntematon verkon tyyppi"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"tuntematon verkon tyyppi"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-fr-rCA/strings.xml b/service/ServiceConnectivityResources/res/values-fr-rCA/strings.xml
index 0badf1b..7d5b366 100644
--- a/service/ServiceConnectivityResources/res/values-fr-rCA/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-fr-rCA/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Ressources de connectivité système"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Connectez-vous au réseau Wi-Fi"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Connectez-vous au réseau"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Ressources de connectivité système"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Connectez-vous au réseau Wi-Fi"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Connectez-vous au réseau"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"Le réseau <xliff:g id="NETWORK_SSID">%1$s</xliff:g> n\'offre aucun accès à Internet"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Touchez pour afficher les options"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Le réseau cellulaire n\'offre aucun accès à Internet"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Le réseau n\'offre aucun accès à Internet"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Impossible d\'accéder au serveur DNS privé"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"Le réseau <xliff:g id="NETWORK_SSID">%1$s</xliff:g> offre une connectivité limitée"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Touchez pour vous connecter quand même"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Passé au réseau <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"L\'appareil utilise <xliff:g id="NEW_NETWORK">%1$s</xliff:g> quand <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> n\'a pas d\'accès à Internet. Des frais peuvent s\'appliquer."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Passé du réseau <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> au <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"Le réseau <xliff:g id="NETWORK_SSID">%1$s</xliff:g> n\'offre aucun accès à Internet"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Touchez pour afficher les options"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Le réseau cellulaire n\'offre aucun accès à Internet"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Le réseau n\'offre aucun accès à Internet"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Impossible d\'accéder au serveur DNS privé"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"Le réseau <xliff:g id="NETWORK_SSID">%1$s</xliff:g> offre une connectivité limitée"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Touchez pour vous connecter quand même"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Passé au réseau <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"L\'appareil utilise <xliff:g id="NEW_NETWORK">%1$s</xliff:g> quand <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> n\'a pas d\'accès à Internet. Des frais peuvent s\'appliquer."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Passé du réseau <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> au <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"données cellulaires"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"RPV"</item>
+ <item msgid="3004933964374161223">"données cellulaires"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"RPV"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"un type de réseau inconnu"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"un type de réseau inconnu"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-fr/strings.xml b/service/ServiceConnectivityResources/res/values-fr/strings.xml
index b483525..2331d9b 100644
--- a/service/ServiceConnectivityResources/res/values-fr/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-fr/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Ressources de connectivité système"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Connectez-vous au réseau Wi-Fi"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Se connecter au réseau"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Ressources de connectivité système"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Connectez-vous au réseau Wi-Fi"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Se connecter au réseau"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"Aucune connexion à Internet pour <xliff:g id="NETWORK_SSID">%1$s</xliff:g>"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Appuyez ici pour afficher des options."</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Le réseau mobile ne dispose d\'aucun accès à Internet"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Le réseau ne dispose d\'aucun accès à Internet"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Impossible d\'accéder au serveur DNS privé"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"La connectivité de <xliff:g id="NETWORK_SSID">%1$s</xliff:g> est limitée"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Appuyer pour se connecter quand même"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Nouveau réseau : <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"L\'appareil utilise <xliff:g id="NEW_NETWORK">%1$s</xliff:g> lorsque <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> n\'a pas de connexion Internet. Des frais peuvent s\'appliquer."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Ancien réseau : <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g>. Nouveau réseau : <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"Aucune connexion à Internet pour <xliff:g id="NETWORK_SSID">%1$s</xliff:g>"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Appuyez ici pour afficher des options."</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Le réseau mobile ne dispose d\'aucun accès à Internet"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Le réseau ne dispose d\'aucun accès à Internet"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Impossible d\'accéder au serveur DNS privé"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"La connectivité de <xliff:g id="NETWORK_SSID">%1$s</xliff:g> est limitée"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Appuyer pour se connecter quand même"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Nouveau réseau : <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"L\'appareil utilise <xliff:g id="NEW_NETWORK">%1$s</xliff:g> lorsque <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> n\'a pas de connexion Internet. Des frais peuvent s\'appliquer."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Ancien réseau : <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g>. Nouveau réseau : <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"données mobiles"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"données mobiles"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"type de réseau inconnu"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"type de réseau inconnu"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-gl/strings.xml b/service/ServiceConnectivityResources/res/values-gl/strings.xml
index dfe8137..f46f84b 100644
--- a/service/ServiceConnectivityResources/res/values-gl/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-gl/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Recursos de conectividade do sistema"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Inicia sesión na rede wifi"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Inicia sesión na rede"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Recursos de conectividade do sistema"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Inicia sesión na rede wifi"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Inicia sesión na rede"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> non ten acceso a Internet"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Toca para ver opcións."</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"A rede de telefonía móbil non ten acceso a Internet"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"A rede non ten acceso a Internet"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Non se puido acceder ao servidor DNS privado"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"A conectividade de <xliff:g id="NETWORK_SSID">%1$s</xliff:g> é limitada"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Toca para conectarte de todas formas"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Cambiouse a: <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"O dispositivo utiliza <xliff:g id="NEW_NETWORK">%1$s</xliff:g> cando <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> non ten acceso a Internet. Pódense aplicar cargos."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Cambiouse de <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> a <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> non ten acceso a Internet"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Toca para ver opcións."</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"A rede de telefonía móbil non ten acceso a Internet"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"A rede non ten acceso a Internet"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Non se puido acceder ao servidor DNS privado"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"A conectividade de <xliff:g id="NETWORK_SSID">%1$s</xliff:g> é limitada"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Toca para conectarte de todas formas"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Cambiouse a: <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"O dispositivo utiliza <xliff:g id="NEW_NETWORK">%1$s</xliff:g> cando <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> non ten acceso a Internet. Pódense aplicar cargos."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Cambiouse de <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> a <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"datos móbiles"</item>
- <item msgid="6341719431034774569">"wifi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"datos móbiles"</item>
+ <item msgid="5624324321165953608">"wifi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"un tipo de rede descoñecido"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"un tipo de rede descoñecido"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-gu/strings.xml b/service/ServiceConnectivityResources/res/values-gu/strings.xml
index e49b11d..ec9ecd3 100644
--- a/service/ServiceConnectivityResources/res/values-gu/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-gu/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"સિસ્ટમની કનેક્ટિવિટીનાં સાધનો"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"વાઇ-ફાઇ નેટવર્ક પર સાઇન ઇન કરો"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"નેટવર્ક પર સાઇન ઇન કરો"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"સિસ્ટમની કનેક્ટિવિટીનાં સાધનો"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"વાઇ-ફાઇ નેટવર્ક પર સાઇન ઇન કરો"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"નેટવર્ક પર સાઇન ઇન કરો"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ઇન્ટરનેટ ઍક્સેસ ધરાવતું નથી"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"વિકલ્પો માટે ટૅપ કરો"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"મોબાઇલ નેટવર્ક કોઈ ઇન્ટરનેટ ઍક્સેસ ધરાવતું નથી"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"નેટવર્ક કોઈ ઇન્ટરનેટ ઍક્સેસ ધરાવતું નથી"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"ખાનગી DNS સર્વર ઍક્સેસ કરી શકાતા નથી"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> મર્યાદિત કનેક્ટિવિટી ધરાવે છે"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"છતાં કનેક્ટ કરવા માટે ટૅપ કરો"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g> પર સ્વિચ કર્યું"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"જ્યારે <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> પાસે કોઈ ઇન્ટરનેટ ઍક્સેસ ન હોય ત્યારે ઉપકરણ <xliff:g id="NEW_NETWORK">%1$s</xliff:g>નો ઉપયોગ કરે છે. શુલ્ક લાગુ થઈ શકે છે."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> પરથી <xliff:g id="NEW_NETWORK">%2$s</xliff:g> પર સ્વિચ કર્યું"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ઇન્ટરનેટ ઍક્સેસ ધરાવતું નથી"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"વિકલ્પો માટે ટૅપ કરો"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"મોબાઇલ નેટવર્ક કોઈ ઇન્ટરનેટ ઍક્સેસ ધરાવતું નથી"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"નેટવર્ક કોઈ ઇન્ટરનેટ ઍક્સેસ ધરાવતું નથી"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"ખાનગી DNS સર્વર ઍક્સેસ કરી શકાતા નથી"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> મર્યાદિત કનેક્ટિવિટી ધરાવે છે"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"છતાં કનેક્ટ કરવા માટે ટૅપ કરો"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g> પર સ્વિચ કર્યું"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"જ્યારે <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> પાસે કોઈ ઇન્ટરનેટ ઍક્સેસ ન હોય ત્યારે ઉપકરણ <xliff:g id="NEW_NETWORK">%1$s</xliff:g>નો ઉપયોગ કરે છે. શુલ્ક લાગુ થઈ શકે છે."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> પરથી <xliff:g id="NEW_NETWORK">%2$s</xliff:g> પર સ્વિચ કર્યું"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"મોબાઇલ ડેટા"</item>
- <item msgid="6341719431034774569">"વાઇ-ફાઇ"</item>
- <item msgid="5081440868800877512">"બ્લૂટૂથ"</item>
- <item msgid="1160736166977503463">"ઇથરનેટ"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"મોબાઇલ ડેટા"</item>
+ <item msgid="5624324321165953608">"વાઇ-ફાઇ"</item>
+ <item msgid="5667906231066981731">"બ્લૂટૂથ"</item>
+ <item msgid="346574747471703768">"ઇથરનેટ"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"કોઈ અજાણ્યો નેટવર્કનો પ્રકાર"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"કોઈ અજાણ્યો નેટવર્કનો પ્રકાર"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-hi/strings.xml b/service/ServiceConnectivityResources/res/values-hi/strings.xml
index 80ed699..6e3bc6b 100644
--- a/service/ServiceConnectivityResources/res/values-hi/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-hi/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"सिस्टम कनेक्टिविटी के संसाधन"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"वाई-फ़ाई नेटवर्क में साइन इन करें"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"नेटवर्क में साइन इन करें"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"सिस्टम कनेक्टिविटी के संसाधन"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"वाई-फ़ाई नेटवर्क में साइन इन करें"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"नेटवर्क में साइन इन करें"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> का इंटरनेट नहीं चल रहा है"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"विकल्पों के लिए टैप करें"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"मोबाइल नेटवर्क पर इंटरनेट ऐक्सेस नहीं है"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"इस नेटवर्क पर इंटरनेट ऐक्सेस नहीं है"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"निजी डीएनएस सर्वर को ऐक्सेस नहीं किया जा सकता"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> की कनेक्टिविटी सीमित है"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"फिर भी कनेक्ट करने के लिए टैप करें"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g> पर ले जाया गया"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> में इंटरनेट की सुविधा नहीं होने पर डिवाइस <xliff:g id="NEW_NETWORK">%1$s</xliff:g> का इस्तेमाल करता है. इसके लिए शुल्क लिया जा सकता है."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> से <xliff:g id="NEW_NETWORK">%2$s</xliff:g> पर ले जाया गया"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> का इंटरनेट नहीं चल रहा है"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"विकल्पों के लिए टैप करें"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"मोबाइल नेटवर्क पर इंटरनेट ऐक्सेस नहीं है"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"इस नेटवर्क पर इंटरनेट ऐक्सेस नहीं है"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"निजी डीएनएस सर्वर को ऐक्सेस नहीं किया जा सकता"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> की कनेक्टिविटी सीमित है"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"फिर भी कनेक्ट करने के लिए टैप करें"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g> पर ले जाया गया"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> में इंटरनेट की सुविधा नहीं होने पर डिवाइस <xliff:g id="NEW_NETWORK">%1$s</xliff:g> का इस्तेमाल करता है. इसके लिए शुल्क लिया जा सकता है."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> से <xliff:g id="NEW_NETWORK">%2$s</xliff:g> पर ले जाया गया"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"मोबाइल डेटा"</item>
- <item msgid="6341719431034774569">"वाई-फ़ाई"</item>
- <item msgid="5081440868800877512">"ब्लूटूथ"</item>
- <item msgid="1160736166977503463">"ईथरनेट"</item>
- <item msgid="7347618872551558605">"वीपीएन"</item>
+ <item msgid="3004933964374161223">"मोबाइल डेटा"</item>
+ <item msgid="5624324321165953608">"वाई-फ़ाई"</item>
+ <item msgid="5667906231066981731">"ब्लूटूथ"</item>
+ <item msgid="346574747471703768">"ईथरनेट"</item>
+ <item msgid="5734728378097476003">"वीपीएन"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"अज्ञात नेटवर्क टाइप"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"अज्ञात नेटवर्क टाइप"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-hr/strings.xml b/service/ServiceConnectivityResources/res/values-hr/strings.xml
index 24bb22f..6a6de4c 100644
--- a/service/ServiceConnectivityResources/res/values-hr/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-hr/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Resursi za povezivost sustava"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Prijava na Wi-Fi mrežu"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Prijava na mrežu"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Resursi za povezivost sustava"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Prijava na Wi-Fi mrežu"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Prijava na mrežu"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> nema pristup internetu"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Dodirnite za opcije"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Mobilna mreža nema pristup internetu"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Mreža nema pristup internetu"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Nije moguće pristupiti privatnom DNS poslužitelju"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ima ograničenu povezivost"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Dodirnite da biste se ipak povezali"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Prelazak na drugu mrežu: <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"Kada <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> nema pristup internetu, na uređaju se upotrebljava <xliff:g id="NEW_NETWORK">%1$s</xliff:g>. Moguća je naplata naknade."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Mreža je promijenjena: <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> > <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> nema pristup internetu"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Dodirnite za opcije"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Mobilna mreža nema pristup internetu"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Mreža nema pristup internetu"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Nije moguće pristupiti privatnom DNS poslužitelju"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ima ograničenu povezivost"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Dodirnite da biste se ipak povezali"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Prelazak na drugu mrežu: <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"Kada <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> nema pristup internetu, na uređaju se upotrebljava <xliff:g id="NEW_NETWORK">%1$s</xliff:g>. Moguća je naplata naknade."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Mreža je promijenjena: <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> > <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"mobilni podaci"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"mobilni podaci"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"nepoznata vrsta mreže"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"nepoznata vrsta mreže"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-hu/strings.xml b/service/ServiceConnectivityResources/res/values-hu/strings.xml
index 47a1142..1d39d30 100644
--- a/service/ServiceConnectivityResources/res/values-hu/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-hu/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Rendszerkapcsolat erőforrásai"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Bejelentkezés Wi-Fi hálózatba"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Bejelentkezés a hálózatba"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Rendszerkapcsolat erőforrásai"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Bejelentkezés Wi-Fi hálózatba"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Bejelentkezés a hálózatba"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"A(z) <xliff:g id="NETWORK_SSID">%1$s</xliff:g> hálózaton nincs internet-hozzáférés"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Koppintson a beállítások megjelenítéséhez"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"A mobilhálózaton nincs internet-hozzáférés"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"A hálózaton nincs internet-hozzáférés"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"A privát DNS-kiszolgálóhoz nem lehet hozzáférni"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"A(z) <xliff:g id="NETWORK_SSID">%1$s</xliff:g> hálózat korlátozott kapcsolatot biztosít"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Koppintson, ha mindenképpen csatlakozni szeretne"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Átváltva erre: <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"<xliff:g id="NEW_NETWORK">%1$s</xliff:g> használata, ha nincs internet-hozzáférés <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g>-kapcsolaton keresztül. A szolgáltató díjat számíthat fel."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Átváltva <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g>-hálózatról erre: <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"A(z) <xliff:g id="NETWORK_SSID">%1$s</xliff:g> hálózaton nincs internet-hozzáférés"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Koppintson a beállítások megjelenítéséhez"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"A mobilhálózaton nincs internet-hozzáférés"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"A hálózaton nincs internet-hozzáférés"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"A privát DNS-kiszolgálóhoz nem lehet hozzáférni"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"A(z) <xliff:g id="NETWORK_SSID">%1$s</xliff:g> hálózat korlátozott kapcsolatot biztosít"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Koppintson, ha mindenképpen csatlakozni szeretne"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Átváltva erre: <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"<xliff:g id="NEW_NETWORK">%1$s</xliff:g> használata, ha nincs internet-hozzáférés <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g>-kapcsolaton keresztül. A szolgáltató díjat számíthat fel."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Átváltva <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g>-hálózatról erre: <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"mobiladatok"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"mobiladatok"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"ismeretlen hálózati típus"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"ismeretlen hálózati típus"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-hy/strings.xml b/service/ServiceConnectivityResources/res/values-hy/strings.xml
index dd951e8..99386d4 100644
--- a/service/ServiceConnectivityResources/res/values-hy/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-hy/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"System Connectivity Resources"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Մուտք գործեք Wi-Fi ցանց"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Մուտք գործեք ցանց"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"System Connectivity Resources"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Մուտք գործեք Wi-Fi ցանց"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Մուտք գործեք ցանց"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ցանցը չունի մուտք ինտերնետին"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Հպեք՝ ընտրանքները տեսնելու համար"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Բջջային ցանցը չի ապահովում ինտերնետ կապ"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Ցանցը միացված չէ ինտերնետին"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Մասնավոր DNS սերվերն անհասանելի է"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ցանցի կապը սահմանափակ է"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Հպեք՝ միանալու համար"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Անցել է <xliff:g id="NETWORK_TYPE">%1$s</xliff:g> ցանցի"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"Երբ <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> ցանցում ինտերնետ կապ չի լինում, սարքն անցնում է <xliff:g id="NEW_NETWORK">%1$s</xliff:g> ցանցի: Նման դեպքում կարող են վճարներ գանձվել:"</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> ցանցից անցել է <xliff:g id="NEW_NETWORK">%2$s</xliff:g> ցանցի"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ցանցը չունի մուտք ինտերնետին"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Հպեք՝ ընտրանքները տեսնելու համար"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Բջջային ցանցը չի ապահովում ինտերնետ կապ"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Ցանցը միացված չէ ինտերնետին"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Մասնավոր DNS սերվերն անհասանելի է"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ցանցի կապը սահմանափակ է"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Հպեք՝ միանալու համար"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Անցել է <xliff:g id="NETWORK_TYPE">%1$s</xliff:g> ցանցի"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"Երբ <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> ցանցում ինտերնետ կապ չի լինում, սարքն անցնում է <xliff:g id="NEW_NETWORK">%1$s</xliff:g> ցանցի: Նման դեպքում կարող են վճարներ գանձվել:"</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> ցանցից անցել է <xliff:g id="NEW_NETWORK">%2$s</xliff:g> ցանցի"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"բջջային ինտերնետ"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"բջջային ինտերնետ"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"ցանցի անհայտ տեսակ"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"ցանցի անհայտ տեսակ"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-in/strings.xml b/service/ServiceConnectivityResources/res/values-in/strings.xml
index d559f6b..f47d257 100644
--- a/service/ServiceConnectivityResources/res/values-in/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-in/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Resource Konektivitas Sistem"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Login ke jaringan Wi-Fi"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Login ke jaringan"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Resource Konektivitas Sistem"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Login ke jaringan Wi-Fi"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Login ke jaringan"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> tidak memiliki akses internet"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Ketuk untuk melihat opsi"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Jaringan seluler tidak memiliki akses internet"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Jaringan tidak memiliki akses internet"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Server DNS pribadi tidak dapat diakses"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> memiliki konektivitas terbatas"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Ketuk untuk tetap menyambungkan"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Dialihkan ke <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"Perangkat menggunakan <xliff:g id="NEW_NETWORK">%1$s</xliff:g> jika <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> tidak memiliki akses internet. Tarif mungkin berlaku."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Dialihkan dari <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> ke <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> tidak memiliki akses internet"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Ketuk untuk melihat opsi"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Jaringan seluler tidak memiliki akses internet"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Jaringan tidak memiliki akses internet"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Server DNS pribadi tidak dapat diakses"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> memiliki konektivitas terbatas"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Ketuk untuk tetap menyambungkan"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Dialihkan ke <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"Perangkat menggunakan <xliff:g id="NEW_NETWORK">%1$s</xliff:g> jika <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> tidak memiliki akses internet. Tarif mungkin berlaku."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Dialihkan dari <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> ke <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"data seluler"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"data seluler"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"jenis jaringan yang tidak dikenal"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"jenis jaringan yang tidak dikenal"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-is/strings.xml b/service/ServiceConnectivityResources/res/values-is/strings.xml
index 877c85f..eeba231 100644
--- a/service/ServiceConnectivityResources/res/values-is/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-is/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Tengigögn kerfis"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Skrá inn á Wi-Fi net"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Skrá inn á net"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Tengigögn kerfis"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Skrá inn á Wi-Fi net"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Skrá inn á net"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> er ekki með internetaðgang"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Ýttu til að sjá valkosti"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Farsímakerfið er ekki tengt við internetið"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Netkerfið er ekki tengt við internetið"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Ekki næst í DNS-einkaþjón"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"Tengigeta <xliff:g id="NETWORK_SSID">%1$s</xliff:g> er takmörkuð"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Ýttu til að tengjast samt"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Skipt yfir á <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"Tækið notar <xliff:g id="NEW_NETWORK">%1$s</xliff:g> þegar <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> er ekki með internetaðgang. Gjöld kunna að eiga við."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Skipt úr <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> yfir í <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> er ekki með internetaðgang"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Ýttu til að sjá valkosti"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Farsímakerfið er ekki tengt við internetið"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Netkerfið er ekki tengt við internetið"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Ekki næst í DNS-einkaþjón"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"Tengigeta <xliff:g id="NETWORK_SSID">%1$s</xliff:g> er takmörkuð"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Ýttu til að tengjast samt"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Skipt yfir á <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"Tækið notar <xliff:g id="NEW_NETWORK">%1$s</xliff:g> þegar <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> er ekki með internetaðgang. Gjöld kunna að eiga við."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Skipt úr <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> yfir í <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"farsímagögn"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"farsímagögn"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"óþekkt tegund netkerfis"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"óþekkt tegund netkerfis"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-it/strings.xml b/service/ServiceConnectivityResources/res/values-it/strings.xml
index bcac393..ec3ff8c 100644
--- a/service/ServiceConnectivityResources/res/values-it/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-it/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Risorse per connettività di sistema"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Accedi a rete Wi-Fi"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Accedi alla rete"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Risorse per connettività di sistema"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Accedi a rete Wi-Fi"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Accedi alla rete"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> non ha accesso a Internet"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Tocca per le opzioni"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"La rete mobile non ha accesso a Internet"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"La rete non ha accesso a Internet"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Non è possibile accedere al server DNS privato"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ha una connettività limitata"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Tocca per connettere comunque"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Passato a <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"Il dispositivo utilizza <xliff:g id="NEW_NETWORK">%1$s</xliff:g> quando <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> non ha accesso a Internet. Potrebbero essere applicati costi."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Passato da <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> a <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> non ha accesso a Internet"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Tocca per le opzioni"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"La rete mobile non ha accesso a Internet"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"La rete non ha accesso a Internet"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Non è possibile accedere al server DNS privato"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ha una connettività limitata"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Tocca per connettere comunque"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Passato a <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"Il dispositivo utilizza <xliff:g id="NEW_NETWORK">%1$s</xliff:g> quando <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> non ha accesso a Internet. Potrebbero essere applicati costi."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Passato da <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> a <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"dati mobili"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"dati mobili"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"tipo di rete sconosciuto"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"tipo di rete sconosciuto"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-iw/strings.xml b/service/ServiceConnectivityResources/res/values-iw/strings.xml
index d6684ce..d123ebb 100644
--- a/service/ServiceConnectivityResources/res/values-iw/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-iw/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"משאבי קישוריות מערכת"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"היכנס לרשת Wi-Fi"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"היכנס לרשת"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"משאבי קישוריות מערכת"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"היכנס לרשת Wi-Fi"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"היכנס לרשת"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"ל-<xliff:g id="NETWORK_SSID">%1$s</xliff:g> אין גישה לאינטרנט"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"הקש לקבלת אפשרויות"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"לרשת הסלולרית אין גישה לאינטרנט"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"לרשת אין גישה לאינטרנט"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"לא ניתן לגשת לשרת DNS הפרטי"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"הקישוריות של <xliff:g id="NETWORK_SSID">%1$s</xliff:g> מוגבלת"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"כדי להתחבר למרות זאת יש להקיש"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"מעבר אל <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"המכשיר משתמש ברשת <xliff:g id="NEW_NETWORK">%1$s</xliff:g> כשלרשת <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> אין גישה לאינטרנט. עשויים לחול חיובים."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"עבר מרשת <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> לרשת <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"ל-<xliff:g id="NETWORK_SSID">%1$s</xliff:g> אין גישה לאינטרנט"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"הקש לקבלת אפשרויות"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"לרשת הסלולרית אין גישה לאינטרנט"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"לרשת אין גישה לאינטרנט"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"לא ניתן לגשת לשרת DNS הפרטי"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"הקישוריות של <xliff:g id="NETWORK_SSID">%1$s</xliff:g> מוגבלת"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"כדי להתחבר למרות זאת יש להקיש"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"מעבר אל <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"המכשיר משתמש ברשת <xliff:g id="NEW_NETWORK">%1$s</xliff:g> כשלרשת <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> אין גישה לאינטרנט. עשויים לחול חיובים."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"עבר מרשת <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> לרשת <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"חבילת גלישה"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"אתרנט"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"חבילת גלישה"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"אתרנט"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"סוג רשת לא מזוהה"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"סוג רשת לא מזוהה"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-ja/strings.xml b/service/ServiceConnectivityResources/res/values-ja/strings.xml
index fa4a30a..7bb6f85 100644
--- a/service/ServiceConnectivityResources/res/values-ja/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-ja/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"システム接続リソース"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Wi-Fiネットワークにログイン"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"ネットワークにログインしてください"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"システム接続リソース"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Wi-Fiネットワークにログイン"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"ネットワークにログインしてください"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> はインターネットにアクセスできません"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"タップしてその他のオプションを表示"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"モバイル ネットワークがインターネットに接続されていません"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"ネットワークがインターネットに接続されていません"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"プライベート DNS サーバーにアクセスできません"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> の接続が制限されています"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"接続するにはタップしてください"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"「<xliff:g id="NETWORK_TYPE">%1$s</xliff:g>」に切り替えました"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"デバイスで「<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g>」によるインターネット接続ができない場合に「<xliff:g id="NEW_NETWORK">%1$s</xliff:g>」を使用します。通信料が発生することがあります。"</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"「<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g>」から「<xliff:g id="NEW_NETWORK">%2$s</xliff:g>」に切り替えました"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> はインターネットにアクセスできません"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"タップしてその他のオプションを表示"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"モバイル ネットワークがインターネットに接続されていません"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"ネットワークがインターネットに接続されていません"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"プライベート DNS サーバーにアクセスできません"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> の接続が制限されています"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"接続するにはタップしてください"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"「<xliff:g id="NETWORK_TYPE">%1$s</xliff:g>」に切り替えました"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"デバイスで「<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g>」によるインターネット接続ができない場合に「<xliff:g id="NEW_NETWORK">%1$s</xliff:g>」を使用します。通信料が発生することがあります。"</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"「<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g>」から「<xliff:g id="NEW_NETWORK">%2$s</xliff:g>」に切り替えました"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"モバイルデータ"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"イーサネット"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"モバイルデータ"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"イーサネット"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"不明なネットワーク タイプ"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"不明なネットワーク タイプ"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-ka/strings.xml b/service/ServiceConnectivityResources/res/values-ka/strings.xml
index 4183310..f42c567 100644
--- a/service/ServiceConnectivityResources/res/values-ka/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-ka/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"სისტემის კავშირის რესურსები"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Wi-Fi ქსელთან დაკავშირება"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"ქსელში შესვლა"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"სისტემის კავშირის რესურსები"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Wi-Fi ქსელთან დაკავშირება"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"ქსელში შესვლა"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>-ს არ აქვს ინტერნეტზე წვდომა"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"შეეხეთ ვარიანტების სანახავად"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"მობილურ ქსელს არ აქვს ინტერნეტზე წვდომა"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"ქსელს არ აქვს ინტერნეტზე წვდომა"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"პირად DNS სერვერზე წვდომა შეუძლებელია"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>-ის კავშირები შეზღუდულია"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"შეეხეთ, თუ მაინც გსურთ დაკავშირება"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"ახლა გამოიყენება <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"თუ <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> ინტერნეტთან კავშირს დაკარგავს, მოწყობილობის მიერ <xliff:g id="NEW_NETWORK">%1$s</xliff:g> იქნება გამოყენებული, რამაც შეიძლება დამატებითი ხარჯები გამოიწვიოს."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"ახლა გამოიყენება <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> (გამოიყენებოდა <xliff:g id="NEW_NETWORK">%2$s</xliff:g>)"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>-ს არ აქვს ინტერნეტზე წვდომა"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"შეეხეთ ვარიანტების სანახავად"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"მობილურ ქსელს არ აქვს ინტერნეტზე წვდომა"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"ქსელს არ აქვს ინტერნეტზე წვდომა"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"პირად DNS სერვერზე წვდომა შეუძლებელია"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>-ის კავშირები შეზღუდულია"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"შეეხეთ, თუ მაინც გსურთ დაკავშირება"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"ახლა გამოიყენება <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"თუ <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> ინტერნეტთან კავშირს დაკარგავს, მოწყობილობის მიერ <xliff:g id="NEW_NETWORK">%1$s</xliff:g> იქნება გამოყენებული, რამაც შეიძლება დამატებითი ხარჯები გამოიწვიოს."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"ახლა გამოიყენება <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> (გამოიყენებოდა <xliff:g id="NEW_NETWORK">%2$s</xliff:g>)"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"მობილური ინტერნეტი"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"მობილური ინტერნეტი"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"უცნობი ტიპის ქსელი"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"უცნობი ტიპის ქსელი"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-kk/strings.xml b/service/ServiceConnectivityResources/res/values-kk/strings.xml
index 54d5eb3..00c0f39 100644
--- a/service/ServiceConnectivityResources/res/values-kk/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-kk/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Жүйе байланысы ресурстары"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Wi-Fi желісіне кіру"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Желіге кіру"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Жүйе байланысы ресурстары"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Wi-Fi желісіне кіру"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Желіге кіру"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> желісінің интернетті пайдалану мүмкіндігі шектеулі."</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Опциялар үшін түртіңіз"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Мобильдік желі интернетке қосылмаған."</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Желі интернетке қосылмаған."</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Жеке DNS серверіне кіру мүмкін емес."</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> желісінің қосылу мүмкіндігі шектеулі."</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Бәрібір жалғау үшін түртіңіз."</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g> желісіне ауысты"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"Құрылғы <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> желісінде интернетпен байланыс жоғалған жағдайда <xliff:g id="NEW_NETWORK">%1$s</xliff:g> желісін пайдаланады. Деректер ақысы алынуы мүмкін."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> желісінен <xliff:g id="NEW_NETWORK">%2$s</xliff:g> желісіне ауысты"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> желісінің интернетті пайдалану мүмкіндігі шектеулі."</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Опциялар үшін түртіңіз"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Мобильдік желі интернетке қосылмаған."</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Желі интернетке қосылмаған."</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Жеке DNS серверіне кіру мүмкін емес."</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> желісінің қосылу мүмкіндігі шектеулі."</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Бәрібір жалғау үшін түртіңіз."</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g> желісіне ауысты"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"Құрылғы <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> желісінде интернетпен байланыс жоғалған жағдайда <xliff:g id="NEW_NETWORK">%1$s</xliff:g> желісін пайдаланады. Деректер ақысы алынуы мүмкін."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> желісінен <xliff:g id="NEW_NETWORK">%2$s</xliff:g> желісіне ауысты"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"мобильдік деректер"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"мобильдік деректер"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"желі түрі белгісіз"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"желі түрі белгісіз"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-km/strings.xml b/service/ServiceConnectivityResources/res/values-km/strings.xml
index bd778a1..fa06c5b 100644
--- a/service/ServiceConnectivityResources/res/values-km/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-km/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"ធនធានតភ្ជាប់ប្រព័ន្ធ"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"ចូលបណ្ដាញវ៉ាយហ្វាយ"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"ចូលទៅបណ្តាញ"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"ធនធានតភ្ជាប់ប្រព័ន្ធ"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"ចូលបណ្ដាញវ៉ាយហ្វាយ"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"ចូលទៅបណ្តាញ"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> មិនមានការតភ្ជាប់អ៊ីនធឺណិតទេ"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"ប៉ះសម្រាប់ជម្រើស"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"បណ្ដាញទូរសព្ទចល័តមិនមានការតភ្ជាប់អ៊ីនធឺណិតទេ"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"បណ្ដាញមិនមានការតភ្ជាប់អ៊ីនធឺណិតទេ"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"មិនអាចចូលប្រើម៉ាស៊ីនមេ DNS ឯកជនបានទេ"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> មានការតភ្ជាប់មានកម្រិត"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"មិនអីទេ ចុចភ្ជាប់ចុះ"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"បានប្តូរទៅ <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"ឧបករណ៍ប្រើ <xliff:g id="NEW_NETWORK">%1$s</xliff:g> នៅពេលដែល <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> មិនមានការតភ្ជាប់អ៊ីនធឺណិត។ អាចគិតថ្លៃលើការប្រើប្រាស់ទិន្នន័យ។"</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"បានប្តូរពី <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> ទៅ <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> មិនមានការតភ្ជាប់អ៊ីនធឺណិតទេ"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"ប៉ះសម្រាប់ជម្រើស"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"បណ្ដាញទូរសព្ទចល័តមិនមានការតភ្ជាប់អ៊ីនធឺណិតទេ"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"បណ្ដាញមិនមានការតភ្ជាប់អ៊ីនធឺណិតទេ"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"មិនអាចចូលប្រើម៉ាស៊ីនមេ DNS ឯកជនបានទេ"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> មានការតភ្ជាប់មានកម្រិត"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"មិនអីទេ ចុចភ្ជាប់ចុះ"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"បានប្តូរទៅ <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"ឧបករណ៍ប្រើ <xliff:g id="NEW_NETWORK">%1$s</xliff:g> នៅពេលដែល <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> មិនមានការតភ្ជាប់អ៊ីនធឺណិត។ អាចគិតថ្លៃលើការប្រើប្រាស់ទិន្នន័យ។"</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"បានប្តូរពី <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> ទៅ <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"ទិន្នន័យទូរសព្ទចល័ត"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"ប៊្លូធូស"</item>
- <item msgid="1160736166977503463">"អ៊ីសឺរណិត"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"ទិន្នន័យទូរសព្ទចល័ត"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"ប៊្លូធូស"</item>
+ <item msgid="346574747471703768">"អ៊ីសឺរណិត"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"ប្រភេទបណ្តាញដែលមិនស្គាល់"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"ប្រភេទបណ្តាញដែលមិនស្គាល់"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-kn/strings.xml b/service/ServiceConnectivityResources/res/values-kn/strings.xml
index 7f3a420..cde8fac 100644
--- a/service/ServiceConnectivityResources/res/values-kn/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-kn/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"ಸಿಸ್ಟಂ ಸಂಪರ್ಕ ಕಲ್ಪಿಸುವಿಕೆ ಮಾಹಿತಿಯ ಮೂಲಗಳು"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"ವೈ-ಫೈ ನೆಟ್ವರ್ಕ್ಗೆ ಸೈನ್ ಇನ್ ಮಾಡಿ"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"ನೆಟ್ವರ್ಕ್ಗೆ ಸೈನ್ ಇನ್ ಮಾಡಿ"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"ಸಿಸ್ಟಂ ಸಂಪರ್ಕ ಕಲ್ಪಿಸುವಿಕೆ ಮಾಹಿತಿಯ ಮೂಲಗಳು"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"ವೈ-ಫೈ ನೆಟ್ವರ್ಕ್ಗೆ ಸೈನ್ ಇನ್ ಮಾಡಿ"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"ನೆಟ್ವರ್ಕ್ಗೆ ಸೈನ್ ಇನ್ ಮಾಡಿ"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ಯಾವುದೇ ಇಂಟರ್ನೆಟ್ ಸಂಪರ್ಕವನ್ನು ಹೊಂದಿಲ್ಲ"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"ಆಯ್ಕೆಗಳಿಗೆ ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"ಮೊಬೈಲ್ ನೆಟ್ವರ್ಕ್ ಯಾವುದೇ ಇಂಟರ್ನೆಟ್ ಪ್ರವೇಶವನ್ನು ಹೊಂದಿಲ್ಲ"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"ನೆಟ್ವರ್ಕ್ ಇಂಟರ್ನೆಟ್ ಪ್ರವೇಶವನ್ನು ಹೊಂದಿಲ್ಲ"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"ಖಾಸಗಿ DNS ಸರ್ವರ್ ಅನ್ನು ಪ್ರವೇಶಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ಸೀಮಿತ ಸಂಪರ್ಕ ಕಲ್ಪಿಸುವಿಕೆಯನ್ನು ಹೊಂದಿದೆ"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"ಹೇಗಾದರೂ ಸಂಪರ್ಕಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g> ಗೆ ಬದಲಾಯಿಸಲಾಗಿದೆ"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> ಇಂಟರ್ನೆಟ್ ಪ್ರವೇಶ ಹೊಂದಿಲ್ಲದಿರುವಾಗ, ಸಾಧನವು <xliff:g id="NEW_NETWORK">%1$s</xliff:g> ಬಳಸುತ್ತದೆ. ಶುಲ್ಕಗಳು ಅನ್ವಯವಾಗಬಹುದು."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> ರಿಂದ <xliff:g id="NEW_NETWORK">%2$s</xliff:g> ಗೆ ಬದಲಾಯಿಸಲಾಗಿದೆ"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ಯಾವುದೇ ಇಂಟರ್ನೆಟ್ ಸಂಪರ್ಕವನ್ನು ಹೊಂದಿಲ್ಲ"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"ಆಯ್ಕೆಗಳಿಗೆ ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"ಮೊಬೈಲ್ ನೆಟ್ವರ್ಕ್ ಯಾವುದೇ ಇಂಟರ್ನೆಟ್ ಪ್ರವೇಶವನ್ನು ಹೊಂದಿಲ್ಲ"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"ನೆಟ್ವರ್ಕ್ ಇಂಟರ್ನೆಟ್ ಪ್ರವೇಶವನ್ನು ಹೊಂದಿಲ್ಲ"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"ಖಾಸಗಿ DNS ಸರ್ವರ್ ಅನ್ನು ಪ್ರವೇಶಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ಸೀಮಿತ ಸಂಪರ್ಕ ಕಲ್ಪಿಸುವಿಕೆಯನ್ನು ಹೊಂದಿದೆ"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"ಹೇಗಾದರೂ ಸಂಪರ್ಕಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g> ಗೆ ಬದಲಾಯಿಸಲಾಗಿದೆ"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> ಇಂಟರ್ನೆಟ್ ಪ್ರವೇಶ ಹೊಂದಿಲ್ಲದಿರುವಾಗ, ಸಾಧನವು <xliff:g id="NEW_NETWORK">%1$s</xliff:g> ಬಳಸುತ್ತದೆ. ಶುಲ್ಕಗಳು ಅನ್ವಯವಾಗಬಹುದು."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> ರಿಂದ <xliff:g id="NEW_NETWORK">%2$s</xliff:g> ಗೆ ಬದಲಾಯಿಸಲಾಗಿದೆ"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"ಮೊಬೈಲ್ ಡೇಟಾ"</item>
- <item msgid="6341719431034774569">"ವೈ-ಫೈ"</item>
- <item msgid="5081440868800877512">"ಬ್ಲೂಟೂತ್"</item>
- <item msgid="1160736166977503463">"ಇಥರ್ನೆಟ್"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"ಮೊಬೈಲ್ ಡೇಟಾ"</item>
+ <item msgid="5624324321165953608">"ವೈ-ಫೈ"</item>
+ <item msgid="5667906231066981731">"ಬ್ಲೂಟೂತ್"</item>
+ <item msgid="346574747471703768">"ಇಥರ್ನೆಟ್"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"ಅಪರಿಚಿತ ನೆಟ್ವರ್ಕ್ ಪ್ರಕಾರ"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"ಅಪರಿಚಿತ ನೆಟ್ವರ್ಕ್ ಪ್ರಕಾರ"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-ko/strings.xml b/service/ServiceConnectivityResources/res/values-ko/strings.xml
index a763cc5..eef59a9 100644
--- a/service/ServiceConnectivityResources/res/values-ko/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-ko/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"시스템 연결 리소스"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Wi-Fi 네트워크에 로그인"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"네트워크에 로그인"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"시스템 연결 리소스"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Wi-Fi 네트워크에 로그인"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"네트워크에 로그인"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>이(가) 인터넷에 액세스할 수 없습니다."</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"탭하여 옵션 보기"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"모바일 네트워크에 인터넷이 연결되어 있지 않습니다."</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"네트워크에 인터넷이 연결되어 있지 않습니다."</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"비공개 DNS 서버에 액세스할 수 없습니다."</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>에서 연결을 제한했습니다."</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"계속 연결하려면 탭하세요."</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g>(으)로 전환"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g>(으)로 인터넷에 연결할 수 없는 경우 기기에서 <xliff:g id="NEW_NETWORK">%1$s</xliff:g>이(가) 사용됩니다. 요금이 부과될 수 있습니다."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g>에서 <xliff:g id="NEW_NETWORK">%2$s</xliff:g>(으)로 전환"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>이(가) 인터넷에 액세스할 수 없습니다."</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"탭하여 옵션 보기"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"모바일 네트워크에 인터넷이 연결되어 있지 않습니다."</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"네트워크에 인터넷이 연결되어 있지 않습니다."</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"비공개 DNS 서버에 액세스할 수 없습니다."</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>에서 연결을 제한했습니다."</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"계속 연결하려면 탭하세요."</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g>(으)로 전환"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g>(으)로 인터넷에 연결할 수 없는 경우 기기에서 <xliff:g id="NEW_NETWORK">%1$s</xliff:g>이(가) 사용됩니다. 요금이 부과될 수 있습니다."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g>에서 <xliff:g id="NEW_NETWORK">%2$s</xliff:g>(으)로 전환"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"모바일 데이터"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"블루투스"</item>
- <item msgid="1160736166977503463">"이더넷"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"모바일 데이터"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"블루투스"</item>
+ <item msgid="346574747471703768">"이더넷"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"알 수 없는 네트워크 유형"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"알 수 없는 네트워크 유형"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-ky/strings.xml b/service/ServiceConnectivityResources/res/values-ky/strings.xml
index 3550af8..0027c8a 100644
--- a/service/ServiceConnectivityResources/res/values-ky/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-ky/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Тутумдун байланыш булагы"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Wi-Fi түйүнүнө кирүү"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Тармакка кирүү"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Системанын байланыш булагы"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Wi-Fi түйүнүнө кирүү"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Тармакка кирүү"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> Интернетке туташуусу жок"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Параметрлерди ачуу үчүн таптап коюңуз"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Мобилдик Интернет жок"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Тармактын Интернет жок"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Жеке DNS сервери жеткиликсиз"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> байланышы чектелген"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Баары бир туташуу үчүн таптаңыз"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g> тармагына которуштурулду"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> тармагы Интернетке туташпай турганда, түзмөгүңүз <xliff:g id="NEW_NETWORK">%1$s</xliff:g> тармагын колдонот. Акы алынышы мүмкүн."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> дегенден <xliff:g id="NEW_NETWORK">%2$s</xliff:g> тармагына которуштурулду"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> Интернетке туташуусу жок"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Параметрлерди ачуу үчүн таптап коюңуз"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Мобилдик Интернет жок"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Тармактын Интернет жок"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Жеке DNS сервери жеткиликсиз"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> байланышы чектелген"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Баары бир туташуу үчүн таптаңыз"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g> тармагына которуштурулду"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> тармагы Интернетке туташпай турганда, түзмөгүңүз <xliff:g id="NEW_NETWORK">%1$s</xliff:g> тармагын колдонот. Акы алынышы мүмкүн."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> дегенден <xliff:g id="NEW_NETWORK">%2$s</xliff:g> тармагына которуштурулду"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"мобилдик трафик"</item>
- <item msgid="6341719431034774569">"Wi‑Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"мобилдик трафик"</item>
+ <item msgid="5624324321165953608">"Wi‑Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"белгисиз тармак түрү"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"белгисиз тармак түрү"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-lo/strings.xml b/service/ServiceConnectivityResources/res/values-lo/strings.xml
index 4b3056f..64419f9 100644
--- a/service/ServiceConnectivityResources/res/values-lo/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-lo/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"ແຫຼ່ງຂໍ້ມູນການເຊື່ອມຕໍ່ລະບົບ"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"ເຂົ້າສູ່ລະບົບເຄືອຂ່າຍ Wi-Fi"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"ລົງຊື່ເຂົ້າເຄືອຂ່າຍ"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"ແຫຼ່ງຂໍ້ມູນການເຊື່ອມຕໍ່ລະບົບ"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"ເຂົ້າສູ່ລະບົບເຄືອຂ່າຍ Wi-Fi"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"ລົງຊື່ເຂົ້າເຄືອຂ່າຍ"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ບໍ່ມີການເຊື່ອມຕໍ່ອິນເຕີເນັດ"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"ແຕະເພື່ອເບິ່ງຕົວເລືອກ"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"ເຄືອຂ່າຍມືຖືບໍ່ສາມາດເຂົ້າເຖິງອິນເຕີເນັດໄດ້"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"ເຄືອຂ່າຍບໍ່ສາມາດເຂົ້າເຖິງອິນເຕີເນັດໄດ້"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"ບໍ່ສາມາດເຂົ້າເຖິງເຊີບເວີ DNS ສ່ວນຕົວໄດ້"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ມີການເຊື່ອມຕໍ່ທີ່ຈຳກັດ"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"ແຕະເພື່ອຢືນຢັນການເຊື່ອມຕໍ່"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"ສະຫຼັບໄປໃຊ້ <xliff:g id="NETWORK_TYPE">%1$s</xliff:g> ແລ້ວ"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"ອຸປະກອນຈະໃຊ້ <xliff:g id="NEW_NETWORK">%1$s</xliff:g> ເມື່ອ <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> ບໍ່ມີການເຊື່ອມຕໍ່ອິນເຕີເນັດ. ອາດມີການຮຽກເກັບຄ່າບໍລິການ."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"ສະຫຼັບຈາກ <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> ໄປໃຊ້ <xliff:g id="NEW_NETWORK">%2$s</xliff:g> ແລ້ວ"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ບໍ່ມີການເຊື່ອມຕໍ່ອິນເຕີເນັດ"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"ແຕະເພື່ອເບິ່ງຕົວເລືອກ"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"ເຄືອຂ່າຍມືຖືບໍ່ສາມາດເຂົ້າເຖິງອິນເຕີເນັດໄດ້"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"ເຄືອຂ່າຍບໍ່ສາມາດເຂົ້າເຖິງອິນເຕີເນັດໄດ້"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"ບໍ່ສາມາດເຂົ້າເຖິງເຊີບເວີ DNS ສ່ວນຕົວໄດ້"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ມີການເຊື່ອມຕໍ່ທີ່ຈຳກັດ"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"ແຕະເພື່ອຢືນຢັນການເຊື່ອມຕໍ່"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"ສະຫຼັບໄປໃຊ້ <xliff:g id="NETWORK_TYPE">%1$s</xliff:g> ແລ້ວ"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"ອຸປະກອນຈະໃຊ້ <xliff:g id="NEW_NETWORK">%1$s</xliff:g> ເມື່ອ <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> ບໍ່ມີການເຊື່ອມຕໍ່ອິນເຕີເນັດ. ອາດມີການຮຽກເກັບຄ່າບໍລິການ."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"ສະຫຼັບຈາກ <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> ໄປໃຊ້ <xliff:g id="NEW_NETWORK">%2$s</xliff:g> ແລ້ວ"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"ອິນເຕີເນັດມືຖື"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"ອີເທີເນັດ"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"ອິນເຕີເນັດມືຖື"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"ອີເທີເນັດ"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"ບໍ່ຮູ້ຈັກປະເພດເຄືອຂ່າຍ"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"ບໍ່ຮູ້ຈັກປະເພດເຄືອຂ່າຍ"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-lt/strings.xml b/service/ServiceConnectivityResources/res/values-lt/strings.xml
index 8eb41f1..f73f142 100644
--- a/service/ServiceConnectivityResources/res/values-lt/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-lt/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"System Connectivity Resources"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Prisijungti prie „Wi-Fi“ tinklo"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Prisijungti prie tinklo"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"System Connectivity Resources"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Prisijungti prie „Wi-Fi“ tinklo"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Prisijungti prie tinklo"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"„<xliff:g id="NETWORK_SSID">%1$s</xliff:g>“ negali pasiekti interneto"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Palieskite, kad būtų rodomos parinktys."</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Mobiliojo ryšio tinkle nėra prieigos prie interneto"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Tinkle nėra prieigos prie interneto"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Privataus DNS serverio negalima pasiekti"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"„<xliff:g id="NETWORK_SSID">%1$s</xliff:g>“ ryšys apribotas"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Palieskite, jei vis tiek norite prisijungti"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Perjungta į tinklą <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"Įrenginyje naudojamas kitas tinklas (<xliff:g id="NEW_NETWORK">%1$s</xliff:g>), kai dabartiniame tinkle (<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g>) nėra interneto ryšio. Gali būti taikomi mokesčiai."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Perjungta iš tinklo <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> į tinklą <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"„<xliff:g id="NETWORK_SSID">%1$s</xliff:g>“ negali pasiekti interneto"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Palieskite, kad būtų rodomos parinktys."</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Mobiliojo ryšio tinkle nėra prieigos prie interneto"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Tinkle nėra prieigos prie interneto"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Privataus DNS serverio negalima pasiekti"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"„<xliff:g id="NETWORK_SSID">%1$s</xliff:g>“ ryšys apribotas"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Palieskite, jei vis tiek norite prisijungti"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Perjungta į tinklą <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"Įrenginyje naudojamas kitas tinklas (<xliff:g id="NEW_NETWORK">%1$s</xliff:g>), kai dabartiniame tinkle (<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g>) nėra interneto ryšio. Gali būti taikomi mokesčiai."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Perjungta iš tinklo <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> į tinklą <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"mobiliojo ryšio duomenys"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Eternetas"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"mobiliojo ryšio duomenys"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Eternetas"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"nežinomas tinklo tipas"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"nežinomas tinklo tipas"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-lv/strings.xml b/service/ServiceConnectivityResources/res/values-lv/strings.xml
index 0647a4f..ce063a5 100644
--- a/service/ServiceConnectivityResources/res/values-lv/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-lv/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Sistēmas savienojamības resursi"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Pierakstieties Wi-Fi tīklā"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Pierakstīšanās tīklā"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Sistēmas savienojamības resursi"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Pierakstieties Wi-Fi tīklā"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Pierakstīšanās tīklā"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"Tīklā <xliff:g id="NETWORK_SSID">%1$s</xliff:g> nav piekļuves internetam"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Pieskarieties, lai skatītu iespējas."</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Mobilajā tīklā nav piekļuves internetam."</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Tīklā nav piekļuves internetam."</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Nevar piekļūt privātam DNS serverim."</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"Tīklā <xliff:g id="NETWORK_SSID">%1$s</xliff:g> ir ierobežota savienojamība"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Lai tik un tā izveidotu savienojumu, pieskarieties"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Pārslēdzās uz tīklu <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"Kad vienā tīklā (<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g>) nav piekļuves internetam, ierīcē tiek izmantots cits tīkls (<xliff:g id="NEW_NETWORK">%1$s</xliff:g>). Var tikt piemērota maksa."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Pārslēdzās no tīkla <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> uz tīklu <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"Tīklā <xliff:g id="NETWORK_SSID">%1$s</xliff:g> nav piekļuves internetam"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Pieskarieties, lai skatītu opcijas."</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Mobilajā tīklā nav piekļuves internetam."</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Tīklā nav piekļuves internetam."</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Nevar piekļūt privātam DNS serverim."</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"Tīklā <xliff:g id="NETWORK_SSID">%1$s</xliff:g> ir ierobežota savienojamība"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Lai tik un tā izveidotu savienojumu, pieskarieties"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Pārslēdzās uz tīklu <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"Kad vienā tīklā (<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g>) nav piekļuves internetam, ierīcē tiek izmantots cits tīkls (<xliff:g id="NEW_NETWORK">%1$s</xliff:g>). Var tikt piemērota maksa."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Pārslēdzās no tīkla <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> uz tīklu <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"mobilie dati"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"mobilie dati"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"nezināms tīkla veids"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"nezināms tīkla veids"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-mk/strings.xml b/service/ServiceConnectivityResources/res/values-mk/strings.xml
index b0024e2..fb105e0 100644
--- a/service/ServiceConnectivityResources/res/values-mk/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-mk/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"System Connectivity Resources"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Најавете се на мрежа на Wi-Fi"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Најавете се на мрежа"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"System Connectivity Resources"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Најавете се на мрежа на Wi-Fi"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Најавете се на мрежа"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> нема интернет-пристап"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Допрете за опции"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Мобилната мрежа нема интернет-пристап"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Мрежата нема интернет-пристап"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Не може да се пристапи до приватниот DNS-сервер"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> има ограничена поврзливост"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Допрете за да се поврзете и покрај тоа"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Префрлено на <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"Уредот користи <xliff:g id="NEW_NETWORK">%1$s</xliff:g> кога <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> нема пристап до интернет. Може да се наплатат трошоци."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Префрлено од <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> на <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> нема интернет-пристап"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Допрете за опции"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Мобилната мрежа нема интернет-пристап"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Мрежата нема интернет-пристап"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Не може да се пристапи до приватниот DNS-сервер"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> има ограничена поврзливост"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Допрете за да се поврзете и покрај тоа"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Префрлено на <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"Уредот користи <xliff:g id="NEW_NETWORK">%1$s</xliff:g> кога <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> нема пристап до интернет. Може да се наплатат трошоци."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Префрлено од <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> на <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"мобилен интернет"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Етернет"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"мобилен интернет"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Етернет"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"непознат тип мрежа"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"непознат тип мрежа"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-ml/strings.xml b/service/ServiceConnectivityResources/res/values-ml/strings.xml
index 8ce7667..9a51238 100644
--- a/service/ServiceConnectivityResources/res/values-ml/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-ml/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"സിസ്റ്റം കണക്റ്റിവിറ്റി ഉറവിടങ്ങൾ"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"വൈഫൈ നെറ്റ്വർക്കിലേക്ക് സൈൻ ഇൻ ചെയ്യുക"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"നെറ്റ്വർക്കിലേക്ക് സൈൻ ഇൻ ചെയ്യുക"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"സിസ്റ്റം കണക്റ്റിവിറ്റി ഉറവിടങ്ങൾ"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"വൈഫൈ നെറ്റ്വർക്കിലേക്ക് സൈൻ ഇൻ ചെയ്യുക"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"നെറ്റ്വർക്കിലേക്ക് സൈൻ ഇൻ ചെയ്യുക"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> എന്നതിന് ഇന്റർനെറ്റ് ആക്സസ് ഇല്ല"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"ഓപ്ഷനുകൾക്ക് ടാപ്പുചെയ്യുക"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"മൊബെെൽ നെറ്റ്വർക്കിന് ഇന്റർനെറ്റ് ആക്സസ് ഇല്ല"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"നെറ്റ്വർക്കിന് ഇന്റർനെറ്റ് ആക്സസ് ഇല്ല"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"സ്വകാര്യ DNS സെർവർ ആക്സസ് ചെയ്യാനാവില്ല"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> എന്നതിന് പരിമിതമായ കണക്റ്റിവിറ്റി ഉണ്ട്"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"ഏതുവിധേനയും കണക്റ്റ് ചെയ്യാൻ ടാപ്പ് ചെയ്യുക"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g> എന്നതിലേക്ക് മാറി"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g>-ന് ഇന്റർനെറ്റ് ആക്സസ് ഇല്ലാത്തപ്പോൾ ഉപകരണം <xliff:g id="NEW_NETWORK">%1$s</xliff:g> ഉപയോഗിക്കുന്നു. നിരക്കുകൾ ബാധകമായേക്കാം."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> നെറ്റ്വർക്കിൽ നിന്ന് <xliff:g id="NEW_NETWORK">%2$s</xliff:g> നെറ്റ്വർക്കിലേക്ക് മാറി"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> എന്നതിന് ഇന്റർനെറ്റ് ആക്സസ് ഇല്ല"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"ഓപ്ഷനുകൾക്ക് ടാപ്പുചെയ്യുക"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"മൊബെെൽ നെറ്റ്വർക്കിന് ഇന്റർനെറ്റ് ആക്സസ് ഇല്ല"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"നെറ്റ്വർക്കിന് ഇന്റർനെറ്റ് ആക്സസ് ഇല്ല"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"സ്വകാര്യ DNS സെർവർ ആക്സസ് ചെയ്യാനാവില്ല"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> എന്നതിന് പരിമിതമായ കണക്റ്റിവിറ്റി ഉണ്ട്"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"ഏതുവിധേനയും കണക്റ്റ് ചെയ്യാൻ ടാപ്പ് ചെയ്യുക"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g> എന്നതിലേക്ക് മാറി"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g>-ന് ഇന്റർനെറ്റ് ആക്സസ് ഇല്ലാത്തപ്പോൾ ഉപകരണം <xliff:g id="NEW_NETWORK">%1$s</xliff:g> ഉപയോഗിക്കുന്നു. നിരക്കുകൾ ബാധകമായേക്കാം."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> നെറ്റ്വർക്കിൽ നിന്ന് <xliff:g id="NEW_NETWORK">%2$s</xliff:g> നെറ്റ്വർക്കിലേക്ക് മാറി"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"മൊബൈൽ ഡാറ്റ"</item>
- <item msgid="6341719431034774569">"വൈഫൈ"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"ഇതർനെറ്റ്"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"മൊബൈൽ ഡാറ്റ"</item>
+ <item msgid="5624324321165953608">"വൈഫൈ"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"ഇതർനെറ്റ്"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"അജ്ഞാതമായ നെറ്റ്വർക്ക് തരം"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"അജ്ഞാതമായ നെറ്റ്വർക്ക് തരം"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-mn/strings.xml b/service/ServiceConnectivityResources/res/values-mn/strings.xml
index be8b592..8372533 100644
--- a/service/ServiceConnectivityResources/res/values-mn/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-mn/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Системийн холболтын нөөцүүд"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Wi-Fi сүлжээнд нэвтэрнэ үү"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Сүлжээнд нэвтэрнэ үү"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Системийн холболтын нөөцүүд"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Wi-Fi сүлжээнд нэвтэрнэ үү"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Сүлжээнд нэвтэрнэ үү"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>-д интернэтийн хандалт алга"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Сонголт хийхийн тулд товшино уу"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Мобайл сүлжээнд интернэт хандалт байхгүй байна"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Сүлжээнд интернэт хандалт байхгүй байна"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Хувийн DNS серверт хандах боломжгүй байна"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> зарим үйлчилгээнд хандах боломжгүй байна"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Ямар ч тохиолдолд холбогдохын тулд товших"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g> руу шилжүүлсэн"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> интернет холболтгүй үед төхөөрөмж <xliff:g id="NEW_NETWORK">%1$s</xliff:g>-г ашигладаг. Төлбөр гарч болзошгүй."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g>-с <xliff:g id="NEW_NETWORK">%2$s</xliff:g> руу шилжүүлсэн"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>-д интернэтийн хандалт алга"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Сонголт хийхийн тулд товшино уу"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Мобайл сүлжээнд интернэт хандалт байхгүй байна"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Сүлжээнд интернэт хандалт байхгүй байна"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Хувийн DNS серверт хандах боломжгүй байна"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> зарим үйлчилгээнд хандах боломжгүй байна"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Ямар ч тохиолдолд холбогдохын тулд товших"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g> руу шилжүүлсэн"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> интернет холболтгүй үед төхөөрөмж <xliff:g id="NEW_NETWORK">%1$s</xliff:g>-г ашигладаг. Төлбөр гарч болзошгүй."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g>-с <xliff:g id="NEW_NETWORK">%2$s</xliff:g> руу шилжүүлсэн"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"мобайл дата"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Этернэт"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"мобайл дата"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Этернэт"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"үл мэдэгдэх сүлжээний төрөл"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"үл мэдэгдэх сүлжээний төрөл"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-mr/strings.xml b/service/ServiceConnectivityResources/res/values-mr/strings.xml
index fe7df84..658b19b 100644
--- a/service/ServiceConnectivityResources/res/values-mr/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-mr/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"सिस्टम कनेक्टिव्हिटी चे स्रोत"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"वाय-फाय नेटवर्कमध्ये साइन इन करा"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"नेटवर्कवर साइन इन करा"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"सिस्टम कनेक्टिव्हिटी चे स्रोत"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"वाय-फाय नेटवर्कमध्ये साइन इन करा"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"नेटवर्कवर साइन इन करा"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ला इंटरनेट अॅक्सेस नाही"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"पर्यायांसाठी टॅप करा"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"मोबाइल नेटवर्कला इंटरनेट ॲक्सेस नाही"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"नेटवर्कला इंटरनेट ॲक्सेस नाही"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"खाजगी DNS सर्व्हर ॲक्सेस करू शकत नाही"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ला मर्यादित कनेक्टिव्हिटी आहे"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"तरीही कनेक्ट करण्यासाठी टॅप करा"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g> वर स्विच केले"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> कडे इंटरनेटचा अॅक्सेस नसताना डिव्हाइस <xliff:g id="NEW_NETWORK">%1$s</xliff:g> वापरते. शुल्क लागू शकते."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> वरून <xliff:g id="NEW_NETWORK">%2$s</xliff:g> वर स्विच केले"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ला इंटरनेट अॅक्सेस नाही"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"पर्यायांसाठी टॅप करा"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"मोबाइल नेटवर्कला इंटरनेट ॲक्सेस नाही"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"नेटवर्कला इंटरनेट ॲक्सेस नाही"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"खाजगी DNS सर्व्हर ॲक्सेस करू शकत नाही"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ला मर्यादित कनेक्टिव्हिटी आहे"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"तरीही कनेक्ट करण्यासाठी टॅप करा"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g> वर स्विच केले"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> कडे इंटरनेटचा अॅक्सेस नसताना डिव्हाइस <xliff:g id="NEW_NETWORK">%1$s</xliff:g> वापरते. शुल्क लागू शकते."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> वरून <xliff:g id="NEW_NETWORK">%2$s</xliff:g> वर स्विच केले"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"मोबाइल डेटा"</item>
- <item msgid="6341719431034774569">"वाय-फाय"</item>
- <item msgid="5081440868800877512">"ब्लूटूथ"</item>
- <item msgid="1160736166977503463">"इथरनेट"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"मोबाइल डेटा"</item>
+ <item msgid="5624324321165953608">"वाय-फाय"</item>
+ <item msgid="5667906231066981731">"ब्लूटूथ"</item>
+ <item msgid="346574747471703768">"इथरनेट"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"अज्ञात नेटवर्क प्रकार"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"अज्ञात नेटवर्क प्रकार"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-ms/strings.xml b/service/ServiceConnectivityResources/res/values-ms/strings.xml
index 54b49a2..84b242c 100644
--- a/service/ServiceConnectivityResources/res/values-ms/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-ms/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Sumber Kesambungan Sistem"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Log masuk ke rangkaian Wi-Fi"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Log masuk ke rangkaian"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Sumber Kesambungan Sistem"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Log masuk ke rangkaian Wi-Fi"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Log masuk ke rangkaian"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> tiada akses Internet"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Ketik untuk mendapatkan pilihan"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Rangkaian mudah alih tiada akses Internet"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Rangkaian tiada akses Internet"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Pelayan DNS peribadi tidak boleh diakses"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> mempunyai kesambungan terhad"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Ketik untuk menyambung juga"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Beralih kepada <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"Peranti menggunakan <xliff:g id="NEW_NETWORK">%1$s</xliff:g> apabila <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> tiada akses Internet. Bayaran mungkin dikenakan."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Beralih daripada <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> kepada <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> tiada akses Internet"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Ketik untuk mendapatkan pilihan"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Rangkaian mudah alih tiada akses Internet"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Rangkaian tiada akses Internet"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Pelayan DNS peribadi tidak boleh diakses"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> mempunyai kesambungan terhad"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Ketik untuk menyambung juga"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Beralih kepada <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"Peranti menggunakan <xliff:g id="NEW_NETWORK">%1$s</xliff:g> apabila <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> tiada akses Internet. Bayaran mungkin dikenakan."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Beralih daripada <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> kepada <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"data mudah alih"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"data mudah alih"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"jenis rangkaian tidak diketahui"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"jenis rangkaian tidak diketahui"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-my/strings.xml b/service/ServiceConnectivityResources/res/values-my/strings.xml
index 15b75f0..6832263 100644
--- a/service/ServiceConnectivityResources/res/values-my/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-my/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"စနစ်ချိတ်ဆက်နိုင်မှု ရင်းမြစ်များ"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"ဝိုင်ဖိုင်ကွန်ရက်သို့ လက်မှတ်ထိုးဝင်ပါ"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"ကွန်ယက်သို့ လက်မှတ်ထိုးဝင်ရန်"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"စနစ်ချိတ်ဆက်နိုင်မှု ရင်းမြစ်များ"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"ဝိုင်ဖိုင်ကွန်ရက်သို့ လက်မှတ်ထိုးဝင်ပါ"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"ကွန်ယက်သို့ လက်မှတ်ထိုးဝင်ရန်"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> တွင် အင်တာနက်အသုံးပြုခွင့် မရှိပါ"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"အခြားရွေးချယ်စရာများကိုကြည့်ရန် တို့ပါ"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"မိုဘိုင်းကွန်ရက်တွင် အင်တာနက်ချိတ်ဆက်မှု မရှိပါ"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"ကွန်ရက်တွင် အင်တာနက်အသုံးပြုခွင့် မရှိပါ"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"သီးသန့် ဒီအန်အက်စ် (DNS) ဆာဗာကို သုံး၍မရပါ။"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> တွင် ချိတ်ဆက်မှုကို ကန့်သတ်ထားသည်"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"မည်သို့ပင်ဖြစ်စေ ချိတ်ဆက်ရန် တို့ပါ"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g> သို့ ပြောင်းလိုက်ပြီ"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> ဖြင့် အင်တာနက် အသုံးမပြုနိုင်သည့်အချိန်တွင် စက်ပစ္စည်းသည် <xliff:g id="NEW_NETWORK">%1$s</xliff:g> ကို သုံးပါသည်။ ဒေတာသုံးစွဲခ ကျသင့်နိုင်ပါသည်။"</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> မှ <xliff:g id="NEW_NETWORK">%2$s</xliff:g> သို့ ပြောင်းလိုက်ပြီ"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> တွင် အင်တာနက်အသုံးပြုခွင့် မရှိပါ"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"အခြားရွေးချယ်စရာများကိုကြည့်ရန် တို့ပါ"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"မိုဘိုင်းကွန်ရက်တွင် အင်တာနက်ချိတ်ဆက်မှု မရှိပါ"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"ကွန်ရက်တွင် အင်တာနက်အသုံးပြုခွင့် မရှိပါ"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"သီးသန့် ဒီအန်အက်စ် (DNS) ဆာဗာကို သုံး၍မရပါ။"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> တွင် ချိတ်ဆက်မှုကို ကန့်သတ်ထားသည်"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"မည်သို့ပင်ဖြစ်စေ ချိတ်ဆက်ရန် တို့ပါ"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g> သို့ ပြောင်းလိုက်ပြီ"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> ဖြင့် အင်တာနက် အသုံးမပြုနိုင်သည့်အချိန်တွင် စက်ပစ္စည်းသည် <xliff:g id="NEW_NETWORK">%1$s</xliff:g> ကို သုံးပါသည်။ ဒေတာသုံးစွဲခ ကျသင့်နိုင်ပါသည်။"</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> မှ <xliff:g id="NEW_NETWORK">%2$s</xliff:g> သို့ ပြောင်းလိုက်ပြီ"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"မိုဘိုင်းဒေတာ"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"ဘလူးတုသ်"</item>
- <item msgid="1160736166977503463">"အီသာနက်"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"မိုဘိုင်းဒေတာ"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"ဘလူးတုသ်"</item>
+ <item msgid="346574747471703768">"အီသာနက်"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"အမည်မသိကွန်ရက်အမျိုးအစား"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"အမည်မသိကွန်ရက်အမျိုးအစား"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-nb/strings.xml b/service/ServiceConnectivityResources/res/values-nb/strings.xml
index a561def..00a0728 100644
--- a/service/ServiceConnectivityResources/res/values-nb/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-nb/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Ressurser for systemtilkobling"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Logg på Wi-Fi-nettverket"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Logg på nettverk"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Ressurser for systemtilkobling"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Logg på Wi-Fi-nettverket"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Logg på nettverk"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> har ingen internettilkobling"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Trykk for å få alternativer"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Mobilnettverket har ingen internettilgang"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Nettverket har ingen internettilgang"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Den private DNS-tjeneren kan ikke nås"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> har begrenset tilkobling"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Trykk for å koble til likevel"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Byttet til <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"Enheten bruker <xliff:g id="NEW_NETWORK">%1$s</xliff:g> når <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> ikke har Internett-tilgang. Avgifter kan påløpe."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Byttet fra <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> til <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> har ingen internettilkobling"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Trykk for å få alternativer"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Mobilnettverket har ingen internettilgang"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Nettverket har ingen internettilgang"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Den private DNS-tjeneren kan ikke nås"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> har begrenset tilkobling"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Trykk for å koble til likevel"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Byttet til <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"Enheten bruker <xliff:g id="NEW_NETWORK">%1$s</xliff:g> når <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> ikke har Internett-tilgang. Avgifter kan påløpe."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Byttet fra <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> til <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"mobildata"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"mobildata"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"en ukjent nettverkstype"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"en ukjent nettverkstype"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-ne/strings.xml b/service/ServiceConnectivityResources/res/values-ne/strings.xml
index f74542d..2eaf162 100644
--- a/service/ServiceConnectivityResources/res/values-ne/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-ne/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"सिस्टम कनेक्टिभिटीका स्रोतहरू"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Wi-Fi नेटवर्कमा साइन इन गर्नुहोस्"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"सञ्जालमा साइन इन गर्नुहोस्"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"सिस्टम कनेक्टिभिटीका स्रोतहरू"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Wi-Fi नेटवर्कमा साइन इन गर्नुहोस्"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"सञ्जालमा साइन इन गर्नुहोस्"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> को इन्टरनेटमाथि पहुँच छैन"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"विकल्पहरूका लागि ट्याप गर्नुहोस्"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"मोबाइल नेटवर्कको इन्टरनेटमाथि पहुँच छैन"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"नेटवर्कको इन्टरनेटमाथि पहुँच छैन"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"निजी DNS सर्भरमाथि पहुँच प्राप्त गर्न सकिँदैन"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> को जडान सीमित छ"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"जसरी भए पनि जडान गर्न ट्याप गर्नुहोस्"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g> मा बदल्नुहोस्"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> मार्फत इन्टरनेटमाथि पहुँच राख्न नसकेको अवस्थामा यन्त्रले <xliff:g id="NEW_NETWORK">%1$s</xliff:g> प्रयोग गर्दछ। शुल्क लाग्न सक्छ।"</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> बाट <xliff:g id="NEW_NETWORK">%2$s</xliff:g> मा परिवर्तन गरियो"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> को इन्टरनेटमाथि पहुँच छैन"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"विकल्पहरूका लागि ट्याप गर्नुहोस्"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"मोबाइल नेटवर्कको इन्टरनेटमाथि पहुँच छैन"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"नेटवर्कको इन्टरनेटमाथि पहुँच छैन"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"निजी DNS सर्भरमाथि पहुँच प्राप्त गर्न सकिँदैन"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> को जडान सीमित छ"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"जसरी भए पनि जडान गर्न ट्याप गर्नुहोस्"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g> मा बदल्नुहोस्"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> मार्फत इन्टरनेटमाथि पहुँच राख्न नसकेको अवस्थामा यन्त्रले <xliff:g id="NEW_NETWORK">%1$s</xliff:g> प्रयोग गर्दछ। शुल्क लाग्न सक्छ।"</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> बाट <xliff:g id="NEW_NETWORK">%2$s</xliff:g> मा परिवर्तन गरियो"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"मोबाइल डेटा"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"ब्लुटुथ"</item>
- <item msgid="1160736166977503463">"इथरनेट"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"मोबाइल डेटा"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"ब्लुटुथ"</item>
+ <item msgid="346574747471703768">"इथरनेट"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"नेटवर्कको कुनै अज्ञात प्रकार"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"नेटवर्कको कुनै अज्ञात प्रकार"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-nl/strings.xml b/service/ServiceConnectivityResources/res/values-nl/strings.xml
index 0f3203b..394c552 100644
--- a/service/ServiceConnectivityResources/res/values-nl/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-nl/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Resources voor systeemconnectiviteit"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Inloggen bij wifi-netwerk"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Inloggen bij netwerk"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Resources voor systeemconnectiviteit"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Inloggen bij wifi-netwerk"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Inloggen bij netwerk"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> heeft geen internettoegang"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Tik voor opties"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Mobiel netwerk heeft geen internettoegang"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Netwerk heeft geen internettoegang"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Geen toegang tot privé-DNS-server"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> heeft beperkte connectiviteit"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Tik om toch verbinding te maken"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Overgeschakeld naar <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"Apparaat gebruikt <xliff:g id="NEW_NETWORK">%1$s</xliff:g> wanneer <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> geen internetverbinding heeft. Er kunnen kosten in rekening worden gebracht."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Overgeschakeld van <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> naar <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> heeft geen internettoegang"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Tik voor opties"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Mobiel netwerk heeft geen internettoegang"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Netwerk heeft geen internettoegang"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Geen toegang tot privé-DNS-server"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> heeft beperkte connectiviteit"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Tik om toch verbinding te maken"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Overgeschakeld naar <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"Apparaat gebruikt <xliff:g id="NEW_NETWORK">%1$s</xliff:g> wanneer <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> geen internetverbinding heeft. Er kunnen kosten in rekening worden gebracht."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Overgeschakeld van <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> naar <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"mobiele data"</item>
- <item msgid="6341719431034774569">"Wifi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"mobiele data"</item>
+ <item msgid="5624324321165953608">"Wifi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"een onbekend netwerktype"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"een onbekend netwerktype"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-or/strings.xml b/service/ServiceConnectivityResources/res/values-or/strings.xml
index ecf4d69..8b85884 100644
--- a/service/ServiceConnectivityResources/res/values-or/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-or/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"ସିଷ୍ଟମର ସଂଯୋଗ ସମ୍ବନ୍ଧିତ ରିସୋର୍ସଗୁଡ଼ିକ"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"ୱାଇ-ଫାଇ ନେଟୱର୍କରେ ସାଇନ୍-ଇନ୍ କରନ୍ତୁ"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"ନେଟ୍ୱର୍କରେ ସାଇନ୍ ଇନ୍ କରନ୍ତୁ"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"ସିଷ୍ଟମର ସଂଯୋଗ ସମ୍ବନ୍ଧିତ ରିସୋର୍ସଗୁଡ଼ିକ"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"ୱାଇ-ଫାଇ ନେଟୱର୍କରେ ସାଇନ୍-ଇନ୍ କରନ୍ତୁ"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"ନେଟ୍ୱର୍କରେ ସାଇନ୍ ଇନ୍ କରନ୍ତୁ"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>ର ଇଣ୍ଟର୍ନେଟ୍ ଆକ୍ସେସ୍ ନାହିଁ"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"ବିକଳ୍ପ ପାଇଁ ଟାପ୍ କରନ୍ତୁ"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"ମୋବାଇଲ୍ ନେଟ୍ୱାର୍କରେ ଇଣ୍ଟର୍ନେଟ୍ ଆକ୍ସେସ୍ ନାହିଁ"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"ନେଟ୍ୱାର୍କରେ ଇଣ୍ଟର୍ନେଟ୍ ଆକ୍ସେସ୍ ନାହିଁ"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"ବ୍ୟକ୍ତିଗତ DNS ସର୍ଭର୍ ଆକ୍ସେସ୍ କରିହେବ ନାହିଁ"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>ର ସୀମିତ ସଂଯୋଗ ଅଛି"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"ତଥାପି ଯୋଗାଯୋଗ କରିବାକୁ ଟାପ୍ କରନ୍ତୁ"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g>କୁ ବଦଳାଗଲା"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g>ର ଇଣ୍ଟରନେଟ୍ ଆକ୍ସେସ୍ ନଥିବାବେଳେ ଡିଭାଇସ୍ <xliff:g id="NEW_NETWORK">%1$s</xliff:g> ବ୍ୟବହାର କରିଥାଏ। ଶୁଳ୍କ ଲାଗୁ ହୋଇପାରେ।"</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> ରୁ <xliff:g id="NEW_NETWORK">%2$s</xliff:g>କୁ ବଦଳାଗଲା"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>ର ଇଣ୍ଟର୍ନେଟ୍ ଆକ୍ସେସ୍ ନାହିଁ"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"ବିକଳ୍ପ ପାଇଁ ଟାପ୍ କରନ୍ତୁ"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"ମୋବାଇଲ୍ ନେଟ୍ୱାର୍କରେ ଇଣ୍ଟର୍ନେଟ୍ ଆକ୍ସେସ୍ ନାହିଁ"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"ନେଟ୍ୱାର୍କରେ ଇଣ୍ଟର୍ନେଟ୍ ଆକ୍ସେସ୍ ନାହିଁ"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"ବ୍ୟକ୍ତିଗତ DNS ସର୍ଭର୍ ଆକ୍ସେସ୍ କରିହେବ ନାହିଁ"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>ର ସୀମିତ ସଂଯୋଗ ଅଛି"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"ତଥାପି ଯୋଗାଯୋଗ କରିବାକୁ ଟାପ୍ କରନ୍ତୁ"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g>କୁ ବଦଳାଗଲା"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g>ର ଇଣ୍ଟରନେଟ୍ ଆକ୍ସେସ୍ ନଥିବାବେଳେ ଡିଭାଇସ୍ <xliff:g id="NEW_NETWORK">%1$s</xliff:g> ବ୍ୟବହାର କରିଥାଏ। ଶୁଳ୍କ ଲାଗୁ ହୋଇପାରେ।"</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> ରୁ <xliff:g id="NEW_NETWORK">%2$s</xliff:g>କୁ ବଦଳାଗଲା"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"ମୋବାଇଲ ଡାଟା"</item>
- <item msgid="6341719431034774569">"ୱାଇ-ଫାଇ"</item>
- <item msgid="5081440868800877512">"ବ୍ଲୁଟୁଥ୍"</item>
- <item msgid="1160736166977503463">"ଇଥରନେଟ୍"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"ମୋବାଇଲ ଡାଟା"</item>
+ <item msgid="5624324321165953608">"ୱାଇ-ଫାଇ"</item>
+ <item msgid="5667906231066981731">"ବ୍ଲୁଟୁଥ୍"</item>
+ <item msgid="346574747471703768">"ଇଥରନେଟ୍"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"ଏକ ଅଜଣା ନେଟୱାର୍କ ପ୍ରକାର"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"ଏକ ଅଜଣା ନେଟୱାର୍କ ପ୍ରକାର"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-pa/strings.xml b/service/ServiceConnectivityResources/res/values-pa/strings.xml
index 4328054..9f71cac 100644
--- a/service/ServiceConnectivityResources/res/values-pa/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-pa/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"ਸਿਸਟਮ ਕਨੈਕਟੀਵਿਟੀ ਸਰੋਤ"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"ਵਾਈ-ਫਾਈ ਨੈੱਟਵਰਕ \'ਤੇ ਸਾਈਨ-ਇਨ ਕਰੋ"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"ਨੈੱਟਵਰਕ \'ਤੇ ਸਾਈਨ-ਇਨ ਕਰੋ"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"ਸਿਸਟਮ ਕਨੈਕਟੀਵਿਟੀ ਸਰੋਤ"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"ਵਾਈ-ਫਾਈ ਨੈੱਟਵਰਕ \'ਤੇ ਸਾਈਨ-ਇਨ ਕਰੋ"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"ਨੈੱਟਵਰਕ \'ਤੇ ਸਾਈਨ-ਇਨ ਕਰੋ"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ਕੋਲ ਇੰਟਰਨੈੱਟ ਪਹੁੰਚ ਨਹੀਂ ਹੈ"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"ਵਿਕਲਪਾਂ ਲਈ ਟੈਪ ਕਰੋ"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"ਮੋਬਾਈਲ ਨੈੱਟਵਰਕ ਕੋਲ ਇੰਟਰਨੈੱਟ ਤੱਕ ਪਹੁੰਚ ਨਹੀਂ ਹੈ"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"ਨੈੱਟਵਰਕ ਕੋਲ ਇੰਟਰਨੈੱਟ ਤੱਕ ਪਹੁੰਚ ਨਹੀਂ ਹੈ"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"ਨਿੱਜੀ ਡੋਮੇਨ ਨਾਮ ਪ੍ਰਣਾਲੀ (DNS) ਸਰਵਰ \'ਤੇ ਪਹੁੰਚ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕੀ"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ਕੋਲ ਸੀਮਤ ਕਨੈਕਟੀਵਿਟੀ ਹੈ"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"ਫਿਰ ਵੀ ਕਨੈਕਟ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"ਬਦਲਕੇ <xliff:g id="NETWORK_TYPE">%1$s</xliff:g> ਲਿਆਂਦਾ ਗਿਆ"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> ਦੀ ਇੰਟਰਨੈੱਟ \'ਤੇ ਪਹੁੰਚ ਨਾ ਹੋਣ \'ਤੇ ਡੀਵਾਈਸ <xliff:g id="NEW_NETWORK">%1$s</xliff:g> ਦੀ ਵਰਤੋਂ ਕਰਦਾ ਹੈ। ਖਰਚੇ ਲਾਗੂ ਹੋ ਸਕਦੇ ਹਨ।"</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> ਤੋਂ ਬਦਲਕੇ <xliff:g id="NEW_NETWORK">%2$s</xliff:g> \'ਤੇ ਕੀਤਾ ਗਿਆ"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ਕੋਲ ਇੰਟਰਨੈੱਟ ਪਹੁੰਚ ਨਹੀਂ ਹੈ"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"ਵਿਕਲਪਾਂ ਲਈ ਟੈਪ ਕਰੋ"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"ਮੋਬਾਈਲ ਨੈੱਟਵਰਕ ਕੋਲ ਇੰਟਰਨੈੱਟ ਤੱਕ ਪਹੁੰਚ ਨਹੀਂ ਹੈ"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"ਨੈੱਟਵਰਕ ਕੋਲ ਇੰਟਰਨੈੱਟ ਤੱਕ ਪਹੁੰਚ ਨਹੀਂ ਹੈ"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"ਨਿੱਜੀ ਡੋਮੇਨ ਨਾਮ ਪ੍ਰਣਾਲੀ (DNS) ਸਰਵਰ \'ਤੇ ਪਹੁੰਚ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕੀ"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ਕੋਲ ਸੀਮਤ ਕਨੈਕਟੀਵਿਟੀ ਹੈ"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"ਫਿਰ ਵੀ ਕਨੈਕਟ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"ਬਦਲਕੇ <xliff:g id="NETWORK_TYPE">%1$s</xliff:g> ਲਿਆਂਦਾ ਗਿਆ"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> ਦੀ ਇੰਟਰਨੈੱਟ \'ਤੇ ਪਹੁੰਚ ਨਾ ਹੋਣ \'ਤੇ ਡੀਵਾਈਸ <xliff:g id="NEW_NETWORK">%1$s</xliff:g> ਦੀ ਵਰਤੋਂ ਕਰਦਾ ਹੈ। ਖਰਚੇ ਲਾਗੂ ਹੋ ਸਕਦੇ ਹਨ।"</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> ਤੋਂ ਬਦਲਕੇ <xliff:g id="NEW_NETWORK">%2$s</xliff:g> \'ਤੇ ਕੀਤਾ ਗਿਆ"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"ਮੋਬਾਈਲ ਡਾਟਾ"</item>
- <item msgid="6341719431034774569">"ਵਾਈ-ਫਾਈ"</item>
- <item msgid="5081440868800877512">"ਬਲੂਟੁੱਥ"</item>
- <item msgid="1160736166977503463">"ਈਥਰਨੈੱਟ"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"ਮੋਬਾਈਲ ਡਾਟਾ"</item>
+ <item msgid="5624324321165953608">"ਵਾਈ-ਫਾਈ"</item>
+ <item msgid="5667906231066981731">"ਬਲੂਟੁੱਥ"</item>
+ <item msgid="346574747471703768">"ਈਥਰਨੈੱਟ"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"ਕੋਈ ਅਗਿਆਤ ਨੈੱਟਵਰਕ ਦੀ ਕਿਸਮ"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"ਕੋਈ ਅਗਿਆਤ ਨੈੱਟਵਰਕ ਦੀ ਕਿਸਮ"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-pl/strings.xml b/service/ServiceConnectivityResources/res/values-pl/strings.xml
index e6b3a0c..cc84e29 100644
--- a/service/ServiceConnectivityResources/res/values-pl/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-pl/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Zasoby systemowe dotyczące łączności"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Zaloguj się w sieci Wi-Fi"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Zaloguj się do sieci"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Zasoby systemowe dotyczące łączności"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Zaloguj się w sieci Wi-Fi"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Zaloguj się do sieci"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> nie ma dostępu do internetu"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Kliknij, by wyświetlić opcje"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Sieć komórkowa nie ma dostępu do internetu"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Sieć nie ma dostępu do internetu"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Brak dostępu do prywatnego serwera DNS"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ma ograniczoną łączność"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Kliknij, by mimo to nawiązać połączenie"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Zmieniono na połączenie typu <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"Urządzenie korzysta z połączenia typu <xliff:g id="NEW_NETWORK">%1$s</xliff:g>, gdy <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> nie dostępu do internetu. Mogą zostać naliczone opłaty."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Przełączono z połączenia typu <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> na <xliff:g id="NEW_NETWORK">%2$s</xliff:g>."</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> nie ma dostępu do internetu"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Kliknij, by wyświetlić opcje"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Sieć komórkowa nie ma dostępu do internetu"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Sieć nie ma dostępu do internetu"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Brak dostępu do prywatnego serwera DNS"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ma ograniczoną łączność"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Kliknij, by mimo to nawiązać połączenie"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Zmieniono na połączenie typu <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"Urządzenie korzysta z połączenia typu <xliff:g id="NEW_NETWORK">%1$s</xliff:g>, gdy <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> nie dostępu do internetu. Mogą zostać naliczone opłaty."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Przełączono z połączenia typu <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> na <xliff:g id="NEW_NETWORK">%2$s</xliff:g>."</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"mobilna transmisja danych"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"mobilna transmisja danych"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"nieznany typ sieci"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"nieznany typ sieci"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-pt-rBR/strings.xml b/service/ServiceConnectivityResources/res/values-pt-rBR/strings.xml
index f1d0bc0..3c15a76 100644
--- a/service/ServiceConnectivityResources/res/values-pt-rBR/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-pt-rBR/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Recursos de conectividade do sistema"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Fazer login na rede Wi-Fi"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Fazer login na rede"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Recursos de conectividade do sistema"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Fazer login na rede Wi-Fi"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Fazer login na rede"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> não tem acesso à Internet"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Toque para ver opções"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"A rede móvel não tem acesso à Internet"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"A rede não tem acesso à Internet"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Não é possível acessar o servidor DNS privado"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> tem conectividade limitada"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Toque para conectar mesmo assim"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Alternado para <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"O dispositivo usa <xliff:g id="NEW_NETWORK">%1$s</xliff:g> quando <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> não tem acesso à Internet. Esse serviço pode ser cobrado."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Alternado de <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> para <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> não tem acesso à Internet"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Toque para ver opções"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"A rede móvel não tem acesso à Internet"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"A rede não tem acesso à Internet"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Não é possível acessar o servidor DNS privado"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> tem conectividade limitada"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Toque para conectar mesmo assim"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Alternado para <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"O dispositivo usa <xliff:g id="NEW_NETWORK">%1$s</xliff:g> quando <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> não tem acesso à Internet. Esse serviço pode ser cobrado."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Alternado de <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> para <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"dados móveis"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"dados móveis"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"um tipo de rede desconhecido"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"um tipo de rede desconhecido"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-pt-rPT/strings.xml b/service/ServiceConnectivityResources/res/values-pt-rPT/strings.xml
index 163d70b..48dde75 100644
--- a/service/ServiceConnectivityResources/res/values-pt-rPT/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-pt-rPT/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Recursos de conetividade do sistema"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Iniciar sessão na rede Wi-Fi"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Início de sessão na rede"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Recursos de conetividade do sistema"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Iniciar sessão na rede Wi-Fi"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Início de sessão na rede"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> não tem acesso à Internet"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Toque para obter mais opções"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"A rede móvel não tem acesso à Internet"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"A rede não tem acesso à Internet"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Não é possível aceder ao servidor DNS."</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> tem conetividade limitada."</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Toque para ligar mesmo assim."</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Mudou para <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"O dispositivo utiliza <xliff:g id="NEW_NETWORK">%1$s</xliff:g> quando <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> não tem acesso à Internet. Podem aplicar-se custos."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Mudou de <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> para <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> não tem acesso à Internet"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Toque para obter mais opções"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"A rede móvel não tem acesso à Internet"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"A rede não tem acesso à Internet"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Não é possível aceder ao servidor DNS."</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> tem conetividade limitada."</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Toque para ligar mesmo assim."</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Mudou para <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"O dispositivo utiliza <xliff:g id="NEW_NETWORK">%1$s</xliff:g> quando <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> não tem acesso à Internet. Podem aplicar-se custos."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Mudou de <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> para <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"dados móveis"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"dados móveis"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"um tipo de rede desconhecido"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"um tipo de rede desconhecido"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-pt/strings.xml b/service/ServiceConnectivityResources/res/values-pt/strings.xml
index f1d0bc0..3c15a76 100644
--- a/service/ServiceConnectivityResources/res/values-pt/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-pt/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Recursos de conectividade do sistema"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Fazer login na rede Wi-Fi"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Fazer login na rede"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Recursos de conectividade do sistema"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Fazer login na rede Wi-Fi"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Fazer login na rede"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> não tem acesso à Internet"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Toque para ver opções"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"A rede móvel não tem acesso à Internet"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"A rede não tem acesso à Internet"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Não é possível acessar o servidor DNS privado"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> tem conectividade limitada"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Toque para conectar mesmo assim"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Alternado para <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"O dispositivo usa <xliff:g id="NEW_NETWORK">%1$s</xliff:g> quando <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> não tem acesso à Internet. Esse serviço pode ser cobrado."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Alternado de <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> para <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> não tem acesso à Internet"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Toque para ver opções"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"A rede móvel não tem acesso à Internet"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"A rede não tem acesso à Internet"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Não é possível acessar o servidor DNS privado"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> tem conectividade limitada"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Toque para conectar mesmo assim"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Alternado para <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"O dispositivo usa <xliff:g id="NEW_NETWORK">%1$s</xliff:g> quando <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> não tem acesso à Internet. Esse serviço pode ser cobrado."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Alternado de <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> para <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"dados móveis"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"dados móveis"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"um tipo de rede desconhecido"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"um tipo de rede desconhecido"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-ro/strings.xml b/service/ServiceConnectivityResources/res/values-ro/strings.xml
index 221261c..fa5848f 100644
--- a/service/ServiceConnectivityResources/res/values-ro/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-ro/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Resurse pentru conectivitatea sistemului"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Conectați-vă la rețeaua Wi-Fi"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Conectați-vă la rețea"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Resurse pentru conectivitatea sistemului"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Conectați-vă la rețeaua Wi-Fi"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Conectați-vă la rețea"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> nu are acces la internet"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Atingeți pentru opțiuni"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Rețeaua mobilă nu are acces la internet"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Rețeaua nu are acces la internet"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Serverul DNS privat nu poate fi accesat"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> are conectivitate limitată"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Atingeți pentru a vă conecta oricum"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"S-a comutat la <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"Dispozitivul folosește <xliff:g id="NEW_NETWORK">%1$s</xliff:g> când <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> nu are acces la internet. Se pot aplica taxe."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"S-a comutat de la <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> la <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> nu are acces la internet"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Atingeți pentru opțiuni"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Rețeaua mobilă nu are acces la internet"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Rețeaua nu are acces la internet"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Serverul DNS privat nu poate fi accesat"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> are conectivitate limitată"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Atingeți pentru a vă conecta oricum"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"S-a comutat la <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"Dispozitivul folosește <xliff:g id="NEW_NETWORK">%1$s</xliff:g> când <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> nu are acces la internet. Se pot aplica taxe."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"S-a comutat de la <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> la <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"date mobile"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"date mobile"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"un tip de rețea necunoscut"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"un tip de rețea necunoscut"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-ru/strings.xml b/service/ServiceConnectivityResources/res/values-ru/strings.xml
index ba179b7..2e074ed 100644
--- a/service/ServiceConnectivityResources/res/values-ru/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-ru/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"System Connectivity Resources"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Подключение к Wi-Fi"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Регистрация в сети"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"System Connectivity Resources"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Подключение к Wi-Fi"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Регистрация в сети"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"Сеть \"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>\" не подключена к Интернету"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Нажмите, чтобы показать варианты."</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Мобильная сеть не подключена к Интернету"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Сеть не подключена к Интернету"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Доступа к частному DNS-серверу нет."</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"Подключение к сети \"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>\" ограничено"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Нажмите, чтобы подключиться"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Новое подключение: <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"Устройство использует <xliff:g id="NEW_NETWORK">%1$s</xliff:g>, если подключение к сети <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> недоступно. Может взиматься плата за передачу данных."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Устройство отключено от сети <xliff:g id="NEW_NETWORK">%2$s</xliff:g> и теперь использует <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"Сеть \"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>\" не подключена к Интернету"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Нажмите, чтобы показать варианты."</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Мобильная сеть не подключена к Интернету"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Сеть не подключена к Интернету"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Доступа к частному DNS-серверу нет."</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"Подключение к сети \"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>\" ограничено"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Нажмите, чтобы подключиться"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Новое подключение: <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"Устройство использует <xliff:g id="NEW_NETWORK">%1$s</xliff:g>, если подключение к сети <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> недоступно. Может взиматься плата за передачу данных."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Устройство отключено от сети <xliff:g id="NEW_NETWORK">%2$s</xliff:g> и теперь использует <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"мобильный интернет"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"мобильный интернет"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"неизвестный тип сети"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"неизвестный тип сети"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-si/strings.xml b/service/ServiceConnectivityResources/res/values-si/strings.xml
index 1c493a7..a4f720a 100644
--- a/service/ServiceConnectivityResources/res/values-si/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-si/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"පද්ධති සබැඳුම් හැකියා සම්පත්"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Wi-Fi ජාලයට පුරනය වන්න"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"ජාලයට පුරනය වන්න"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"පද්ධති සබැඳුම් හැකියා සම්පත්"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Wi-Fi ජාලයට පුරනය වන්න"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"ජාලයට පුරනය වන්න"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> හට අන්තර්ජාල ප්රවේශය නැත"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"විකල්ප සඳහා තට්ටු කරන්න"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"ජංගම ජාලවලට අන්තර්ජාල ප්රවේශය නැත"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"ජාලයට අන්තර්ජාල ප්රවේශය නැත"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"පුද්ගලික DNS සේවාදායකයට ප්රවේශ වීමට නොහැකිය"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> හට සීමිත සබැඳුම් හැකියාවක් ඇත"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"කෙසේ වෙතත් ඉදිරියට යාමට තට්ටු කරන්න"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g> වෙත මාරු විය"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"උපාංගය <xliff:g id="NEW_NETWORK">%1$s</xliff:g> <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> සඳහා අන්තර්ජාල ප්රවේශය නැති විට භාවිත කරයි. ගාස්තු අදාළ විය හැකිය."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> සිට <xliff:g id="NEW_NETWORK">%2$s</xliff:g> වෙත මාරු විය"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> හට අන්තර්ජාල ප්රවේශය නැත"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"විකල්ප සඳහා තට්ටු කරන්න"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"ජංගම ජාලවලට අන්තර්ජාල ප්රවේශය නැත"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"ජාලයට අන්තර්ජාල ප්රවේශය නැත"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"පුද්ගලික DNS සේවාදායකයට ප්රවේශ වීමට නොහැකිය"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> හට සීමිත සබැඳුම් හැකියාවක් ඇත"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"කෙසේ වෙතත් ඉදිරියට යාමට තට්ටු කරන්න"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g> වෙත මාරු විය"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"උපාංගය <xliff:g id="NEW_NETWORK">%1$s</xliff:g> <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> සඳහා අන්තර්ජාල ප්රවේශය නැති විට භාවිත කරයි. ගාස්තු අදාළ විය හැකිය."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> සිට <xliff:g id="NEW_NETWORK">%2$s</xliff:g> වෙත මාරු විය"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"ජංගම දත්ත"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"බ්ලූටූත්"</item>
- <item msgid="1160736166977503463">"ඊතර්නෙට්"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"ජංගම දත්ත"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"බ්ලූටූත්"</item>
+ <item msgid="346574747471703768">"ඊතර්නෙට්"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"නොදන්නා ජාල වර්ගයකි"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"නොදන්නා ජාල වර්ගයකි"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-sk/strings.xml b/service/ServiceConnectivityResources/res/values-sk/strings.xml
index 1b9313a..432b670 100644
--- a/service/ServiceConnectivityResources/res/values-sk/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-sk/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Zdroje možností pripojenia systému"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Prihlásiť sa do siete Wi‑Fi"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Prihlásenie do siete"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Zdroje možností pripojenia systému"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Prihlásiť sa do siete Wi‑Fi"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Prihlásenie do siete"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> nemá prístup k internetu"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Klepnutím získate možnosti"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Mobilná sieť nemá prístup k internetu"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Sieť nemá prístup k internetu"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"K súkromnému serveru DNS sa nepodarilo získať prístup"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> má obmedzené pripojenie"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Ak sa chcete aj napriek tomu pripojiť, klepnite"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Prepnuté na sieť: <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"Keď <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> nemá prístup k internetu, zariadenie používa <xliff:g id="NEW_NETWORK">%1$s</xliff:g>. Môžu sa účtovať poplatky."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Prepnuté zo siete <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> na sieť <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> nemá prístup k internetu"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Klepnutím získate možnosti"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Mobilná sieť nemá prístup k internetu"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Sieť nemá prístup k internetu"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"K súkromnému serveru DNS sa nepodarilo získať prístup"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> má obmedzené pripojenie"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Ak sa chcete aj napriek tomu pripojiť, klepnite"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Prepnuté na sieť: <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"Keď <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> nemá prístup k internetu, zariadenie používa <xliff:g id="NEW_NETWORK">%1$s</xliff:g>. Môžu sa účtovať poplatky."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Prepnuté zo siete <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> na sieť <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"mobilné dáta"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"mobilné dáta"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"neznámy typ siete"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"neznámy typ siete"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-sl/strings.xml b/service/ServiceConnectivityResources/res/values-sl/strings.xml
index 739fb8e..b727614 100644
--- a/service/ServiceConnectivityResources/res/values-sl/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-sl/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Viri povezljivosti sistema"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Prijavite se v omrežje Wi-Fi"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Prijava v omrežje"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Viri povezljivosti sistema"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Prijavite se v omrežje Wi-Fi"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Prijava v omrežje"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"Omrežje <xliff:g id="NETWORK_SSID">%1$s</xliff:g> nima dostopa do interneta"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Dotaknite se za možnosti"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Mobilno omrežje nima dostopa do interneta"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Omrežje nima dostopa do interneta"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Do zasebnega strežnika DNS ni mogoče dostopati"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"Povezljivost omrežja <xliff:g id="NETWORK_SSID">%1$s</xliff:g> je omejena"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Dotaknite se, da kljub temu vzpostavite povezavo"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Preklopljeno na omrežje vrste <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"Naprava uporabi omrežje vrste <xliff:g id="NEW_NETWORK">%1$s</xliff:g>, ko omrežje vrste <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> nima dostopa do interneta. Prenos podatkov se lahko zaračuna."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Preklopljeno z omrežja vrste <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> na omrežje vrste <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"Omrežje <xliff:g id="NETWORK_SSID">%1$s</xliff:g> nima dostopa do interneta"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Dotaknite se za možnosti"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Mobilno omrežje nima dostopa do interneta"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Omrežje nima dostopa do interneta"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Do zasebnega strežnika DNS ni mogoče dostopati"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"Povezljivost omrežja <xliff:g id="NETWORK_SSID">%1$s</xliff:g> je omejena"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Dotaknite se, da kljub temu vzpostavite povezavo"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Preklopljeno na omrežje vrste <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"Naprava uporabi omrežje vrste <xliff:g id="NEW_NETWORK">%1$s</xliff:g>, ko omrežje vrste <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> nima dostopa do interneta. Prenos podatkov se lahko zaračuna."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Preklopljeno z omrežja vrste <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> na omrežje vrste <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"prenos podatkov v mobilnem omrežju"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"prenos podatkov v mobilnem omrežju"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"neznana vrsta omrežja"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"neznana vrsta omrežja"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-sq/strings.xml b/service/ServiceConnectivityResources/res/values-sq/strings.xml
index cf8cf3b..385c75c 100644
--- a/service/ServiceConnectivityResources/res/values-sq/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-sq/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Burimet e lidhshmërisë së sistemit"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Identifikohu në rrjetin Wi-Fi"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Identifikohu në rrjet"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Burimet e lidhshmërisë së sistemit"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Identifikohu në rrjetin Wi-Fi"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Identifikohu në rrjet"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> nuk ka qasje në internet"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Trokit për opsionet"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Rrjeti celular nuk ka qasje në internet"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Rrjeti nuk ka qasje në internet"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Serveri privat DNS nuk mund të qaset"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ka lidhshmëri të kufizuar"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Trokit për t\'u lidhur gjithsesi"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Kaloi te <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"Pajisja përdor <xliff:g id="NEW_NETWORK">%1$s</xliff:g> kur <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> nuk ka qasje në internet. Mund të zbatohen tarifa."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Kaloi nga <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> te <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> nuk ka qasje në internet"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Trokit për opsionet"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Rrjeti celular nuk ka qasje në internet"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Rrjeti nuk ka qasje në internet"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Serveri privat DNS nuk mund të qaset"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ka lidhshmëri të kufizuar"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Trokit për t\'u lidhur gjithsesi"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Kaloi te <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"Pajisja përdor <xliff:g id="NEW_NETWORK">%1$s</xliff:g> kur <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> nuk ka qasje në internet. Mund të zbatohen tarifa."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Kaloi nga <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> te <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"të dhënat celulare"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Eternet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"të dhënat celulare"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Eternet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"një lloj rrjeti i panjohur"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"një lloj rrjeti i panjohur"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-sr/strings.xml b/service/ServiceConnectivityResources/res/values-sr/strings.xml
index 1f7c95c..928dc79 100644
--- a/service/ServiceConnectivityResources/res/values-sr/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-sr/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Ресурси за повезивање са системом"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Пријављивање на WiFi мрежу"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Пријавите се на мрежу"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Ресурси за повезивање са системом"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Пријављивање на WiFi мрежу"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Пријавите се на мрежу"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> нема приступ интернету"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Додирните за опције"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Мобилна мрежа нема приступ интернету"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Мрежа нема приступ интернету"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Приступ приватном DNS серверу није успео"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> има ограничену везу"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Додирните да бисте се ипак повезали"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Прешли сте на тип мреже <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"Уређај користи тип мреже <xliff:g id="NEW_NETWORK">%1$s</xliff:g> када тип мреже <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> нема приступ интернету. Можда ће се наплаћивати трошкови."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Прешли сте са типа мреже <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> на тип мреже <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> нема приступ интернету"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Додирните за опције"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Мобилна мрежа нема приступ интернету"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Мрежа нема приступ интернету"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Приступ приватном DNS серверу није успео"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> има ограничену везу"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Додирните да бисте се ипак повезали"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Прешли сте на тип мреже <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"Уређај користи тип мреже <xliff:g id="NEW_NETWORK">%1$s</xliff:g> када тип мреже <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> нема приступ интернету. Можда ће се наплаћивати трошкови."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Прешли сте са типа мреже <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> на тип мреже <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"мобилни подаци"</item>
- <item msgid="6341719431034774569">"WiFi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Етернет"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"мобилни подаци"</item>
+ <item msgid="5624324321165953608">"WiFi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Етернет"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"непознат тип мреже"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"непознат тип мреже"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-sv/strings.xml b/service/ServiceConnectivityResources/res/values-sv/strings.xml
index 57e74e9..d714124 100644
--- a/service/ServiceConnectivityResources/res/values-sv/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-sv/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Resurser för systemanslutning"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Logga in på ett wifi-nätverk"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Logga in på nätverket"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Resurser för systemanslutning"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Logga in på ett wifi-nätverk"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Logga in på nätverket"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> har ingen internetanslutning"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Tryck för alternativ"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Mobilnätverket har ingen internetanslutning"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Nätverket har ingen internetanslutning"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Det går inte att komma åt den privata DNS-servern."</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> har begränsad anslutning"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Tryck för att ansluta ändå"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Byte av nätverk till <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"<xliff:g id="NEW_NETWORK">%1$s</xliff:g> används på enheten när det inte finns internetåtkomst via <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g>. Avgifter kan tillkomma."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Byte av nätverk från <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> till <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> har ingen internetanslutning"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Tryck för alternativ"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Mobilnätverket har ingen internetanslutning"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Nätverket har ingen internetanslutning"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Det går inte att komma åt den privata DNS-servern."</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> har begränsad anslutning"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Tryck för att ansluta ändå"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Byte av nätverk till <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"<xliff:g id="NEW_NETWORK">%1$s</xliff:g> används på enheten när det inte finns internetåtkomst via <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g>. Avgifter kan tillkomma."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Byte av nätverk från <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> till <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"mobildata"</item>
- <item msgid="6341719431034774569">"Wifi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"mobildata"</item>
+ <item msgid="5624324321165953608">"Wifi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"en okänd nätverkstyp"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"en okänd nätverkstyp"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-sw/strings.xml b/service/ServiceConnectivityResources/res/values-sw/strings.xml
index 5c4d594..15d6cab 100644
--- a/service/ServiceConnectivityResources/res/values-sw/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-sw/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Nyenzo za Muunganisho wa Mfumo"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Ingia kwa mtandao wa Wi-Fi"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Ingia katika mtandao"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Nyenzo za Muunganisho wa Mfumo"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Ingia kwa mtandao wa Wi-Fi"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Ingia katika mtandao"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> haina uwezo wa kufikia intaneti"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Gusa ili upate chaguo"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Mtandao wa simu hauna uwezo wa kufikia intaneti"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Mtandao hauna uwezo wa kufikia intaneti"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Seva ya faragha ya DNS haiwezi kufikiwa"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ina muunganisho unaofikia huduma chache."</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Gusa ili uunganishe tu"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Sasa inatumia <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"Kifaa hutumia <xliff:g id="NEW_NETWORK">%1$s</xliff:g> wakati <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> haina intaneti. Huenda ukalipishwa."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Imebadilisha mtandao kutoka <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> na sasa inatumia <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> haina uwezo wa kufikia intaneti"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Gusa ili upate chaguo"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Mtandao wa simu hauna uwezo wa kufikia intaneti"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Mtandao hauna uwezo wa kufikia intaneti"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Seva ya faragha ya DNS haiwezi kufikiwa"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ina muunganisho unaofikia huduma chache."</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Gusa ili uunganishe tu"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Sasa inatumia <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"Kifaa hutumia <xliff:g id="NEW_NETWORK">%1$s</xliff:g> wakati <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> haina intaneti. Huenda ukalipishwa."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Imebadilisha mtandao kutoka <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> na sasa inatumia <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"data ya mtandao wa simu"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethaneti"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"data ya mtandao wa simu"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethaneti"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"aina ya mtandao isiyojulikana"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"aina ya mtandao isiyojulikana"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-ta/strings.xml b/service/ServiceConnectivityResources/res/values-ta/strings.xml
index 90f89c9..9850a35 100644
--- a/service/ServiceConnectivityResources/res/values-ta/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-ta/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"சிஸ்டம் இணைப்பு மூலங்கள்"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"வைஃபை நெட்வொர்க்கில் உள்நுழையவும்"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"நெட்வொர்க்கில் உள்நுழையவும்"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"சிஸ்டம் இணைப்பு தகவல்கள்"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"வைஃபை நெட்வொர்க்கில் உள்நுழையவும்"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"நெட்வொர்க்கில் உள்நுழையவும்"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> நெட்வொர்க்கிற்கு இணைய அணுகல் இல்லை"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"விருப்பங்களுக்கு, தட்டவும்"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"மொபைல் நெட்வொர்க்கிற்கு இணைய அணுகல் இல்லை"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"நெட்வொர்க்கிற்கு இணைய அணுகல் இல்லை"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"தனிப்பட்ட DNS சேவையகத்தை அணுக இயலாது"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> வரம்பிற்கு உட்பட்ட இணைப்புநிலையைக் கொண்டுள்ளது"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"எப்படியேனும் இணைப்பதற்குத் தட்டவும்"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g>க்கு மாற்றப்பட்டது"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> நெட்வொர்க்கில் இண்டர்நெட் அணுகல் இல்லாததால், சாதனமானது <xliff:g id="NEW_NETWORK">%1$s</xliff:g> நெட்வொர்க்கைப் பயன்படுத்துகிறது. கட்டணங்கள் விதிக்கப்படலாம்."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> இலிருந்து <xliff:g id="NEW_NETWORK">%2$s</xliff:g>க்கு மாற்றப்பட்டது"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> நெட்வொர்க்கிற்கு இணைய அணுகல் இல்லை"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"விருப்பங்களுக்கு, தட்டவும்"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"மொபைல் நெட்வொர்க்கிற்கு இணைய அணுகல் இல்லை"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"நெட்வொர்க்கிற்கு இணைய அணுகல் இல்லை"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"தனிப்பட்ட DNS சேவையகத்தை அணுக இயலாது"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> வரம்பிற்கு உட்பட்ட இணைப்புநிலையைக் கொண்டுள்ளது"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"எப்படியேனும் இணைப்பதற்குத் தட்டவும்"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g>க்கு மாற்றப்பட்டது"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> நெட்வொர்க்கில் இண்டர்நெட் அணுகல் இல்லாததால், சாதனமானது <xliff:g id="NEW_NETWORK">%1$s</xliff:g> நெட்வொர்க்கைப் பயன்படுத்துகிறது. கட்டணங்கள் விதிக்கப்படலாம்."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> இலிருந்து <xliff:g id="NEW_NETWORK">%2$s</xliff:g>க்கு மாற்றப்பட்டது"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"மொபைல் டேட்டா"</item>
- <item msgid="6341719431034774569">"வைஃபை"</item>
- <item msgid="5081440868800877512">"புளூடூத்"</item>
- <item msgid="1160736166977503463">"ஈதர்நெட்"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"மொபைல் டேட்டா"</item>
+ <item msgid="5624324321165953608">"வைஃபை"</item>
+ <item msgid="5667906231066981731">"புளூடூத்"</item>
+ <item msgid="346574747471703768">"ஈதர்நெட்"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"தெரியாத நெட்வொர்க் வகை"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"தெரியாத நெட்வொர்க் வகை"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-te/strings.xml b/service/ServiceConnectivityResources/res/values-te/strings.xml
index c69b599..f7182a8 100644
--- a/service/ServiceConnectivityResources/res/values-te/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-te/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"సిస్టమ్ కనెక్టివిటీ రిసోర్స్లు"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Wi-Fi నెట్వర్క్కి సైన్ ఇన్ చేయండి"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"నెట్వర్క్కి సైన్ ఇన్ చేయండి"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"సిస్టమ్ కనెక్టివిటీ రిసోర్స్లు"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Wi-Fi నెట్వర్క్కి సైన్ ఇన్ చేయండి"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"నెట్వర్క్కి సైన్ ఇన్ చేయండి"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>కి ఇంటర్నెట్ యాక్సెస్ లేదు"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"ఎంపికల కోసం నొక్కండి"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"మొబైల్ నెట్వర్క్కు ఇంటర్నెట్ యాక్సెస్ లేదు"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"నెట్వర్క్కు ఇంటర్నెట్ యాక్సెస్ లేదు"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"ప్రైవేట్ DNS సర్వర్ను యాక్సెస్ చేయడం సాధ్యపడదు"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> పరిమిత కనెక్టివిటీని కలిగి ఉంది"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"ఏదేమైనా కనెక్ట్ చేయడానికి నొక్కండి"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g>కి మార్చబడింది"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"పరికరం <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g>కి ఇంటర్నెట్ యాక్సెస్ లేనప్పుడు <xliff:g id="NEW_NETWORK">%1$s</xliff:g>ని ఉపయోగిస్తుంది. ఛార్జీలు వర్తించవచ్చు."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> నుండి <xliff:g id="NEW_NETWORK">%2$s</xliff:g>కి మార్చబడింది"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>కి ఇంటర్నెట్ యాక్సెస్ లేదు"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"ఎంపికల కోసం నొక్కండి"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"మొబైల్ నెట్వర్క్కు ఇంటర్నెట్ యాక్సెస్ లేదు"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"నెట్వర్క్కు ఇంటర్నెట్ యాక్సెస్ లేదు"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"ప్రైవేట్ DNS సర్వర్ను యాక్సెస్ చేయడం సాధ్యపడదు"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> పరిమిత కనెక్టివిటీని కలిగి ఉంది"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"ఏదేమైనా కనెక్ట్ చేయడానికి నొక్కండి"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g>కి మార్చబడింది"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"పరికరం <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g>కి ఇంటర్నెట్ యాక్సెస్ లేనప్పుడు <xliff:g id="NEW_NETWORK">%1$s</xliff:g>ని ఉపయోగిస్తుంది. ఛార్జీలు వర్తించవచ్చు."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> నుండి <xliff:g id="NEW_NETWORK">%2$s</xliff:g>కి మార్చబడింది"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"మొబైల్ డేటా"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"బ్లూటూత్"</item>
- <item msgid="1160736166977503463">"ఈథర్నెట్"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"మొబైల్ డేటా"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"బ్లూటూత్"</item>
+ <item msgid="346574747471703768">"ఈథర్నెట్"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"తెలియని నెట్వర్క్ రకం"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"తెలియని నెట్వర్క్ రకం"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-th/strings.xml b/service/ServiceConnectivityResources/res/values-th/strings.xml
index eee5a35..7049309 100644
--- a/service/ServiceConnectivityResources/res/values-th/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-th/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"ทรัพยากรการเชื่อมต่อของระบบ"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"ลงชื่อเข้าใช้เครือข่าย WiFi"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"ลงชื่อเข้าใช้เครือข่าย"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"ทรัพยากรการเชื่อมต่อของระบบ"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"ลงชื่อเข้าใช้เครือข่าย WiFi"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"ลงชื่อเข้าใช้เครือข่าย"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> เข้าถึงอินเทอร์เน็ตไม่ได้"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"แตะเพื่อดูตัวเลือก"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"เครือข่ายมือถือไม่มีการเข้าถึงอินเทอร์เน็ต"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"เครือข่ายไม่มีการเข้าถึงอินเทอร์เน็ต"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"เข้าถึงเซิร์ฟเวอร์ DNS ไม่ได้"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> มีการเชื่อมต่อจำกัด"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"แตะเพื่อเชื่อมต่อ"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"เปลี่ยนเป็น <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"อุปกรณ์จะใช้ <xliff:g id="NEW_NETWORK">%1$s</xliff:g> เมื่อ <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> เข้าถึงอินเทอร์เน็ตไม่ได้ โดยอาจมีค่าบริการ"</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"เปลี่ยนจาก <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> เป็น <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> เข้าถึงอินเทอร์เน็ตไม่ได้"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"แตะเพื่อดูตัวเลือก"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"เครือข่ายมือถือไม่มีการเข้าถึงอินเทอร์เน็ต"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"เครือข่ายไม่มีการเข้าถึงอินเทอร์เน็ต"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"เข้าถึงเซิร์ฟเวอร์ DNS ไม่ได้"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> มีการเชื่อมต่อจำกัด"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"แตะเพื่อเชื่อมต่อ"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"เปลี่ยนเป็น <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"อุปกรณ์จะใช้ <xliff:g id="NEW_NETWORK">%1$s</xliff:g> เมื่อ <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> เข้าถึงอินเทอร์เน็ตไม่ได้ โดยอาจมีค่าบริการ"</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"เปลี่ยนจาก <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> เป็น <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"อินเทอร์เน็ตมือถือ"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"บลูทูธ"</item>
- <item msgid="1160736166977503463">"อีเทอร์เน็ต"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"อินเทอร์เน็ตมือถือ"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"บลูทูธ"</item>
+ <item msgid="346574747471703768">"อีเทอร์เน็ต"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"ประเภทเครือข่ายที่ไม่รู้จัก"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"ประเภทเครือข่ายที่ไม่รู้จัก"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-tl/strings.xml b/service/ServiceConnectivityResources/res/values-tl/strings.xml
index 8d665fe..c866fd4 100644
--- a/service/ServiceConnectivityResources/res/values-tl/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-tl/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Mga Resource ng Pagkakonekta ng System"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Mag-sign in sa Wi-Fi network"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Mag-sign in sa network"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Mga Resource ng Pagkakonekta ng System"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Mag-sign in sa Wi-Fi network"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Mag-sign in sa network"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"Walang access sa internet ang <xliff:g id="NETWORK_SSID">%1$s</xliff:g>"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"I-tap para sa mga opsyon"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Walang access sa internet ang mobile network"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Walang access sa internet ang network"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Hindi ma-access ang pribadong DNS server"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"Limitado ang koneksyon ng <xliff:g id="NETWORK_SSID">%1$s</xliff:g>"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"I-tap para kumonekta pa rin"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Lumipat sa <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"Ginagamit ng device ang <xliff:g id="NEW_NETWORK">%1$s</xliff:g> kapag walang access sa internet ang <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g>. Maaaring may mga malapat na singilin."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Lumipat sa <xliff:g id="NEW_NETWORK">%2$s</xliff:g> mula sa <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"Walang access sa internet ang <xliff:g id="NETWORK_SSID">%1$s</xliff:g>"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"I-tap para sa mga opsyon"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Walang access sa internet ang mobile network"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Walang access sa internet ang network"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Hindi ma-access ang pribadong DNS server"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"Limitado ang koneksyon ng <xliff:g id="NETWORK_SSID">%1$s</xliff:g>"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"I-tap para kumonekta pa rin"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Lumipat sa <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"Ginagamit ng device ang <xliff:g id="NEW_NETWORK">%1$s</xliff:g> kapag walang access sa internet ang <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g>. Maaaring may mga malapat na singilin."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Lumipat sa <xliff:g id="NEW_NETWORK">%2$s</xliff:g> mula sa <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"mobile data"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"mobile data"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"isang hindi kilalang uri ng network"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"isang hindi kilalang uri ng network"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-tr/strings.xml b/service/ServiceConnectivityResources/res/values-tr/strings.xml
index cfb7632..c4930a8 100644
--- a/service/ServiceConnectivityResources/res/values-tr/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-tr/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Sistem Bağlantı Kaynakları"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Kablosuz ağda oturum açın"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Ağda oturum açın"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Sistem Bağlantı Kaynakları"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Kablosuz ağda oturum açın"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Ağda oturum açın"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ağının internet bağlantısı yok"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Seçenekler için dokunun"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Mobil ağın internet bağlantısı yok"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Ağın internet bağlantısı yok"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Gizli DNS sunucusuna erişilemiyor"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> sınırlı bağlantıya sahip"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Yine de bağlanmak için dokunun"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g> ağına geçildi"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> ağının internet erişimi olmadığında cihaz <xliff:g id="NEW_NETWORK">%1$s</xliff:g> ağını kullanır. Bunun için ödeme alınabilir."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> ağından <xliff:g id="NEW_NETWORK">%2$s</xliff:g> ağına geçildi"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ağının internet bağlantısı yok"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Seçenekler için dokunun"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Mobil ağın internet bağlantısı yok"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Ağın internet bağlantısı yok"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Gizli DNS sunucusuna erişilemiyor"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> sınırlı bağlantıya sahip"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Yine de bağlanmak için dokunun"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g> ağına geçildi"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> ağının internet erişimi olmadığında cihaz <xliff:g id="NEW_NETWORK">%1$s</xliff:g> ağını kullanır. Bunun için ödeme alınabilir."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> ağından <xliff:g id="NEW_NETWORK">%2$s</xliff:g> ağına geçildi"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"mobil veri"</item>
- <item msgid="6341719431034774569">"Kablosuz"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"mobil veri"</item>
+ <item msgid="5624324321165953608">"Kablosuz"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"bilinmeyen ağ türü"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"bilinmeyen ağ türü"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-uk/strings.xml b/service/ServiceConnectivityResources/res/values-uk/strings.xml
index c5da746..8811263 100644
--- a/service/ServiceConnectivityResources/res/values-uk/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-uk/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Ресурси для підключення системи"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Вхід у мережу Wi-Fi"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Вхід у мережу"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Ресурси для підключення системи"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Вхід у мережу Wi-Fi"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Вхід у мережу"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"Мережа <xliff:g id="NETWORK_SSID">%1$s</xliff:g> не має доступу до Інтернету"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Торкніться, щоб відкрити опції"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Мобільна мережа не має доступу до Інтернету"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Мережа не має доступу до Інтернету"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Немає доступу до приватного DNS-сервера"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"Підключення до мережі <xliff:g id="NETWORK_SSID">%1$s</xliff:g> обмежено"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Натисніть, щоб усе одно підключитися"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Пристрій перейшов на мережу <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"Коли мережа <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> не має доступу до Інтернету, використовується <xliff:g id="NEW_NETWORK">%1$s</xliff:g>. Може стягуватися плата."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Пристрій перейшов з мережі <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> на мережу <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"Мережа <xliff:g id="NETWORK_SSID">%1$s</xliff:g> не має доступу до Інтернету"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Торкніться, щоб відкрити опції"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Мобільна мережа не має доступу до Інтернету"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Мережа не має доступу до Інтернету"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Немає доступу до приватного DNS-сервера"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"Підключення до мережі <xliff:g id="NETWORK_SSID">%1$s</xliff:g> обмежено"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Натисніть, щоб усе одно підключитися"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Пристрій перейшов на мережу <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"Коли мережа <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> не має доступу до Інтернету, використовується <xliff:g id="NEW_NETWORK">%1$s</xliff:g>. Може стягуватися плата."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Пристрій перейшов з мережі <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> на мережу <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"мобільний Інтернет"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"мобільний Інтернет"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"невідомий тип мережі"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"невідомий тип мережі"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-ur/strings.xml b/service/ServiceConnectivityResources/res/values-ur/strings.xml
index bd2a228..8f9656c 100644
--- a/service/ServiceConnectivityResources/res/values-ur/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-ur/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"سسٹم کنیکٹوٹی کے وسائل"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Wi-Fi نیٹ ورک میں سائن ان کریں"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"نیٹ ورک میں سائن ان کریں"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"سسٹم کنیکٹوٹی کے وسائل"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Wi-Fi نیٹ ورک میں سائن ان کریں"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"نیٹ ورک میں سائن ان کریں"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> کو انٹرنیٹ تک رسائی حاصل نہیں ہے"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"اختیارات کیلئے تھپتھپائیں"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"موبائل نیٹ ورک کو انٹرنیٹ تک رسائی حاصل نہیں ہے"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"نیٹ ورک کو انٹرنیٹ تک رسائی حاصل نہیں ہے"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"نجی DNS سرور تک رسائی حاصل نہیں کی جا سکی"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> کی کنیکٹوٹی محدود ہے"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"بہر حال منسلک کرنے کے لیے تھپتھپائیں"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g> پر سوئچ ہو گیا"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"جب <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> کو انٹرنیٹ تک رسائی نہیں ہوتی ہے تو آلہ <xliff:g id="NEW_NETWORK">%1$s</xliff:g> کا استعمال کرتا ہے۔ چارجز لاگو ہو سکتے ہیں۔"</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> سے <xliff:g id="NEW_NETWORK">%2$s</xliff:g> پر سوئچ ہو گیا"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> کو انٹرنیٹ تک رسائی حاصل نہیں ہے"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"اختیارات کیلئے تھپتھپائیں"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"موبائل نیٹ ورک کو انٹرنیٹ تک رسائی حاصل نہیں ہے"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"نیٹ ورک کو انٹرنیٹ تک رسائی حاصل نہیں ہے"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"نجی DNS سرور تک رسائی حاصل نہیں کی جا سکی"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> کی کنیکٹوٹی محدود ہے"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"بہر حال منسلک کرنے کے لیے تھپتھپائیں"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"<xliff:g id="NETWORK_TYPE">%1$s</xliff:g> پر سوئچ ہو گیا"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"جب <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> کو انٹرنیٹ تک رسائی نہیں ہوتی ہے تو آلہ <xliff:g id="NEW_NETWORK">%1$s</xliff:g> کا استعمال کرتا ہے۔ چارجز لاگو ہو سکتے ہیں۔"</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> سے <xliff:g id="NEW_NETWORK">%2$s</xliff:g> پر سوئچ ہو گیا"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"موبائل ڈیٹا"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"بلوٹوتھ"</item>
- <item msgid="1160736166977503463">"ایتھرنیٹ"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"موبائل ڈیٹا"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"بلوٹوتھ"</item>
+ <item msgid="346574747471703768">"ایتھرنیٹ"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"نامعلوم نیٹ ورک کی قسم"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"نامعلوم نیٹ ورک کی قسم"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-uz/strings.xml b/service/ServiceConnectivityResources/res/values-uz/strings.xml
index 567aa88..d7285ad 100644
--- a/service/ServiceConnectivityResources/res/values-uz/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-uz/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Tizim aloqa resurslari"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Wi-Fi tarmoqqa kirish"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Tarmoqqa kirish"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Tizim aloqa resurslari"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Wi-Fi tarmoqqa kirish"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Tarmoqqa kirish"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> nomli tarmoqda internetga ruxsati yoʻq"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Variantlarni ko‘rsatish uchun bosing"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Mobil tarmoq internetga ulanmagan"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Tarmoq internetga ulanmagan"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Xususiy DNS server ishlamayapti"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> nomli tarmoqda aloqa cheklangan"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Baribir ulash uchun bosing"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Yangi ulanish: <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"Agar <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> tarmoqda internet uzilsa, qurilma <xliff:g id="NEW_NETWORK">%1$s</xliff:g>ga ulanadi. Sarflangan trafik uchun haq olinishi mumkin."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> tarmog‘idan <xliff:g id="NEW_NETWORK">%2$s</xliff:g> tarmog‘iga o‘tildi"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> nomli tarmoqda internetga ruxsati yoʻq"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Variantlarni ko‘rsatish uchun bosing"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Mobil tarmoq internetga ulanmagan"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Tarmoq internetga ulanmagan"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Xususiy DNS server ishlamayapti"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> nomli tarmoqda aloqa cheklangan"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Baribir ulash uchun bosing"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Yangi ulanish: <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"Agar <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> tarmoqda internet uzilsa, qurilma <xliff:g id="NEW_NETWORK">%1$s</xliff:g>ga ulanadi. Sarflangan trafik uchun haq olinishi mumkin."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> tarmog‘idan <xliff:g id="NEW_NETWORK">%2$s</xliff:g> tarmog‘iga o‘tildi"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"mobil internet"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"mobil internet"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"nomaʼlum tarmoq turi"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"nomaʼlum tarmoq turi"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-vi/strings.xml b/service/ServiceConnectivityResources/res/values-vi/strings.xml
index 590b388..239fb81 100644
--- a/service/ServiceConnectivityResources/res/values-vi/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-vi/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Tài nguyên kết nối hệ thống"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Đăng nhập vào mạng Wi-Fi"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Đăng nhập vào mạng"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Tài nguyên kết nối hệ thống"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Đăng nhập vào mạng Wi-Fi"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Đăng nhập vào mạng"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> không có quyền truy cập Internet"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Nhấn để biết tùy chọn"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Mạng di động không có quyền truy cập Internet"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Mạng không có quyền truy cập Internet"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Không thể truy cập máy chủ DNS riêng tư"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> có khả năng kết nối giới hạn"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Nhấn để tiếp tục kết nối"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Đã chuyển sang <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"Thiết bị sử dụng <xliff:g id="NEW_NETWORK">%1$s</xliff:g> khi <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> không có quyền truy cập Internet. Bạn có thể phải trả phí."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Đã chuyển từ <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> sang <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> không có quyền truy cập Internet"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Nhấn để biết tùy chọn"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Mạng di động không có quyền truy cập Internet"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Mạng không có quyền truy cập Internet"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Không thể truy cập máy chủ DNS riêng tư"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> có khả năng kết nối giới hạn"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Nhấn để tiếp tục kết nối"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Đã chuyển sang <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"Thiết bị sử dụng <xliff:g id="NEW_NETWORK">%1$s</xliff:g> khi <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> không có quyền truy cập Internet. Bạn có thể phải trả phí."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Đã chuyển từ <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> sang <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"dữ liệu di động"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"Bluetooth"</item>
- <item msgid="1160736166977503463">"Ethernet"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"dữ liệu di động"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="346574747471703768">"Ethernet"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"loại mạng không xác định"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"loại mạng không xác định"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-zh-rCN/strings.xml b/service/ServiceConnectivityResources/res/values-zh-rCN/strings.xml
index 9d6cff9..e318c0b 100644
--- a/service/ServiceConnectivityResources/res/values-zh-rCN/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-zh-rCN/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"系统网络连接资源"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"登录到WLAN网络"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"登录到网络"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"系统网络连接资源"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"登录到WLAN网络"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"登录到网络"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> 无法访问互联网"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"点按即可查看相关选项"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"此移动网络无法访问互联网"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"此网络无法访问互联网"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"无法访问私人 DNS 服务器"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> 的连接受限"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"点按即可继续连接"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"已切换至<xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"设备会在<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g>无法访问互联网时使用<xliff:g id="NEW_NETWORK">%1$s</xliff:g>(可能需要支付相应的费用)。"</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"已从<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g>切换至<xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> 无法访问互联网"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"点按即可查看相关选项"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"此移动网络无法访问互联网"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"此网络无法访问互联网"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"无法访问私人 DNS 服务器"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> 的连接受限"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"点按即可继续连接"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"已切换至<xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"设备会在<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g>无法访问互联网时使用<xliff:g id="NEW_NETWORK">%1$s</xliff:g>(可能需要支付相应的费用)。"</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"已从<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g>切换至<xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"移动数据"</item>
- <item msgid="6341719431034774569">"WLAN"</item>
- <item msgid="5081440868800877512">"蓝牙"</item>
- <item msgid="1160736166977503463">"以太网"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"移动数据"</item>
+ <item msgid="5624324321165953608">"WLAN"</item>
+ <item msgid="5667906231066981731">"蓝牙"</item>
+ <item msgid="346574747471703768">"以太网"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"未知网络类型"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"未知网络类型"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-zh-rHK/strings.xml b/service/ServiceConnectivityResources/res/values-zh-rHK/strings.xml
index c84241c..af3dccd 100644
--- a/service/ServiceConnectivityResources/res/values-zh-rHK/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-zh-rHK/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"系統連線資源"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"登入 Wi-Fi 網絡"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"登入網絡"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"系統連線資源"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"登入 Wi-Fi 網絡"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"登入網絡"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>未有連接至互聯網"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"輕按即可查看選項"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"流動網絡並未連接互聯網"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"網絡並未連接互聯網"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"無法存取私人 DNS 伺服器"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>連線受限"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"仍要輕按以連結至此網絡"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"已切換至<xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"裝置會在 <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> 無法連線至互聯網時使用<xliff:g id="NEW_NETWORK">%1$s</xliff:g> (可能需要支付相關費用)。"</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"已從<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g>切換至<xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>未有連接至互聯網"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"輕按即可查看選項"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"流動網絡並未連接互聯網"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"網絡並未連接互聯網"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"無法存取私人 DNS 伺服器"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>連線受限"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"仍要輕按以連結至此網絡"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"已切換至<xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"裝置會在 <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> 無法連線至互聯網時使用<xliff:g id="NEW_NETWORK">%1$s</xliff:g> (可能需要支付相關費用)。"</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"已從<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g>切換至<xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"流動數據"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"藍牙"</item>
- <item msgid="1160736166977503463">"以太網絡"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"流動數據"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"藍牙"</item>
+ <item msgid="346574747471703768">"以太網絡"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"不明網絡類型"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"不明網絡類型"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-zh-rTW/strings.xml b/service/ServiceConnectivityResources/res/values-zh-rTW/strings.xml
index 07540d1..6441707 100644
--- a/service/ServiceConnectivityResources/res/values-zh-rTW/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-zh-rTW/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"系統連線資源"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"登入 Wi-Fi 網路"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"登入網路"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"系統連線資源"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"登入 Wi-Fi 網路"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"登入網路"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> 沒有網際網路連線"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"輕觸即可查看選項"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"這個行動網路沒有網際網路連線"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"這個網路沒有網際網路連線"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"無法存取私人 DNS 伺服器"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> 的連線能力受限"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"輕觸即可繼續連線"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"已切換至<xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"裝置會在無法連上「<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g>」時切換至「<xliff:g id="NEW_NETWORK">%1$s</xliff:g>」(可能需要支付相關費用)。"</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"已從 <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> 切換至<xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> 沒有網際網路連線"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"輕觸即可查看選項"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"這個行動網路沒有網際網路連線"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"這個網路沒有網際網路連線"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"無法存取私人 DNS 伺服器"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> 的連線能力受限"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"輕觸即可繼續連線"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"已切換至<xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"裝置會在無法連上「<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g>」時切換至「<xliff:g id="NEW_NETWORK">%1$s</xliff:g>」(可能需要支付相關費用)。"</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"已從 <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> 切換至<xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"行動數據"</item>
- <item msgid="6341719431034774569">"Wi-Fi"</item>
- <item msgid="5081440868800877512">"藍牙"</item>
- <item msgid="1160736166977503463">"乙太網路"</item>
- <item msgid="7347618872551558605">"VPN"</item>
+ <item msgid="3004933964374161223">"行動數據"</item>
+ <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5667906231066981731">"藍牙"</item>
+ <item msgid="346574747471703768">"乙太網路"</item>
+ <item msgid="5734728378097476003">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"不明的網路類型"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"不明的網路類型"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values-zu/strings.xml b/service/ServiceConnectivityResources/res/values-zu/strings.xml
index 19f390b..b59f0d1 100644
--- a/service/ServiceConnectivityResources/res/values-zu/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-zu/strings.xml
@@ -17,27 +17,27 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="connectivityResourcesAppLabel" msgid="8294935652079168395">"Izinsiza Zokuxhumeka Zesistimu"</string>
- <string name="wifi_available_sign_in" msgid="5254156478006453593">"Ngena ngemvume kunethiwekhi ye-Wi-Fi"</string>
- <string name="network_available_sign_in" msgid="7794369329839408792">"Ngena ngemvume kunethiwekhi"</string>
- <!-- no translation found for network_available_sign_in_detailed (3643910593681893097) -->
+ <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Izinsiza Zokuxhumeka Zesistimu"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Ngena ngemvume kunethiwekhi ye-Wi-Fi"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Ngena ngemvume kunethiwekhi"</string>
+ <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="3961697321010262514">"I-<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ayinakho ukufinyelela kwe-inthanethi"</string>
- <string name="wifi_no_internet_detailed" msgid="1229067002306296104">"Thepha ukuze uthole izinketho"</string>
- <string name="mobile_no_internet" msgid="2262524005014119639">"Inethiwekhi yeselula ayinakho ukufinyelela kwe-inthanethi"</string>
- <string name="other_networks_no_internet" msgid="8226004998719563755">"Inethiwekhi ayinakho ukufinyelela kwenethiwekhi"</string>
- <string name="private_dns_broken_detailed" msgid="3537567373166991809">"Iseva eyimfihlo ye-DNS ayikwazi ukufinyelelwa"</string>
- <string name="network_partial_connectivity" msgid="5957065286265771273">"I-<xliff:g id="NETWORK_SSID">%1$s</xliff:g> inokuxhumeka okukhawulelwe"</string>
- <string name="network_partial_connectivity_detailed" msgid="6975752539442533034">"Thepha ukuze uxhume noma kunjalo"</string>
- <string name="network_switch_metered" msgid="2814798852883117872">"Kushintshelwe ku-<xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="605546931076348229">"Idivayisi isebenzisa i-<xliff:g id="NEW_NETWORK">%1$s</xliff:g> uma i-<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> inganakho ukufinyelela kwe-inthanethi. Kungasebenza izindleko."</string>
- <string name="network_switch_metered_toast" msgid="8831325515040986641">"Kushintshelewe kusuka ku-<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> kuya ku-<xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"I-<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ayinakho ukufinyelela kwe-inthanethi"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Thepha ukuze uthole izinketho"</string>
+ <string name="mobile_no_internet" msgid="4087718456753201450">"Inethiwekhi yeselula ayinakho ukufinyelela kwe-inthanethi"</string>
+ <string name="other_networks_no_internet" msgid="5693932964749676542">"Inethiwekhi ayinakho ukufinyelela kwenethiwekhi"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Iseva eyimfihlo ye-DNS ayikwazi ukufinyelelwa"</string>
+ <string name="network_partial_connectivity" msgid="5549503845834993258">"I-<xliff:g id="NETWORK_SSID">%1$s</xliff:g> inokuxhumeka okukhawulelwe"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Thepha ukuze uxhume noma kunjalo"</string>
+ <string name="network_switch_metered" msgid="5016937523571166319">"Kushintshelwe ku-<xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1257300152739542096">"Idivayisi isebenzisa i-<xliff:g id="NEW_NETWORK">%1$s</xliff:g> uma i-<xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> inganakho ukufinyelela kwe-inthanethi. Kungasebenza izindleko."</string>
+ <string name="network_switch_metered_toast" msgid="70691146054130335">"Kushintshelewe kusuka ku-<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> kuya ku-<xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="5454013645032700715">"idatha yeselula"</item>
- <item msgid="6341719431034774569">"I-Wi-Fi"</item>
- <item msgid="5081440868800877512">"I-Bluetooth"</item>
- <item msgid="1160736166977503463">"I-Ethernet"</item>
- <item msgid="7347618872551558605">"I-VPN"</item>
+ <item msgid="3004933964374161223">"idatha yeselula"</item>
+ <item msgid="5624324321165953608">"I-Wi-Fi"</item>
+ <item msgid="5667906231066981731">"I-Bluetooth"</item>
+ <item msgid="346574747471703768">"I-Ethernet"</item>
+ <item msgid="5734728378097476003">"I-VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="7826330274368951740">"uhlobo olungaziwa lwenethiwekhi"</string>
+ <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"uhlobo olungaziwa lwenethiwekhi"</string>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values/config.xml b/service/ServiceConnectivityResources/res/values/config.xml
index 1af00c7..81782f9 100644
--- a/service/ServiceConnectivityResources/res/values/config.xml
+++ b/service/ServiceConnectivityResources/res/values/config.xml
@@ -176,7 +176,7 @@
NetworkMonitor will continue to attempt validation, and if it fails after this time has passed,
the network will be marked unvalidated.
- Only supported up to S. On T+, the Wi-Fi code should use destroyAndAwaitReplacement in order
+ Only supported up to S. On T+, the Wi-Fi code should use unregisterAfterReplacement in order
to ensure that apps see the network disconnect and reconnect. -->
<integer translatable="false" name="config_validationFailureAfterRoamIgnoreTimeMillis">-1</integer>
</resources>
diff --git a/service/aidl_api/connectivity_native_aidl_interface/1/.hash b/service/aidl_api/connectivity_native_aidl_interface/1/.hash
new file mode 100644
index 0000000..4625b4b
--- /dev/null
+++ b/service/aidl_api/connectivity_native_aidl_interface/1/.hash
@@ -0,0 +1 @@
+037b467eb02b172a3161e11bbc3dd691aebb5fce
diff --git a/service/aidl_api/connectivity_native_aidl_interface/1/android/net/connectivity/aidl/ConnectivityNative.aidl b/service/aidl_api/connectivity_native_aidl_interface/1/android/net/connectivity/aidl/ConnectivityNative.aidl
new file mode 100644
index 0000000..b3985a4
--- /dev/null
+++ b/service/aidl_api/connectivity_native_aidl_interface/1/android/net/connectivity/aidl/ConnectivityNative.aidl
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2022, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file 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.connectivity.aidl;
+interface ConnectivityNative {
+ void blockPortForBind(in int port);
+ void unblockPortForBind(in int port);
+ void unblockAllPortsForBind();
+ int[] getPortsBlockedForBind();
+}
diff --git a/service/aidl_api/connectivity_native_aidl_interface/current/android/net/connectivity/aidl/ConnectivityNative.aidl b/service/aidl_api/connectivity_native_aidl_interface/current/android/net/connectivity/aidl/ConnectivityNative.aidl
new file mode 100644
index 0000000..b3985a4
--- /dev/null
+++ b/service/aidl_api/connectivity_native_aidl_interface/current/android/net/connectivity/aidl/ConnectivityNative.aidl
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2022, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file 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.connectivity.aidl;
+interface ConnectivityNative {
+ void blockPortForBind(in int port);
+ void unblockPortForBind(in int port);
+ void unblockAllPortsForBind();
+ int[] getPortsBlockedForBind();
+}
diff --git a/service/binder/android/net/connectivity/aidl/ConnectivityNative.aidl b/service/binder/android/net/connectivity/aidl/ConnectivityNative.aidl
new file mode 100644
index 0000000..31e24b4
--- /dev/null
+++ b/service/binder/android/net/connectivity/aidl/ConnectivityNative.aidl
@@ -0,0 +1,59 @@
+/**
+ * Copyright (c) 2022, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.connectivity.aidl;
+
+interface ConnectivityNative {
+ /**
+ * Blocks a port from being assigned during bind(). The caller is responsible for updating
+ * /proc/sys/net/ipv4/ip_local_port_range with the port being blocked so that calls to connect()
+ * will not automatically assign one of the blocked ports.
+ * Will return success even if port was already blocked.
+ *
+ * @param port Int corresponding to port number.
+ *
+ * @throws IllegalArgumentException if the port is invalid.
+ * @throws SecurityException if the UID of the client doesn't have network stack permission.
+ * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+ * unix errno.
+ */
+ void blockPortForBind(in int port);
+
+ /**
+ * Unblocks a port that has previously been blocked.
+ * Will return success even if port was already unblocked.
+ *
+ * @param port Int corresponding to port number.
+ *
+ * @throws IllegalArgumentException if the port is invalid.
+ * @throws SecurityException if the UID of the client doesn't have network stack permission.
+ * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+ * unix errno.
+ */
+ void unblockPortForBind(in int port);
+
+ /**
+ * Unblocks all ports that have previously been blocked.
+ */
+ void unblockAllPortsForBind();
+
+ /**
+ * Gets the list of ports that have been blocked.
+ *
+ * @return List of blocked ports.
+ */
+ int[] getPortsBlockedForBind();
+}
\ No newline at end of file
diff --git a/service/jarjar-rules.txt b/service/jarjar-rules.txt
index 06a4cef..c7223fc 100644
--- a/service/jarjar-rules.txt
+++ b/service/jarjar-rules.txt
@@ -105,5 +105,19 @@
# From the API shims
rule com.android.networkstack.apishim.** com.android.connectivity.@0
+# From filegroup framework-connectivity-protos
+rule android.service.*Proto com.android.connectivity.@0
+
+# From mdns-aidl-interface
+rule android.net.mdns.aidl.** android.net.connectivity.@0
+
+# From nearby-service, including proto
+rule service.proto.** com.android.server.nearby.@0
+rule androidx.annotation.Keep* com.android.server.nearby.@0
+rule androidx.collection.** com.android.server.nearby.@0
+rule androidx.core.** com.android.server.nearby.@0
+rule androidx.versionedparcelable.** com.android.server.nearby.@0
+rule com.google.common.** com.android.server.nearby.@0
+
# Remaining are connectivity sources in com.android.server and com.android.server.connectivity:
# TODO: move to a subpackage of com.android.connectivity (such as com.android.connectivity.server)
diff --git a/service/jni/com_android_net_module_util/onload.cpp b/service/jni/com_android_net_module_util/onload.cpp
index 2f09e55..d91eb03 100644
--- a/service/jni/com_android_net_module_util/onload.cpp
+++ b/service/jni/com_android_net_module_util/onload.cpp
@@ -21,6 +21,7 @@
int register_com_android_net_module_util_BpfMap(JNIEnv* env, char const* class_name);
int register_com_android_net_module_util_TcUtils(JNIEnv* env, char const* class_name);
+int register_com_android_net_module_util_BpfUtils(JNIEnv* env, char const* class_name);
extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
JNIEnv *env;
@@ -35,6 +36,9 @@
if (register_com_android_net_module_util_TcUtils(env,
"android/net/connectivity/com/android/net/module/util/TcUtils") < 0) return JNI_ERR;
+ if (register_com_android_net_module_util_BpfUtils(env,
+ "android/net/connectivity/com/android/net/module/util/BpfUtils") < 0) return JNI_ERR;
+
return JNI_VERSION_1_6;
}
diff --git a/service/jni/com_android_server_BpfNetMaps.cpp b/service/jni/com_android_server_BpfNetMaps.cpp
index f13c68d..bc70c93 100644
--- a/service/jni/com_android_server_BpfNetMaps.cpp
+++ b/service/jni/com_android_server_BpfNetMaps.cpp
@@ -39,148 +39,126 @@
namespace android {
-static void native_init(JNIEnv* env, jobject clazz) {
+#define CHECK_LOG(status) \
+ do { \
+ if (!isOk(status)) \
+ ALOGE("%s failed, error code = %d", __func__, status.code()); \
+ } while (0)
+
+static void native_init(JNIEnv* env, jclass clazz) {
Status status = mTc.start();
- if (!isOk(status)) {
- ALOGE("%s failed, error code = %d", __func__, status.code());
- }
+ CHECK_LOG(status);
}
-static jint native_addNaughtyApp(JNIEnv* env, jobject clazz, jint uid) {
+static jint native_addNaughtyApp(JNIEnv* env, jobject self, jint uid) {
const uint32_t appUids = static_cast<uint32_t>(abs(uid));
Status status = mTc.updateUidOwnerMap(appUids, PENALTY_BOX_MATCH,
TrafficController::IptOp::IptOpInsert);
- if (!isOk(status)) {
- ALOGE("%s failed, error code = %d", __func__, status.code());
- }
+ CHECK_LOG(status);
return (jint)status.code();
}
-static jint native_removeNaughtyApp(JNIEnv* env, jobject clazz, jint uid) {
+static jint native_removeNaughtyApp(JNIEnv* env, jobject self, jint uid) {
const uint32_t appUids = static_cast<uint32_t>(abs(uid));
Status status = mTc.updateUidOwnerMap(appUids, PENALTY_BOX_MATCH,
TrafficController::IptOp::IptOpDelete);
- if (!isOk(status)) {
- ALOGE("%s failed, error code = %d", __func__, status.code());
- }
+ CHECK_LOG(status);
return (jint)status.code();
}
-static jint native_addNiceApp(JNIEnv* env, jobject clazz, jint uid) {
+static jint native_addNiceApp(JNIEnv* env, jobject self, jint uid) {
const uint32_t appUids = static_cast<uint32_t>(abs(uid));
Status status = mTc.updateUidOwnerMap(appUids, HAPPY_BOX_MATCH,
TrafficController::IptOp::IptOpInsert);
- if (!isOk(status)) {
- ALOGE("%s failed, error code = %d", __func__, status.code());
- }
+ CHECK_LOG(status);
return (jint)status.code();
}
-static jint native_removeNiceApp(JNIEnv* env, jobject clazz, jint uid) {
+static jint native_removeNiceApp(JNIEnv* env, jobject self, jint uid) {
const uint32_t appUids = static_cast<uint32_t>(abs(uid));
Status status = mTc.updateUidOwnerMap(appUids, HAPPY_BOX_MATCH,
TrafficController::IptOp::IptOpDelete);
- if (!isOk(status)) {
- ALOGD("%s failed, error code = %d", __func__, status.code());
- }
+ CHECK_LOG(status);
return (jint)status.code();
}
-static jint native_setChildChain(JNIEnv* env, jobject clazz, jint childChain, jboolean enable) {
+static jint native_setChildChain(JNIEnv* env, jobject self, jint childChain, jboolean enable) {
auto chain = static_cast<ChildChain>(childChain);
int res = mTc.toggleUidOwnerMap(chain, enable);
- if (res) {
- ALOGE("%s failed, error code = %d", __func__, res);
- }
+ if (res) ALOGE("%s failed, error code = %d", __func__, res);
return (jint)res;
}
-static jint native_replaceUidChain(JNIEnv* env, jobject clazz, jstring name, jboolean isAllowlist,
- jintArray jUids) {
+static jint native_replaceUidChain(JNIEnv* env, jobject self, jstring name, jboolean isAllowlist,
+ jintArray jUids) {
const ScopedUtfChars chainNameUtf8(env, name);
- if (chainNameUtf8.c_str() == nullptr) {
- return -EINVAL;
- }
+ if (chainNameUtf8.c_str() == nullptr) return -EINVAL;
const std::string chainName(chainNameUtf8.c_str());
ScopedIntArrayRO uids(env, jUids);
- if (uids.get() == nullptr) {
- return -EINVAL;
- }
+ if (uids.get() == nullptr) return -EINVAL;
size_t size = uids.size();
static_assert(sizeof(*(uids.get())) == sizeof(int32_t));
std::vector<int32_t> data ((int32_t *)&uids[0], (int32_t*)&uids[size]);
int res = mTc.replaceUidOwnerMap(chainName, isAllowlist, data);
- if (res) {
- ALOGE("%s failed, error code = %d", __func__, res);
- }
+ if (res) ALOGE("%s failed, error code = %d", __func__, res);
return (jint)res;
}
-static jint native_setUidRule(JNIEnv* env, jobject clazz, jint childChain, jint uid,
- jint firewallRule) {
+static jint native_setUidRule(JNIEnv* env, jobject self, jint childChain, jint uid,
+ jint firewallRule) {
auto chain = static_cast<ChildChain>(childChain);
auto rule = static_cast<FirewallRule>(firewallRule);
FirewallType fType = mTc.getFirewallType(chain);
int res = mTc.changeUidOwnerRule(chain, uid, rule, fType);
- if (res) {
- ALOGE("%s failed, error code = %d", __func__, res);
- }
+ if (res) ALOGE("%s failed, error code = %d", __func__, res);
return (jint)res;
}
-static jint native_addUidInterfaceRules(JNIEnv* env, jobject clazz, jstring ifName,
- jintArray jUids) {
- const ScopedUtfChars ifNameUtf8(env, ifName);
- if (ifNameUtf8.c_str() == nullptr) {
- return -EINVAL;
+static jint native_addUidInterfaceRules(JNIEnv* env, jobject self, jstring ifName,
+ jintArray jUids) {
+ // Null ifName is a wildcard to allow apps to receive packets on all interfaces and ifIndex is
+ // set to 0.
+ int ifIndex = 0;
+ if (ifName != nullptr) {
+ const ScopedUtfChars ifNameUtf8(env, ifName);
+ const std::string interfaceName(ifNameUtf8.c_str());
+ ifIndex = if_nametoindex(interfaceName.c_str());
}
- const std::string interfaceName(ifNameUtf8.c_str());
- const int ifIndex = if_nametoindex(interfaceName.c_str());
ScopedIntArrayRO uids(env, jUids);
- if (uids.get() == nullptr) {
- return -EINVAL;
- }
+ if (uids.get() == nullptr) return -EINVAL;
size_t size = uids.size();
static_assert(sizeof(*(uids.get())) == sizeof(int32_t));
std::vector<int32_t> data ((int32_t *)&uids[0], (int32_t*)&uids[size]);
Status status = mTc.addUidInterfaceRules(ifIndex, data);
- if (!isOk(status)) {
- ALOGE("%s failed, error code = %d", __func__, status.code());
- }
+ CHECK_LOG(status);
return (jint)status.code();
}
-static jint native_removeUidInterfaceRules(JNIEnv* env, jobject clazz, jintArray jUids) {
+static jint native_removeUidInterfaceRules(JNIEnv* env, jobject self, jintArray jUids) {
ScopedIntArrayRO uids(env, jUids);
- if (uids.get() == nullptr) {
- return -EINVAL;
- }
+ if (uids.get() == nullptr) return -EINVAL;
size_t size = uids.size();
static_assert(sizeof(*(uids.get())) == sizeof(int32_t));
std::vector<int32_t> data ((int32_t *)&uids[0], (int32_t*)&uids[size]);
Status status = mTc.removeUidInterfaceRules(data);
- if (!isOk(status)) {
- ALOGE("%s failed, error code = %d", __func__, status.code());
- }
+ CHECK_LOG(status);
return (jint)status.code();
}
-static jint native_swapActiveStatsMap(JNIEnv* env, jobject clazz) {
+static jint native_swapActiveStatsMap(JNIEnv* env, jobject self) {
Status status = mTc.swapActiveStatsMap();
- if (!isOk(status)) {
- ALOGD("%s failed, error code = %d", __func__, status.code());
- }
+ CHECK_LOG(status);
return (jint)status.code();
}
-static void native_setPermissionForUids(JNIEnv* env, jobject clazz, jint permission,
- jintArray jUids) {
+static void native_setPermissionForUids(JNIEnv* env, jobject self, jint permission,
+ jintArray jUids) {
ScopedIntArrayRO uids(env, jUids);
if (uids.get() == nullptr) return;
@@ -190,7 +168,7 @@
mTc.setPermissionForUids(permission, data);
}
-static void native_dump(JNIEnv* env, jobject clazz, jobject javaFd, jboolean verbose) {
+static void native_dump(JNIEnv* env, jobject self, jobject javaFd, jboolean verbose) {
int fd = netjniutils::GetNativeFileDescriptor(env, javaFd);
if (fd < 0) {
jniThrowExceptionFmt(env, "java/io/IOException", "Invalid file descriptor");
@@ -235,9 +213,8 @@
// clang-format on
int register_com_android_server_BpfNetMaps(JNIEnv* env) {
- return jniRegisterNativeMethods(env,
- "com/android/server/BpfNetMaps",
- gMethods, NELEM(gMethods));
+ return jniRegisterNativeMethods(env, "com/android/server/BpfNetMaps",
+ gMethods, NELEM(gMethods));
}
}; // namespace android
diff --git a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
index 4517b5c..e2c5a63 100644
--- a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
+++ b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
@@ -34,7 +34,6 @@
#include <netjniutils/netjniutils.h>
#include <private/android_filesystem_config.h>
-#include "libclat/bpfhelper.h"
#include "libclat/clatutils.h"
#include "nativehelper/scoped_utf_chars.h"
@@ -257,46 +256,6 @@
}
}
-int initTracker(const std::string& iface, const std::string& pfx96, const std::string& v4,
- const std::string& v6, net::clat::ClatdTracker* output) {
- strlcpy(output->iface, iface.c_str(), sizeof(output->iface));
- output->ifIndex = if_nametoindex(iface.c_str());
- if (output->ifIndex == 0) {
- ALOGE("interface %s not found", output->iface);
- return -1;
- }
-
- unsigned len = snprintf(output->v4iface, sizeof(output->v4iface),
- "%s%s", DEVICEPREFIX, iface.c_str());
- if (len >= sizeof(output->v4iface)) {
- ALOGE("interface name too long '%s'", output->v4iface);
- return -1;
- }
-
- output->v4ifIndex = if_nametoindex(output->v4iface);
- if (output->v4ifIndex == 0) {
- ALOGE("v4-interface %s not found", output->v4iface);
- return -1;
- }
-
- if (!inet_pton(AF_INET6, pfx96.c_str(), &output->pfx96)) {
- ALOGE("invalid IPv6 address specified for plat prefix: %s", pfx96.c_str());
- return -1;
- }
-
- if (!inet_pton(AF_INET, v4.c_str(), &output->v4)) {
- ALOGE("Invalid IPv4 address %s", v4.c_str());
- return -1;
- }
-
- if (!inet_pton(AF_INET6, v6.c_str(), &output->v6)) {
- ALOGE("Invalid source address %s", v6.c_str());
- return -1;
- }
-
- return 0;
-}
-
static jint com_android_server_connectivity_ClatCoordinator_startClatd(
JNIEnv* env, jobject clazz, jobject tunJavaFd, jobject readSockJavaFd,
jobject writeSockJavaFd, jstring iface, jstring pfx96, jstring v4, jstring v6) {
@@ -355,7 +314,8 @@
}
// TODO: use android::base::ScopeGuard.
- if (int ret = posix_spawnattr_setflags(&attr, POSIX_SPAWN_USEVFORK)) {
+ if (int ret = posix_spawnattr_setflags(&attr, POSIX_SPAWN_USEVFORK
+ | POSIX_SPAWN_CLOEXEC_DEFAULT)) {
posix_spawnattr_destroy(&attr);
throwIOException(env, "posix_spawnattr_setflags failed", ret);
return -1;
@@ -404,15 +364,6 @@
posix_spawnattr_destroy(&attr);
posix_spawn_file_actions_destroy(&fa);
- // 6. Start BPF if any
- if (!net::clat::initMaps()) {
- net::clat::ClatdTracker tracker = {};
- if (!initTracker(ifaceStr.c_str(), pfx96Str.c_str(), v4Str.c_str(), v6Str.c_str(),
- &tracker)) {
- net::clat::maybeStartBpf(tracker);
- }
- }
-
return pid;
}
@@ -467,14 +418,6 @@
return;
}
- if (!net::clat::initMaps()) {
- net::clat::ClatdTracker tracker = {};
- if (!initTracker(ifaceStr.c_str(), pfx96Str.c_str(), v4Str.c_str(), v6Str.c_str(),
- &tracker)) {
- net::clat::maybeStopBpf(tracker);
- }
- }
-
stopClatdProcess(pid);
}
diff --git a/service/native/Android.bp b/service/native/Android.bp
index cb26bc3..697fcbd 100644
--- a/service/native/Android.bp
+++ b/service/native/Android.bp
@@ -52,7 +52,8 @@
cc_test {
name: "traffic_controller_unit_test",
- test_suites: ["general-tests"],
+ test_suites: ["general-tests", "mts-tethering"],
+ test_config_template: ":net_native_test_config_template",
require_root: true,
local_include_dirs: ["include"],
header_libs: [
@@ -71,4 +72,13 @@
"libnetd_updatable",
"netd_aidl_interface-lateststable-ndk",
],
+ compile_multilib: "both",
+ multilib: {
+ lib32: {
+ suffix: "32",
+ },
+ lib64: {
+ suffix: "64",
+ },
+ },
}
diff --git a/service/native/TrafficController.cpp b/service/native/TrafficController.cpp
index 3e98edb..68fc9c8 100644
--- a/service/native/TrafficController.cpp
+++ b/service/native/TrafficController.cpp
@@ -74,6 +74,8 @@
const char* TrafficController::LOCAL_POWERSAVE = "fw_powersave";
const char* TrafficController::LOCAL_RESTRICTED = "fw_restricted";
const char* TrafficController::LOCAL_LOW_POWER_STANDBY = "fw_low_power_standby";
+const char* TrafficController::LOCAL_OEM_DENY_1 = "fw_oem_deny_1";
+const char* TrafficController::LOCAL_OEM_DENY_2 = "fw_oem_deny_2";
static_assert(BPF_PERMISSION_INTERNET == INetd::PERMISSION_INTERNET,
"Mismatch between BPF and AIDL permissions: PERMISSION_INTERNET");
@@ -88,7 +90,7 @@
} \
} while (0)
-const std::string uidMatchTypeToString(uint8_t match) {
+const std::string uidMatchTypeToString(uint32_t match) {
std::string matchType;
FLAG_MSG_TRANS(matchType, HAPPY_BOX_MATCH, match);
FLAG_MSG_TRANS(matchType, PENALTY_BOX_MATCH, match);
@@ -98,6 +100,9 @@
FLAG_MSG_TRANS(matchType, RESTRICTED_MATCH, match);
FLAG_MSG_TRANS(matchType, LOW_POWER_STANDBY_MATCH, match);
FLAG_MSG_TRANS(matchType, IIF_MATCH, match);
+ FLAG_MSG_TRANS(matchType, LOCKDOWN_VPN_MATCH, match);
+ FLAG_MSG_TRANS(matchType, OEM_DENY_1_MATCH, match);
+ FLAG_MSG_TRANS(matchType, OEM_DENY_2_MATCH, match);
if (match) {
return StringPrintf("Unknown match: %u", match);
}
@@ -183,6 +188,7 @@
RETURN_IF_NOT_OK(mUidOwnerMap.init(UID_OWNER_MAP_PATH));
RETURN_IF_NOT_OK(mUidOwnerMap.clear());
RETURN_IF_NOT_OK(mUidPermissionMap.init(UID_PERMISSION_MAP_PATH));
+ ALOGI("%s successfully", __func__);
return netdutils::status::ok;
}
@@ -272,7 +278,7 @@
if (oldMatch.ok()) {
UidOwnerValue newMatch = {
.iif = (match == IIF_MATCH) ? 0 : oldMatch.value().iif,
- .rule = static_cast<uint8_t>(oldMatch.value().rule & ~match),
+ .rule = oldMatch.value().rule & ~match,
};
if (newMatch.rule == 0) {
RETURN_IF_NOT_OK(mUidOwnerMap.deleteValue(uid));
@@ -286,23 +292,20 @@
}
Status TrafficController::addRule(uint32_t uid, UidOwnerMatchType match, uint32_t iif) {
- // iif should be non-zero if and only if match == MATCH_IIF
- if (match == IIF_MATCH && iif == 0) {
- return statusFromErrno(EINVAL, "Interface match must have nonzero interface index");
- } else if (match != IIF_MATCH && iif != 0) {
+ if (match != IIF_MATCH && iif != 0) {
return statusFromErrno(EINVAL, "Non-interface match must have zero interface index");
}
auto oldMatch = mUidOwnerMap.readValue(uid);
if (oldMatch.ok()) {
UidOwnerValue newMatch = {
- .iif = iif ? iif : oldMatch.value().iif,
- .rule = static_cast<uint8_t>(oldMatch.value().rule | match),
+ .iif = (match == IIF_MATCH) ? iif : oldMatch.value().iif,
+ .rule = oldMatch.value().rule | match,
};
RETURN_IF_NOT_OK(mUidOwnerMap.writeValue(uid, newMatch, BPF_ANY));
} else {
UidOwnerValue newMatch = {
.iif = iif,
- .rule = static_cast<uint8_t>(match),
+ .rule = match,
};
RETURN_IF_NOT_OK(mUidOwnerMap.writeValue(uid, newMatch, BPF_ANY));
}
@@ -335,6 +338,12 @@
return ALLOWLIST;
case LOW_POWER_STANDBY:
return ALLOWLIST;
+ case LOCKDOWN:
+ return DENYLIST;
+ case OEM_DENY_1:
+ return DENYLIST;
+ case OEM_DENY_2:
+ return DENYLIST;
case NONE:
default:
return DENYLIST;
@@ -360,6 +369,15 @@
case LOW_POWER_STANDBY:
res = updateOwnerMapEntry(LOW_POWER_STANDBY_MATCH, uid, rule, type);
break;
+ case LOCKDOWN:
+ res = updateOwnerMapEntry(LOCKDOWN_VPN_MATCH, uid, rule, type);
+ break;
+ case OEM_DENY_1:
+ res = updateOwnerMapEntry(OEM_DENY_1_MATCH, uid, rule, type);
+ break;
+ case OEM_DENY_2:
+ res = updateOwnerMapEntry(OEM_DENY_2_MATCH, uid, rule, type);
+ break;
case NONE:
default:
ALOGW("Unknown child chain: %d", chain);
@@ -399,9 +417,6 @@
Status TrafficController::addUidInterfaceRules(const int iif,
const std::vector<int32_t>& uidsToAdd) {
- if (!iif) {
- return statusFromErrno(EINVAL, "Interface rule must specify interface");
- }
std::lock_guard guard(mMutex);
for (auto uid : uidsToAdd) {
@@ -440,6 +455,10 @@
res = replaceRulesInMap(RESTRICTED_MATCH, uids);
} else if (!name.compare(LOCAL_LOW_POWER_STANDBY)) {
res = replaceRulesInMap(LOW_POWER_STANDBY_MATCH, uids);
+ } else if (!name.compare(LOCAL_OEM_DENY_1)) {
+ res = replaceRulesInMap(OEM_DENY_1_MATCH, uids);
+ } else if (!name.compare(LOCAL_OEM_DENY_2)) {
+ res = replaceRulesInMap(OEM_DENY_2_MATCH, uids);
} else {
ALOGE("unknown chain name: %s", name.c_str());
return -EINVAL;
@@ -454,15 +473,15 @@
int TrafficController::toggleUidOwnerMap(ChildChain chain, bool enable) {
std::lock_guard guard(mMutex);
uint32_t key = UID_RULES_CONFIGURATION_KEY;
- auto oldConfiguration = mConfigurationMap.readValue(key);
- if (!oldConfiguration.ok()) {
+ auto oldConfigure = mConfigurationMap.readValue(key);
+ if (!oldConfigure.ok()) {
ALOGE("Cannot read the old configuration from map: %s",
- oldConfiguration.error().message().c_str());
- return -oldConfiguration.error().code();
+ oldConfigure.error().message().c_str());
+ return -oldConfigure.error().code();
}
Status res;
BpfConfig newConfiguration;
- uint8_t match;
+ uint32_t match;
switch (chain) {
case DOZABLE:
match = DOZABLE_MATCH;
@@ -479,11 +498,17 @@
case LOW_POWER_STANDBY:
match = LOW_POWER_STANDBY_MATCH;
break;
+ case OEM_DENY_1:
+ match = OEM_DENY_1_MATCH;
+ break;
+ case OEM_DENY_2:
+ match = OEM_DENY_2_MATCH;
+ break;
default:
return -EINVAL;
}
newConfiguration =
- enable ? (oldConfiguration.value() | match) : (oldConfiguration.value() & (~match));
+ enable ? (oldConfigure.value() | match) : (oldConfigure.value() & (~match));
res = mConfigurationMap.writeValue(key, newConfiguration, BPF_EXIST);
if (!isOk(res)) {
ALOGE("Failed to toggleUidOwnerMap(%d): %s", chain, res.msg().c_str());
@@ -495,17 +520,17 @@
std::lock_guard guard(mMutex);
uint32_t key = CURRENT_STATS_MAP_CONFIGURATION_KEY;
- auto oldConfiguration = mConfigurationMap.readValue(key);
- if (!oldConfiguration.ok()) {
+ auto oldConfigure = mConfigurationMap.readValue(key);
+ if (!oldConfigure.ok()) {
ALOGE("Cannot read the old configuration from map: %s",
- oldConfiguration.error().message().c_str());
- return Status(oldConfiguration.error().code(), oldConfiguration.error().message());
+ oldConfigure.error().message().c_str());
+ return Status(oldConfigure.error().code(), oldConfigure.error().message());
}
// Write to the configuration map to inform the kernel eBPF program to switch
// from using one map to the other. Use flag BPF_EXIST here since the map should
// be already populated in initMaps.
- uint8_t newConfigure = (oldConfiguration.value() == SELECT_MAP_A) ? SELECT_MAP_B : SELECT_MAP_A;
+ uint32_t newConfigure = (oldConfigure.value() == SELECT_MAP_A) ? SELECT_MAP_B : SELECT_MAP_A;
auto res = mConfigurationMap.writeValue(CURRENT_STATS_MAP_CONFIGURATION_KEY, newConfigure,
BPF_EXIST);
if (!res.ok()) {
diff --git a/service/native/TrafficControllerTest.cpp b/service/native/TrafficControllerTest.cpp
index 9529cae..dfa7097 100644
--- a/service/native/TrafficControllerTest.cpp
+++ b/service/native/TrafficControllerTest.cpp
@@ -36,6 +36,7 @@
#include <netdutils/MockSyscalls.h>
+#define TEST_BPF_MAP
#include "TrafficController.h"
#include "bpf/BpfUtils.h"
#include "NetdUpdatablePublic.h"
@@ -65,7 +66,7 @@
BpfMap<uint64_t, UidTagValue> mFakeCookieTagMap;
BpfMap<uint32_t, StatsValue> mFakeAppUidStatsMap;
BpfMap<StatsKey, StatsValue> mFakeStatsMapA;
- BpfMap<uint32_t, uint8_t> mFakeConfigurationMap;
+ BpfMap<uint32_t, uint32_t> mFakeConfigurationMap;
BpfMap<uint32_t, UidOwnerValue> mFakeUidOwnerMap;
BpfMap<uint32_t, uint8_t> mFakeUidPermissionMap;
@@ -73,52 +74,42 @@
std::lock_guard guard(mTc.mMutex);
ASSERT_EQ(0, setrlimitForTest());
- mFakeCookieTagMap.reset(createMap(BPF_MAP_TYPE_HASH, sizeof(uint64_t), sizeof(UidTagValue),
- TEST_MAP_SIZE, 0));
+ mFakeCookieTagMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE);
ASSERT_VALID(mFakeCookieTagMap);
- mFakeAppUidStatsMap.reset(createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(StatsValue),
- TEST_MAP_SIZE, 0));
+ mFakeAppUidStatsMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE);
ASSERT_VALID(mFakeAppUidStatsMap);
- mFakeStatsMapA.reset(createMap(BPF_MAP_TYPE_HASH, sizeof(StatsKey), sizeof(StatsValue),
- TEST_MAP_SIZE, 0));
+ mFakeStatsMapA.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE);
ASSERT_VALID(mFakeStatsMapA);
- mFakeConfigurationMap.reset(
- createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(uint8_t), 1, 0));
+ mFakeConfigurationMap.resetMap(BPF_MAP_TYPE_HASH, 1);
ASSERT_VALID(mFakeConfigurationMap);
- mFakeUidOwnerMap.reset(createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(UidOwnerValue),
- TEST_MAP_SIZE, 0));
+ mFakeUidOwnerMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE);
ASSERT_VALID(mFakeUidOwnerMap);
- mFakeUidPermissionMap.reset(
- createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(uint8_t), TEST_MAP_SIZE, 0));
+ mFakeUidPermissionMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE);
ASSERT_VALID(mFakeUidPermissionMap);
- mTc.mCookieTagMap.reset(dupFd(mFakeCookieTagMap.getMap()));
+ mTc.mCookieTagMap = mFakeCookieTagMap;
ASSERT_VALID(mTc.mCookieTagMap);
- mTc.mAppUidStatsMap.reset(dupFd(mFakeAppUidStatsMap.getMap()));
+ mTc.mAppUidStatsMap = mFakeAppUidStatsMap;
ASSERT_VALID(mTc.mAppUidStatsMap);
- mTc.mStatsMapA.reset(dupFd(mFakeStatsMapA.getMap()));
+ mTc.mStatsMapA = mFakeStatsMapA;
ASSERT_VALID(mTc.mStatsMapA);
- mTc.mConfigurationMap.reset(dupFd(mFakeConfigurationMap.getMap()));
+ mTc.mConfigurationMap = mFakeConfigurationMap;
ASSERT_VALID(mTc.mConfigurationMap);
// Always write to stats map A by default.
ASSERT_RESULT_OK(mTc.mConfigurationMap.writeValue(CURRENT_STATS_MAP_CONFIGURATION_KEY,
SELECT_MAP_A, BPF_ANY));
- mTc.mUidOwnerMap.reset(dupFd(mFakeUidOwnerMap.getMap()));
+ mTc.mUidOwnerMap = mFakeUidOwnerMap;
ASSERT_VALID(mTc.mUidOwnerMap);
- mTc.mUidPermissionMap.reset(dupFd(mFakeUidPermissionMap.getMap()));
+ mTc.mUidPermissionMap = mFakeUidPermissionMap;
ASSERT_VALID(mTc.mUidPermissionMap);
mTc.mPrivilegedUser.clear();
}
- int dupFd(const android::base::unique_fd& mapFd) {
- return fcntl(mapFd.get(), F_DUPFD_CLOEXEC, 0);
- }
-
void populateFakeStats(uint64_t cookie, uint32_t uid, uint32_t tag, StatsKey* key) {
UidTagValue cookieMapkey = {.uid = (uint32_t)uid, .tag = tag};
EXPECT_RESULT_OK(mFakeCookieTagMap.writeValue(cookie, cookieMapkey, BPF_ANY));
@@ -307,6 +298,9 @@
checkUidOwnerRuleForChain(POWERSAVE, POWERSAVE_MATCH);
checkUidOwnerRuleForChain(RESTRICTED, RESTRICTED_MATCH);
checkUidOwnerRuleForChain(LOW_POWER_STANDBY, LOW_POWER_STANDBY_MATCH);
+ checkUidOwnerRuleForChain(LOCKDOWN, LOCKDOWN_VPN_MATCH);
+ checkUidOwnerRuleForChain(OEM_DENY_1, OEM_DENY_1_MATCH);
+ checkUidOwnerRuleForChain(OEM_DENY_2, OEM_DENY_2_MATCH);
ASSERT_EQ(-EINVAL, mTc.changeUidOwnerRule(NONE, TEST_UID, ALLOW, ALLOWLIST));
ASSERT_EQ(-EINVAL, mTc.changeUidOwnerRule(INVALID_CHAIN, TEST_UID, ALLOW, ALLOWLIST));
}
@@ -318,6 +312,8 @@
checkUidMapReplace("fw_powersave", uids, POWERSAVE_MATCH);
checkUidMapReplace("fw_restricted", uids, RESTRICTED_MATCH);
checkUidMapReplace("fw_low_power_standby", uids, LOW_POWER_STANDBY_MATCH);
+ checkUidMapReplace("fw_oem_deny_1", uids, OEM_DENY_1_MATCH);
+ checkUidMapReplace("fw_oem_deny_2", uids, OEM_DENY_2_MATCH);
ASSERT_EQ(-EINVAL, mTc.replaceUidOwnerMap("unknow", true, uids));
}
@@ -491,6 +487,70 @@
checkEachUidValue({10001, 10002}, IIF_MATCH);
}
+TEST_F(TrafficControllerTest, TestAddUidInterfaceFilteringRulesWithWildcard) {
+ // iif=0 is a wildcard
+ int iif = 0;
+ // Add interface rule with wildcard to uids
+ ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif, {1000, 1001})));
+ expectUidOwnerMapValues({1000, 1001}, IIF_MATCH, iif);
+}
+
+TEST_F(TrafficControllerTest, TestRemoveUidInterfaceFilteringRulesWithWildcard) {
+ // iif=0 is a wildcard
+ int iif = 0;
+ // Add interface rule with wildcard to two uids
+ ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif, {1000, 1001})));
+ expectUidOwnerMapValues({1000, 1001}, IIF_MATCH, iif);
+
+ // Remove interface rule from one of the uids
+ ASSERT_TRUE(isOk(mTc.removeUidInterfaceRules({1000})));
+ expectUidOwnerMapValues({1001}, IIF_MATCH, iif);
+ checkEachUidValue({1001}, IIF_MATCH);
+
+ // Remove interface rule from the remaining uid
+ ASSERT_TRUE(isOk(mTc.removeUidInterfaceRules({1001})));
+ expectMapEmpty(mFakeUidOwnerMap);
+}
+
+TEST_F(TrafficControllerTest, TestUidInterfaceFilteringRulesWithWildcardAndExistingMatches) {
+ // Set up existing DOZABLE_MATCH and POWERSAVE_MATCH rule
+ ASSERT_TRUE(isOk(updateUidOwnerMaps({1000}, DOZABLE_MATCH,
+ TrafficController::IptOpInsert)));
+ ASSERT_TRUE(isOk(updateUidOwnerMaps({1000}, POWERSAVE_MATCH,
+ TrafficController::IptOpInsert)));
+
+ // iif=0 is a wildcard
+ int iif = 0;
+ // Add interface rule with wildcard to the existing uid
+ ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif, {1000})));
+ expectUidOwnerMapValues({1000}, POWERSAVE_MATCH | DOZABLE_MATCH | IIF_MATCH, iif);
+
+ // Remove interface rule with wildcard from the existing uid
+ ASSERT_TRUE(isOk(mTc.removeUidInterfaceRules({1000})));
+ expectUidOwnerMapValues({1000}, POWERSAVE_MATCH | DOZABLE_MATCH, 0);
+}
+
+TEST_F(TrafficControllerTest, TestUidInterfaceFilteringRulesWithWildcardAndNewMatches) {
+ // iif=0 is a wildcard
+ int iif = 0;
+ // Set up existing interface rule with wildcard
+ ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif, {1000})));
+
+ // Add DOZABLE_MATCH and POWERSAVE_MATCH rule to the existing uid
+ ASSERT_TRUE(isOk(updateUidOwnerMaps({1000}, DOZABLE_MATCH,
+ TrafficController::IptOpInsert)));
+ ASSERT_TRUE(isOk(updateUidOwnerMaps({1000}, POWERSAVE_MATCH,
+ TrafficController::IptOpInsert)));
+ expectUidOwnerMapValues({1000}, POWERSAVE_MATCH | DOZABLE_MATCH | IIF_MATCH, iif);
+
+ // Remove DOZABLE_MATCH and POWERSAVE_MATCH rule from the existing uid
+ ASSERT_TRUE(isOk(updateUidOwnerMaps({1000}, DOZABLE_MATCH,
+ TrafficController::IptOpDelete)));
+ ASSERT_TRUE(isOk(updateUidOwnerMaps({1000}, POWERSAVE_MATCH,
+ TrafficController::IptOpDelete)));
+ expectUidOwnerMapValues({1000}, IIF_MATCH, iif);
+}
+
TEST_F(TrafficControllerTest, TestGrantInternetPermission) {
std::vector<uid_t> appUids = {TEST_UID, TEST_UID2, TEST_UID3};
@@ -608,7 +668,7 @@
BpfMap<uint64_t, UidTagValue> mCookieTagMap;
void SetUp() {
- mCookieTagMap.reset(android::bpf::mapRetrieveRW(COOKIE_TAG_MAP_PATH));
+ mCookieTagMap.init(COOKIE_TAG_MAP_PATH);
ASSERT_TRUE(mCookieTagMap.isValid());
}
@@ -620,7 +680,7 @@
if (res.ok() || (res.error().code() == ENOENT)) {
return Result<void>();
}
- ALOGE("Failed to delete data(cookie = %" PRIu64 "): %s\n", key,
+ ALOGE("Failed to delete data(cookie = %" PRIu64 "): %s", key,
strerror(res.error().code()));
}
// Move forward to next cookie in the map.
diff --git a/service/native/include/Common.h b/service/native/include/Common.h
index dc44845..48f68ea 100644
--- a/service/native/include/Common.h
+++ b/service/native/include/Common.h
@@ -35,6 +35,9 @@
POWERSAVE = 3,
RESTRICTED = 4,
LOW_POWER_STANDBY = 5,
+ LOCKDOWN = 6,
+ OEM_DENY_1 = 7,
+ OEM_DENY_2 = 8,
INVALID_CHAIN
};
// LINT.ThenChange(packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java)
diff --git a/service/native/include/TrafficController.h b/service/native/include/TrafficController.h
index 79e75ac..7a36e1e 100644
--- a/service/native/include/TrafficController.h
+++ b/service/native/include/TrafficController.h
@@ -88,6 +88,8 @@
static const char* LOCAL_POWERSAVE;
static const char* LOCAL_RESTRICTED;
static const char* LOCAL_LOW_POWER_STANDBY;
+ static const char* LOCAL_OEM_DENY_1;
+ static const char* LOCAL_OEM_DENY_2;
private:
/*
@@ -149,13 +151,13 @@
* the map right now:
* - Entry with UID_RULES_CONFIGURATION_KEY:
* Store the configuration for the current uid rules. It indicates the device
- * is in doze/powersave/standby/restricted/low power standby mode.
+ * is in doze/powersave/standby/restricted/low power standby/oem deny mode.
* - Entry with CURRENT_STATS_MAP_CONFIGURATION_KEY:
* Stores the current live stats map that kernel program is writing to.
* Userspace can do scraping and cleaning job on the other one depending on the
* current configs.
*/
- bpf::BpfMap<uint32_t, uint8_t> mConfigurationMap GUARDED_BY(mMutex);
+ bpf::BpfMap<uint32_t, uint32_t> mConfigurationMap GUARDED_BY(mMutex);
/*
* mUidOwnerMap: Store uids that are used for bandwidth control uid match.
diff --git a/service/native/libs/libclat/Android.bp b/service/native/libs/libclat/Android.bp
index 17ee996..54d40ac 100644
--- a/service/native/libs/libclat/Android.bp
+++ b/service/native/libs/libclat/Android.bp
@@ -19,19 +19,12 @@
cc_library_static {
name: "libclat",
defaults: ["netd_defaults"],
- header_libs: [
- "bpf_connectivity_headers",
- "libbase_headers",
- ],
srcs: [
- "TcUtils.cpp", // TODO: move to frameworks/libs/net
- "bpfhelper.cpp",
"clatutils.cpp",
],
stl: "libc++_static",
static_libs: [
"libip_checksum",
- "libnetdutils", // for netdutils/UidConstants.h in bpf_shared.h
],
shared_libs: ["liblog"],
export_include_dirs: ["include"],
@@ -42,12 +35,9 @@
cc_test {
name: "libclat_test",
defaults: ["netd_defaults"],
- test_suites: ["device-tests"],
- header_libs: [
- "bpf_connectivity_headers",
- ],
+ test_suites: ["general-tests", "mts-tethering"],
+ test_config_template: ":net_native_test_config_template",
srcs: [
- "TcUtilsTest.cpp",
"clatutils_test.cpp",
],
static_libs: [
@@ -55,12 +45,19 @@
"libclat",
"libip_checksum",
"libnetd_test_tun_interface",
- "libnetdutils", // for netdutils/UidConstants.h in bpf_shared.h
- "libtcutils",
],
shared_libs: [
"liblog",
"libnetutils",
],
+ compile_multilib: "both",
+ multilib: {
+ lib32: {
+ suffix: "32",
+ },
+ lib64: {
+ suffix: "64",
+ },
+ },
require_root: true,
}
diff --git a/service/native/libs/libclat/TcUtils.cpp b/service/native/libs/libclat/TcUtils.cpp
deleted file mode 100644
index cdfb763..0000000
--- a/service/native/libs/libclat/TcUtils.cpp
+++ /dev/null
@@ -1,390 +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.
- */
-
-#define LOG_TAG "TcUtils"
-
-#include "libclat/TcUtils.h"
-
-#include <arpa/inet.h>
-#include <linux/if.h>
-#include <linux/if_arp.h>
-#include <linux/netlink.h>
-#include <linux/pkt_cls.h>
-#include <linux/pkt_sched.h>
-#include <sys/ioctl.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <log/log.h>
-
-#include "android-base/unique_fd.h"
-
-namespace android {
-namespace net {
-
-using std::max;
-
-// Sync from system/netd/server/NetlinkCommands.h
-const sockaddr_nl KERNEL_NLADDR = {AF_NETLINK, 0, 0, 0};
-const uint16_t NETLINK_REQUEST_FLAGS = NLM_F_REQUEST | NLM_F_ACK;
-
-static int doSIOCGIF(const std::string& interface, int opt) {
- base::unique_fd ufd(socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0));
-
- if (ufd < 0) {
- const int err = errno;
- ALOGE("socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0)");
- return -err;
- };
-
- struct ifreq ifr = {};
- // We use strncpy() instead of strlcpy() since kernel has to be able
- // to handle non-zero terminated junk passed in by userspace anyway,
- // and this way too long interface names (more than IFNAMSIZ-1 = 15
- // characters plus terminating NULL) will not get truncated to 15
- // characters and zero-terminated and thus potentially erroneously
- // match a truncated interface if one were to exist.
- strncpy(ifr.ifr_name, interface.c_str(), sizeof(ifr.ifr_name));
-
- if (ioctl(ufd, opt, &ifr, sizeof(ifr))) return -errno;
-
- if (opt == SIOCGIFHWADDR) return ifr.ifr_hwaddr.sa_family;
- if (opt == SIOCGIFMTU) return ifr.ifr_mtu;
- return -EINVAL;
-}
-
-int hardwareAddressType(const std::string& interface) {
- return doSIOCGIF(interface, SIOCGIFHWADDR);
-}
-
-int deviceMTU(const std::string& interface) {
- return doSIOCGIF(interface, SIOCGIFMTU);
-}
-
-base::Result<bool> isEthernet(const std::string& interface) {
- int rv = hardwareAddressType(interface);
- if (rv < 0) {
- errno = -rv;
- return ErrnoErrorf("Get hardware address type of interface {} failed", interface);
- }
-
- switch (rv) {
- case ARPHRD_ETHER:
- return true;
- case ARPHRD_NONE:
- case ARPHRD_RAWIP: // in Linux 4.14+ rmnet support was upstreamed and this is 519
- case 530: // this is ARPHRD_RAWIP on some Android 4.9 kernels with rmnet
- return false;
- default:
- errno = EAFNOSUPPORT; // Address family not supported
- return ErrnoErrorf("Unknown hardware address type {} on interface {}", rv, interface);
- }
-}
-
-// TODO: use //system/netd/server/NetlinkCommands.cpp:openNetlinkSocket(protocol)
-// and //system/netd/server/SockDiag.cpp:checkError(fd)
-static int sendAndProcessNetlinkResponse(const void* req, int len) {
- base::unique_fd fd(socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE));
- if (fd == -1) {
- const int err = errno;
- ALOGE("socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE)");
- return -err;
- }
-
- static constexpr int on = 1;
- int rv = setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, &on, sizeof(on));
- if (rv) ALOGE("setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, %d)", on);
-
- // this is needed to get sane strace netlink parsing, it allocates the pid
- rv = bind(fd, (const struct sockaddr*)&KERNEL_NLADDR, sizeof(KERNEL_NLADDR));
- if (rv) {
- const int err = errno;
- ALOGE("bind(fd, {AF_NETLINK, 0, 0})");
- return -err;
- }
-
- // we do not want to receive messages from anyone besides the kernel
- rv = connect(fd, (const struct sockaddr*)&KERNEL_NLADDR, sizeof(KERNEL_NLADDR));
- if (rv) {
- const int err = errno;
- ALOGE("connect(fd, {AF_NETLINK, 0, 0})");
- return -err;
- }
-
- rv = send(fd, req, len, 0);
- if (rv == -1) return -errno;
- if (rv != len) return -EMSGSIZE;
-
- struct {
- nlmsghdr h;
- nlmsgerr e;
- char buf[256];
- } resp = {};
-
- rv = recv(fd, &resp, sizeof(resp), MSG_TRUNC);
-
- if (rv == -1) {
- const int err = errno;
- ALOGE("recv() failed");
- return -err;
- }
-
- if (rv < (int)NLMSG_SPACE(sizeof(struct nlmsgerr))) {
- ALOGE("recv() returned short packet: %d", rv);
- return -EMSGSIZE;
- }
-
- if (resp.h.nlmsg_len != (unsigned)rv) {
- ALOGE("recv() returned invalid header length: %d != %d", resp.h.nlmsg_len, rv);
- return -EBADMSG;
- }
-
- if (resp.h.nlmsg_type != NLMSG_ERROR) {
- ALOGE("recv() did not return NLMSG_ERROR message: %d", resp.h.nlmsg_type);
- return -EBADMSG;
- }
-
- return resp.e.error; // returns 0 on success
-}
-
-// ADD: nlMsgType=RTM_NEWQDISC nlMsgFlags=NLM_F_EXCL|NLM_F_CREATE
-// REPLACE: nlMsgType=RTM_NEWQDISC nlMsgFlags=NLM_F_CREATE|NLM_F_REPLACE
-// DEL: nlMsgType=RTM_DELQDISC nlMsgFlags=0
-int doTcQdiscClsact(int ifIndex, uint16_t nlMsgType, uint16_t nlMsgFlags) {
- // This is the name of the qdisc we are attaching.
- // Some hoop jumping to make this compile time constant with known size,
- // so that the structure declaration is well defined at compile time.
-#define CLSACT "clsact"
- // sizeof() includes the terminating NULL
- static constexpr size_t ASCIIZ_LEN_CLSACT = sizeof(CLSACT);
-
- const struct {
- nlmsghdr n;
- tcmsg t;
- struct {
- nlattr attr;
- char str[NLMSG_ALIGN(ASCIIZ_LEN_CLSACT)];
- } kind;
- } req = {
- .n =
- {
- .nlmsg_len = sizeof(req),
- .nlmsg_type = nlMsgType,
- .nlmsg_flags = static_cast<__u16>(NETLINK_REQUEST_FLAGS | nlMsgFlags),
- },
- .t =
- {
- .tcm_family = AF_UNSPEC,
- .tcm_ifindex = ifIndex,
- .tcm_handle = TC_H_MAKE(TC_H_CLSACT, 0),
- .tcm_parent = TC_H_CLSACT,
- },
- .kind =
- {
- .attr =
- {
- .nla_len = NLA_HDRLEN + ASCIIZ_LEN_CLSACT,
- .nla_type = TCA_KIND,
- },
- .str = CLSACT,
- },
- };
-#undef CLSACT
-
- return sendAndProcessNetlinkResponse(&req, sizeof(req));
-}
-
-// tc filter add dev .. in/egress prio 4 protocol ipv6/ip bpf object-pinned /sys/fs/bpf/...
-// direct-action
-int tcFilterAddDevBpf(int ifIndex, bool ingress, uint16_t proto, int bpfFd, bool ethernet) {
- // This is the name of the filter we're attaching (ie. this is the 'bpf'
- // packet classifier enabled by kernel config option CONFIG_NET_CLS_BPF.
- //
- // We go through some hoops in order to make this compile time constants
- // so that we can define the struct further down the function with the
- // field for this sized correctly already during the build.
-#define BPF "bpf"
- // sizeof() includes the terminating NULL
- static constexpr size_t ASCIIZ_LEN_BPF = sizeof(BPF);
-
- // This is to replicate program name suffix used by 'tc' Linux cli
- // when it attaches programs.
-#define FSOBJ_SUFFIX ":[*fsobj]"
-
- // This macro expands (from header files) to:
- // prog_clatd_schedcls_ingress6_clat_rawip:[*fsobj]
- // and is the name of the pinned ingress ebpf program for ARPHRD_RAWIP interfaces.
- // (also compatible with anything that has 0 size L2 header)
- static constexpr char name_clat_rx_rawip[] = CLAT_INGRESS6_PROG_RAWIP_NAME FSOBJ_SUFFIX;
-
- // This macro expands (from header files) to:
- // prog_clatd_schedcls_ingress6_clat_ether:[*fsobj]
- // and is the name of the pinned ingress ebpf program for ARPHRD_ETHER interfaces.
- // (also compatible with anything that has standard ethernet header)
- static constexpr char name_clat_rx_ether[] = CLAT_INGRESS6_PROG_ETHER_NAME FSOBJ_SUFFIX;
-
- // This macro expands (from header files) to:
- // prog_clatd_schedcls_egress4_clat_rawip:[*fsobj]
- // and is the name of the pinned egress ebpf program for ARPHRD_RAWIP interfaces.
- // (also compatible with anything that has 0 size L2 header)
- static constexpr char name_clat_tx_rawip[] = CLAT_EGRESS4_PROG_RAWIP_NAME FSOBJ_SUFFIX;
-
- // This macro expands (from header files) to:
- // prog_clatd_schedcls_egress4_clat_ether:[*fsobj]
- // and is the name of the pinned egress ebpf program for ARPHRD_ETHER interfaces.
- // (also compatible with anything that has standard ethernet header)
- static constexpr char name_clat_tx_ether[] = CLAT_EGRESS4_PROG_ETHER_NAME FSOBJ_SUFFIX;
-
-#undef FSOBJ_SUFFIX
-
- // The actual name we'll use is determined at run time via 'ethernet' and 'ingress'
- // booleans. We need to compile time allocate enough space in the struct
- // hence this macro magic to make sure we have enough space for either
- // possibility. In practice some of these are actually the same size.
- static constexpr size_t ASCIIZ_MAXLEN_NAME = max({
- sizeof(name_clat_rx_rawip),
- sizeof(name_clat_rx_ether),
- sizeof(name_clat_tx_rawip),
- sizeof(name_clat_tx_ether),
- });
-
- // These are not compile time constants: 'name' is used in strncpy below
- const char* const name_clat_rx = ethernet ? name_clat_rx_ether : name_clat_rx_rawip;
- const char* const name_clat_tx = ethernet ? name_clat_tx_ether : name_clat_tx_rawip;
- const char* const name = ingress ? name_clat_rx : name_clat_tx;
-
- struct {
- nlmsghdr n;
- tcmsg t;
- struct {
- nlattr attr;
- char str[NLMSG_ALIGN(ASCIIZ_LEN_BPF)];
- } kind;
- struct {
- nlattr attr;
- struct {
- nlattr attr;
- __u32 u32;
- } fd;
- struct {
- nlattr attr;
- char str[NLMSG_ALIGN(ASCIIZ_MAXLEN_NAME)];
- } name;
- struct {
- nlattr attr;
- __u32 u32;
- } flags;
- } options;
- } req = {
- .n =
- {
- .nlmsg_len = sizeof(req),
- .nlmsg_type = RTM_NEWTFILTER,
- .nlmsg_flags = NETLINK_REQUEST_FLAGS | NLM_F_EXCL | NLM_F_CREATE,
- },
- .t =
- {
- .tcm_family = AF_UNSPEC,
- .tcm_ifindex = ifIndex,
- .tcm_handle = TC_H_UNSPEC,
- .tcm_parent = TC_H_MAKE(TC_H_CLSACT,
- ingress ? TC_H_MIN_INGRESS : TC_H_MIN_EGRESS),
- .tcm_info = static_cast<__u32>((PRIO_CLAT << 16) | htons(proto)),
- },
- .kind =
- {
- .attr =
- {
- .nla_len = sizeof(req.kind),
- .nla_type = TCA_KIND,
- },
- .str = BPF,
- },
- .options =
- {
- .attr =
- {
- .nla_len = sizeof(req.options),
- .nla_type = NLA_F_NESTED | TCA_OPTIONS,
- },
- .fd =
- {
- .attr =
- {
- .nla_len = sizeof(req.options.fd),
- .nla_type = TCA_BPF_FD,
- },
- .u32 = static_cast<__u32>(bpfFd),
- },
- .name =
- {
- .attr =
- {
- .nla_len = sizeof(req.options.name),
- .nla_type = TCA_BPF_NAME,
- },
- // Visible via 'tc filter show', but
- // is overwritten by strncpy below
- .str = "placeholder",
- },
- .flags =
- {
- .attr =
- {
- .nla_len = sizeof(req.options.flags),
- .nla_type = TCA_BPF_FLAGS,
- },
- .u32 = TCA_BPF_FLAG_ACT_DIRECT,
- },
- },
- };
-#undef BPF
-
- strncpy(req.options.name.str, name, sizeof(req.options.name.str));
-
- return sendAndProcessNetlinkResponse(&req, sizeof(req));
-}
-
-// tc filter del dev .. in/egress prio 4 protocol ..
-int tcFilterDelDev(int ifIndex, bool ingress, uint16_t prio, uint16_t proto) {
- const struct {
- nlmsghdr n;
- tcmsg t;
- } req = {
- .n =
- {
- .nlmsg_len = sizeof(req),
- .nlmsg_type = RTM_DELTFILTER,
- .nlmsg_flags = NETLINK_REQUEST_FLAGS,
- },
- .t =
- {
- .tcm_family = AF_UNSPEC,
- .tcm_ifindex = ifIndex,
- .tcm_handle = TC_H_UNSPEC,
- .tcm_parent = TC_H_MAKE(TC_H_CLSACT,
- ingress ? TC_H_MIN_INGRESS : TC_H_MIN_EGRESS),
- .tcm_info = (static_cast<uint32_t>(prio) << 16) |
- static_cast<uint32_t>(htons(proto)),
- },
- };
-
- return sendAndProcessNetlinkResponse(&req, sizeof(req));
-}
-
-} // namespace net
-} // namespace android
diff --git a/service/native/libs/libclat/TcUtilsTest.cpp b/service/native/libs/libclat/TcUtilsTest.cpp
deleted file mode 100644
index 08f3042..0000000
--- a/service/native/libs/libclat/TcUtilsTest.cpp
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * Copyright 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.
- *
- * TcUtilsTest.cpp - unit tests for TcUtils.cpp
- */
-
-#include <gtest/gtest.h>
-
-#include "libclat/TcUtils.h"
-
-#include <linux/if_arp.h>
-#include <stdlib.h>
-#include <sys/wait.h>
-
-#include "bpf/BpfUtils.h"
-#include "bpf_shared.h"
-
-namespace android {
-namespace net {
-
-class TcUtilsTest : public ::testing::Test {
- public:
- void SetUp() {}
-};
-
-TEST_F(TcUtilsTest, HardwareAddressTypeOfNonExistingIf) {
- ASSERT_EQ(-ENODEV, hardwareAddressType("not_existing_if"));
-}
-
-TEST_F(TcUtilsTest, HardwareAddressTypeOfLoopback) {
- ASSERT_EQ(ARPHRD_LOOPBACK, hardwareAddressType("lo"));
-}
-
-// If wireless 'wlan0' interface exists it should be Ethernet.
-TEST_F(TcUtilsTest, HardwareAddressTypeOfWireless) {
- int type = hardwareAddressType("wlan0");
- if (type == -ENODEV) return;
-
- ASSERT_EQ(ARPHRD_ETHER, type);
-}
-
-// If cellular 'rmnet_data0' interface exists it should
-// *probably* not be Ethernet and instead be RawIp.
-TEST_F(TcUtilsTest, HardwareAddressTypeOfCellular) {
- int type = hardwareAddressType("rmnet_data0");
- if (type == -ENODEV) return;
-
- ASSERT_NE(ARPHRD_ETHER, type);
-
- // ARPHRD_RAWIP is 530 on some pre-4.14 Qualcomm devices.
- if (type == 530) return;
-
- ASSERT_EQ(ARPHRD_RAWIP, type);
-}
-
-TEST_F(TcUtilsTest, IsEthernetOfNonExistingIf) {
- auto res = isEthernet("not_existing_if");
- ASSERT_FALSE(res.ok());
- ASSERT_EQ(ENODEV, res.error().code());
-}
-
-TEST_F(TcUtilsTest, IsEthernetOfLoopback) {
- auto res = isEthernet("lo");
- ASSERT_FALSE(res.ok());
- ASSERT_EQ(EAFNOSUPPORT, res.error().code());
-}
-
-// If wireless 'wlan0' interface exists it should be Ethernet.
-// See also HardwareAddressTypeOfWireless.
-TEST_F(TcUtilsTest, IsEthernetOfWireless) {
- auto res = isEthernet("wlan0");
- if (!res.ok() && res.error().code() == ENODEV) return;
-
- ASSERT_RESULT_OK(res);
- ASSERT_TRUE(res.value());
-}
-
-// If cellular 'rmnet_data0' interface exists it should
-// *probably* not be Ethernet and instead be RawIp.
-// See also HardwareAddressTypeOfCellular.
-TEST_F(TcUtilsTest, IsEthernetOfCellular) {
- auto res = isEthernet("rmnet_data0");
- if (!res.ok() && res.error().code() == ENODEV) return;
-
- ASSERT_RESULT_OK(res);
- ASSERT_FALSE(res.value());
-}
-
-TEST_F(TcUtilsTest, DeviceMTUOfNonExistingIf) {
- ASSERT_EQ(-ENODEV, deviceMTU("not_existing_if"));
-}
-
-TEST_F(TcUtilsTest, DeviceMTUofLoopback) {
- ASSERT_EQ(65536, deviceMTU("lo"));
-}
-
-TEST_F(TcUtilsTest, GetClatEgress4MapFd) {
- int fd = getClatEgress4MapFd();
- ASSERT_GE(fd, 3); // 0,1,2 - stdin/out/err, thus fd >= 3
- EXPECT_EQ(FD_CLOEXEC, fcntl(fd, F_GETFD));
- close(fd);
-}
-
-TEST_F(TcUtilsTest, GetClatEgress4RawIpProgFd) {
- int fd = getClatEgress4ProgFd(RAWIP);
- ASSERT_GE(fd, 3);
- EXPECT_EQ(FD_CLOEXEC, fcntl(fd, F_GETFD));
- close(fd);
-}
-
-TEST_F(TcUtilsTest, GetClatEgress4EtherProgFd) {
- int fd = getClatEgress4ProgFd(ETHER);
- ASSERT_GE(fd, 3);
- EXPECT_EQ(FD_CLOEXEC, fcntl(fd, F_GETFD));
- close(fd);
-}
-
-TEST_F(TcUtilsTest, GetClatIngress6MapFd) {
- int fd = getClatIngress6MapFd();
- ASSERT_GE(fd, 3); // 0,1,2 - stdin/out/err, thus fd >= 3
- EXPECT_EQ(FD_CLOEXEC, fcntl(fd, F_GETFD));
- close(fd);
-}
-
-TEST_F(TcUtilsTest, GetClatIngress6RawIpProgFd) {
- int fd = getClatIngress6ProgFd(RAWIP);
- ASSERT_GE(fd, 3);
- EXPECT_EQ(FD_CLOEXEC, fcntl(fd, F_GETFD));
- close(fd);
-}
-
-TEST_F(TcUtilsTest, GetClatIngress6EtherProgFd) {
- int fd = getClatIngress6ProgFd(ETHER);
- ASSERT_GE(fd, 3);
- EXPECT_EQ(FD_CLOEXEC, fcntl(fd, F_GETFD));
- close(fd);
-}
-
-// See Linux kernel source in include/net/flow.h
-#define LOOPBACK_IFINDEX 1
-
-TEST_F(TcUtilsTest, AttachReplaceDetachClsactLo) {
- // This attaches and detaches a configuration-less and thus no-op clsact
- // qdisc to loopback interface (and it takes fractions of a second)
- EXPECT_EQ(0, tcQdiscAddDevClsact(LOOPBACK_IFINDEX));
- EXPECT_EQ(0, tcQdiscReplaceDevClsact(LOOPBACK_IFINDEX));
- EXPECT_EQ(0, tcQdiscDelDevClsact(LOOPBACK_IFINDEX));
- EXPECT_EQ(-EINVAL, tcQdiscDelDevClsact(LOOPBACK_IFINDEX));
-}
-
-static void checkAttachDetachBpfFilterClsactLo(const bool ingress, const bool ethernet) {
- // Older kernels return EINVAL instead of ENOENT due to lacking proper error propagation...
- const int errNOENT = android::bpf::isAtLeastKernelVersion(4, 19, 0) ? ENOENT : EINVAL;
-
- int clatBpfFd = ingress ? getClatIngress6ProgFd(ethernet) : getClatEgress4ProgFd(ethernet);
- ASSERT_GE(clatBpfFd, 3);
-
- // This attaches and detaches a clsact plus ebpf program to loopback
- // interface, but it should not affect traffic by virtue of us not
- // actually populating the ebpf control map.
- // Furthermore: it only takes fractions of a second.
- EXPECT_EQ(-EINVAL, tcFilterDelDevIngressClatIpv6(LOOPBACK_IFINDEX));
- EXPECT_EQ(-EINVAL, tcFilterDelDevEgressClatIpv4(LOOPBACK_IFINDEX));
- EXPECT_EQ(0, tcQdiscAddDevClsact(LOOPBACK_IFINDEX));
- EXPECT_EQ(-errNOENT, tcFilterDelDevIngressClatIpv6(LOOPBACK_IFINDEX));
- EXPECT_EQ(-errNOENT, tcFilterDelDevEgressClatIpv4(LOOPBACK_IFINDEX));
- if (ingress) {
- EXPECT_EQ(0, tcFilterAddDevIngressClatIpv6(LOOPBACK_IFINDEX, clatBpfFd, ethernet));
- EXPECT_EQ(0, tcFilterDelDevIngressClatIpv6(LOOPBACK_IFINDEX));
- } else {
- EXPECT_EQ(0, tcFilterAddDevEgressClatIpv4(LOOPBACK_IFINDEX, clatBpfFd, ethernet));
- EXPECT_EQ(0, tcFilterDelDevEgressClatIpv4(LOOPBACK_IFINDEX));
- }
- EXPECT_EQ(-errNOENT, tcFilterDelDevIngressClatIpv6(LOOPBACK_IFINDEX));
- EXPECT_EQ(-errNOENT, tcFilterDelDevEgressClatIpv4(LOOPBACK_IFINDEX));
- EXPECT_EQ(0, tcQdiscDelDevClsact(LOOPBACK_IFINDEX));
- EXPECT_EQ(-EINVAL, tcFilterDelDevIngressClatIpv6(LOOPBACK_IFINDEX));
- EXPECT_EQ(-EINVAL, tcFilterDelDevEgressClatIpv4(LOOPBACK_IFINDEX));
-
- close(clatBpfFd);
-}
-
-TEST_F(TcUtilsTest, CheckAttachBpfFilterRawIpClsactEgressLo) {
- checkAttachDetachBpfFilterClsactLo(EGRESS, RAWIP);
-}
-
-TEST_F(TcUtilsTest, CheckAttachBpfFilterEthernetClsactEgressLo) {
- checkAttachDetachBpfFilterClsactLo(EGRESS, ETHER);
-}
-
-TEST_F(TcUtilsTest, CheckAttachBpfFilterRawIpClsactIngressLo) {
- checkAttachDetachBpfFilterClsactLo(INGRESS, RAWIP);
-}
-
-TEST_F(TcUtilsTest, CheckAttachBpfFilterEthernetClsactIngressLo) {
- checkAttachDetachBpfFilterClsactLo(INGRESS, ETHER);
-}
-
-} // namespace net
-} // namespace android
diff --git a/service/native/libs/libclat/bpfhelper.cpp b/service/native/libs/libclat/bpfhelper.cpp
deleted file mode 100644
index 00785ad..0000000
--- a/service/native/libs/libclat/bpfhelper.cpp
+++ /dev/null
@@ -1,221 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * main.c - main function
- */
-#define LOG_TAG "bpfhelper"
-
-#include "libclat/bpfhelper.h"
-
-#include <android-base/unique_fd.h>
-#include <log/log.h>
-
-#include "bpf/BpfMap.h"
-#include "libclat/TcUtils.h"
-
-#define DEVICEPREFIX "v4-"
-
-using android::base::unique_fd;
-using android::bpf::BpfMap;
-
-BpfMap<ClatEgress4Key, ClatEgress4Value> mClatEgress4Map;
-BpfMap<ClatIngress6Key, ClatIngress6Value> mClatIngress6Map;
-
-namespace android {
-namespace net {
-namespace clat {
-
-// TODO: have a clearMap function to remove all stubs while system server crash.
-// For long term, move bpf access into java and map initialization should live
-// ClatCoordinator constructor.
-int initMaps(void) {
- int rv = getClatEgress4MapFd();
- if (rv < 0) {
- ALOGE("getClatEgress4MapFd() failure: %s", strerror(-rv));
- return -rv;
- }
- mClatEgress4Map.reset(rv);
-
- rv = getClatIngress6MapFd();
- if (rv < 0) {
- ALOGE("getClatIngress6MapFd() failure: %s", strerror(-rv));
- mClatEgress4Map.reset(-1);
- return -rv;
- }
- mClatIngress6Map.reset(rv);
-
- return 0;
-}
-
-void maybeStartBpf(const ClatdTracker& tracker) {
- auto isEthernet = android::net::isEthernet(tracker.iface);
- if (!isEthernet.ok()) {
- ALOGE("isEthernet(%s[%d]) failure: %s", tracker.iface, tracker.ifIndex,
- isEthernet.error().message().c_str());
- return;
- }
-
- // This program will be attached to the v4-* interface which is a TUN and thus always rawip.
- int rv = getClatEgress4ProgFd(RAWIP);
- if (rv < 0) {
- ALOGE("getClatEgress4ProgFd(RAWIP) failure: %s", strerror(-rv));
- return;
- }
- unique_fd txRawIpProgFd(rv);
-
- rv = getClatIngress6ProgFd(isEthernet.value());
- if (rv < 0) {
- ALOGE("getClatIngress6ProgFd(%d) failure: %s", isEthernet.value(), strerror(-rv));
- return;
- }
- unique_fd rxProgFd(rv);
-
- ClatEgress4Key txKey = {
- .iif = tracker.v4ifIndex,
- .local4 = tracker.v4,
- };
- ClatEgress4Value txValue = {
- .oif = tracker.ifIndex,
- .local6 = tracker.v6,
- .pfx96 = tracker.pfx96,
- .oifIsEthernet = isEthernet.value(),
- };
-
- auto ret = mClatEgress4Map.writeValue(txKey, txValue, BPF_ANY);
- if (!ret.ok()) {
- ALOGE("mClatEgress4Map.writeValue failure: %s", strerror(ret.error().code()));
- return;
- }
-
- ClatIngress6Key rxKey = {
- .iif = tracker.ifIndex,
- .pfx96 = tracker.pfx96,
- .local6 = tracker.v6,
- };
- ClatIngress6Value rxValue = {
- // TODO: move all the clat code to eBPF and remove the tun interface entirely.
- .oif = tracker.v4ifIndex,
- .local4 = tracker.v4,
- };
-
- ret = mClatIngress6Map.writeValue(rxKey, rxValue, BPF_ANY);
- if (!ret.ok()) {
- ALOGE("mClatIngress6Map.writeValue failure: %s", strerror(ret.error().code()));
- ret = mClatEgress4Map.deleteValue(txKey);
- if (!ret.ok())
- ALOGE("mClatEgress4Map.deleteValue failure: %s", strerror(ret.error().code()));
- return;
- }
-
- // We do tc setup *after* populating the maps, so scanning through them
- // can always be used to tell us what needs cleanup.
-
- // Usually the clsact will be added in RouteController::addInterfaceToPhysicalNetwork.
- // But clat is started before the v4- interface is added to the network. The clat startup have
- // to add clsact of v4- tun interface first for adding bpf filter in maybeStartBpf.
- // TODO: move "qdisc add clsact" of v4- tun interface out from ClatdController.
- rv = tcQdiscAddDevClsact(tracker.v4ifIndex);
- if (rv) {
- ALOGE("tcQdiscAddDevClsact(%d[%s]) failure: %s", tracker.v4ifIndex, tracker.v4iface,
- strerror(-rv));
- ret = mClatEgress4Map.deleteValue(txKey);
- if (!ret.ok())
- ALOGE("mClatEgress4Map.deleteValue failure: %s", strerror(ret.error().code()));
- ret = mClatIngress6Map.deleteValue(rxKey);
- if (!ret.ok())
- ALOGE("mClatIngress6Map.deleteValue failure: %s", strerror(ret.error().code()));
- return;
- }
-
- rv = tcFilterAddDevEgressClatIpv4(tracker.v4ifIndex, txRawIpProgFd, RAWIP);
- if (rv) {
- ALOGE("tcFilterAddDevEgressClatIpv4(%d[%s], RAWIP) failure: %s", tracker.v4ifIndex,
- tracker.v4iface, strerror(-rv));
-
- // The v4- interface clsact is not deleted for unwinding error because once it is created
- // with interface addition, the lifetime is till interface deletion. Moreover, the clsact
- // has no clat filter now. It should not break anything.
-
- ret = mClatEgress4Map.deleteValue(txKey);
- if (!ret.ok())
- ALOGE("mClatEgress4Map.deleteValue failure: %s", strerror(ret.error().code()));
- ret = mClatIngress6Map.deleteValue(rxKey);
- if (!ret.ok())
- ALOGE("mClatIngress6Map.deleteValue failure: %s", strerror(ret.error().code()));
- return;
- }
-
- rv = tcFilterAddDevIngressClatIpv6(tracker.ifIndex, rxProgFd, isEthernet.value());
- if (rv) {
- ALOGE("tcFilterAddDevIngressClatIpv6(%d[%s], %d) failure: %s", tracker.ifIndex,
- tracker.iface, isEthernet.value(), strerror(-rv));
- rv = tcFilterDelDevEgressClatIpv4(tracker.v4ifIndex);
- if (rv) {
- ALOGE("tcFilterDelDevEgressClatIpv4(%d[%s]) failure: %s", tracker.v4ifIndex,
- tracker.v4iface, strerror(-rv));
- }
-
- // The v4- interface clsact is not deleted. See the reason in the error unwinding code of
- // the egress filter attaching of v4- tun interface.
-
- ret = mClatEgress4Map.deleteValue(txKey);
- if (!ret.ok())
- ALOGE("mClatEgress4Map.deleteValue failure: %s", strerror(ret.error().code()));
- ret = mClatIngress6Map.deleteValue(rxKey);
- if (!ret.ok())
- ALOGE("mClatIngress6Map.deleteValue failure: %s", strerror(ret.error().code()));
- return;
- }
-
- // success
-}
-
-void maybeStopBpf(const ClatdTracker& tracker) {
- int rv = tcFilterDelDevIngressClatIpv6(tracker.ifIndex);
- if (rv < 0) {
- ALOGE("tcFilterDelDevIngressClatIpv6(%d[%s]) failure: %s", tracker.ifIndex, tracker.iface,
- strerror(-rv));
- }
-
- rv = tcFilterDelDevEgressClatIpv4(tracker.v4ifIndex);
- if (rv < 0) {
- ALOGE("tcFilterDelDevEgressClatIpv4(%d[%s]) failure: %s", tracker.v4ifIndex,
- tracker.v4iface, strerror(-rv));
- }
-
- // We cleanup the maps last, so scanning through them can be used to
- // determine what still needs cleanup.
-
- ClatEgress4Key txKey = {
- .iif = tracker.v4ifIndex,
- .local4 = tracker.v4,
- };
-
- auto ret = mClatEgress4Map.deleteValue(txKey);
- if (!ret.ok()) ALOGE("mClatEgress4Map.deleteValue failure: %s", strerror(ret.error().code()));
-
- ClatIngress6Key rxKey = {
- .iif = tracker.ifIndex,
- .pfx96 = tracker.pfx96,
- .local6 = tracker.v6,
- };
-
- ret = mClatIngress6Map.deleteValue(rxKey);
- if (!ret.ok()) ALOGE("mClatIngress6Map.deleteValue failure: %s", strerror(ret.error().code()));
-}
-
-} // namespace clat
-} // namespace net
-} // namespace android
diff --git a/service/native/libs/libclat/include/libclat/TcUtils.h b/service/native/libs/libclat/include/libclat/TcUtils.h
deleted file mode 100644
index 212838e..0000000
--- a/service/native/libs/libclat/include/libclat/TcUtils.h
+++ /dev/null
@@ -1,117 +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.
- */
-
-#pragma once
-
-#include <android-base/result.h>
-#include <errno.h>
-#include <linux/if_ether.h>
-#include <linux/if_link.h>
-#include <linux/rtnetlink.h>
-
-#include <string>
-
-#include "bpf/BpfUtils.h"
-#include "bpf_shared.h"
-
-namespace android {
-namespace net {
-
-// For better code clarity - do not change values - used for booleans like
-// with_ethernet_header or isEthernet.
-constexpr bool RAWIP = false;
-constexpr bool ETHER = true;
-
-// For better code clarity when used for 'bool ingress' parameter.
-constexpr bool EGRESS = false;
-constexpr bool INGRESS = true;
-
-// The priority of clat hook - must be after tethering.
-constexpr uint16_t PRIO_CLAT = 4;
-
-// this returns an ARPHRD_* constant or a -errno
-int hardwareAddressType(const std::string& interface);
-
-// return MTU or -errno
-int deviceMTU(const std::string& interface);
-
-base::Result<bool> isEthernet(const std::string& interface);
-
-inline int getClatEgress4MapFd(void) {
- const int fd = bpf::mapRetrieveRW(CLAT_EGRESS4_MAP_PATH);
- return (fd == -1) ? -errno : fd;
-}
-
-inline int getClatEgress4ProgFd(bool with_ethernet_header) {
- const int fd = bpf::retrieveProgram(with_ethernet_header ? CLAT_EGRESS4_PROG_ETHER_PATH
- : CLAT_EGRESS4_PROG_RAWIP_PATH);
- return (fd == -1) ? -errno : fd;
-}
-
-inline int getClatIngress6MapFd(void) {
- const int fd = bpf::mapRetrieveRW(CLAT_INGRESS6_MAP_PATH);
- return (fd == -1) ? -errno : fd;
-}
-
-inline int getClatIngress6ProgFd(bool with_ethernet_header) {
- const int fd = bpf::retrieveProgram(with_ethernet_header ? CLAT_INGRESS6_PROG_ETHER_PATH
- : CLAT_INGRESS6_PROG_RAWIP_PATH);
- return (fd == -1) ? -errno : fd;
-}
-
-int doTcQdiscClsact(int ifIndex, uint16_t nlMsgType, uint16_t nlMsgFlags);
-
-inline int tcQdiscAddDevClsact(int ifIndex) {
- return doTcQdiscClsact(ifIndex, RTM_NEWQDISC, NLM_F_EXCL | NLM_F_CREATE);
-}
-
-inline int tcQdiscReplaceDevClsact(int ifIndex) {
- return doTcQdiscClsact(ifIndex, RTM_NEWQDISC, NLM_F_CREATE | NLM_F_REPLACE);
-}
-
-inline int tcQdiscDelDevClsact(int ifIndex) {
- return doTcQdiscClsact(ifIndex, RTM_DELQDISC, 0);
-}
-
-// tc filter add dev .. in/egress prio 4 protocol ipv6/ip bpf object-pinned /sys/fs/bpf/...
-// direct-action
-int tcFilterAddDevBpf(int ifIndex, bool ingress, uint16_t proto, int bpfFd, bool ethernet);
-
-// tc filter add dev .. ingress prio 4 protocol ipv6 bpf object-pinned /sys/fs/bpf/... direct-action
-inline int tcFilterAddDevIngressClatIpv6(int ifIndex, int bpfFd, bool ethernet) {
- return tcFilterAddDevBpf(ifIndex, INGRESS, ETH_P_IPV6, bpfFd, ethernet);
-}
-
-// tc filter add dev .. egress prio 4 protocol ip bpf object-pinned /sys/fs/bpf/... direct-action
-inline int tcFilterAddDevEgressClatIpv4(int ifIndex, int bpfFd, bool ethernet) {
- return tcFilterAddDevBpf(ifIndex, EGRESS, ETH_P_IP, bpfFd, ethernet);
-}
-
-// tc filter del dev .. in/egress prio .. protocol ..
-int tcFilterDelDev(int ifIndex, bool ingress, uint16_t prio, uint16_t proto);
-
-// tc filter del dev .. ingress prio 4 protocol ipv6
-inline int tcFilterDelDevIngressClatIpv6(int ifIndex) {
- return tcFilterDelDev(ifIndex, INGRESS, PRIO_CLAT, ETH_P_IPV6);
-}
-
-// tc filter del dev .. egress prio 4 protocol ip
-inline int tcFilterDelDevEgressClatIpv4(int ifIndex) {
- return tcFilterDelDev(ifIndex, EGRESS, PRIO_CLAT, ETH_P_IP);
-}
-
-} // namespace net
-} // namespace android
diff --git a/service/native/libs/libclat/include/libclat/bpfhelper.h b/service/native/libs/libclat/include/libclat/bpfhelper.h
deleted file mode 100644
index c0328c0..0000000
--- a/service/native/libs/libclat/include/libclat/bpfhelper.h
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright (C) 2021 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#pragma once
-
-#include <arpa/inet.h>
-#include <linux/if.h>
-
-namespace android {
-namespace net {
-namespace clat {
-
-struct ClatdTracker {
- unsigned ifIndex;
- char iface[IFNAMSIZ];
- unsigned v4ifIndex;
- char v4iface[IFNAMSIZ];
- in_addr v4;
- in6_addr v6;
- in6_addr pfx96;
-};
-
-int initMaps(void);
-void maybeStartBpf(const ClatdTracker& tracker);
-void maybeStopBpf(const ClatdTracker& tracker);
-
-} // namespace clat
-} // namespace net
-} // namespace android
diff --git a/service/proguard.flags b/service/proguard.flags
new file mode 100644
index 0000000..94397ab
--- /dev/null
+++ b/service/proguard.flags
@@ -0,0 +1,18 @@
+# Make sure proguard keeps all connectivity classes
+# TODO: instead of keeping everything, consider listing only "entry points"
+# (service loader, JNI registered methods, etc) and letting the optimizer do its job
+-keep class android.net.** { *; }
+-keep class com.android.connectivity.** { *; }
+-keep class com.android.net.** { *; }
+-keep class !com.android.server.nearby.**,com.android.server.** { *; }
+
+# Prevent proguard from stripping out any nearby-service and fast-pair-lite-protos fields.
+-keep class com.android.server.nearby.NearbyService { *; }
+-keep class com.android.server.nearby.service.proto { *; }
+
+# 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.connectivity.com.google.protobuf.MessageLite { <fields>; }
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index e58160a..8d8442f 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -34,6 +34,9 @@
import static android.net.ConnectivityManager.BLOCKED_REASON_LOCKDOWN_VPN;
import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
+import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
+import static android.net.ConnectivityManager.FIREWALL_RULE_DEFAULT;
+import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
import static android.net.ConnectivityManager.TYPE_BLUETOOTH;
import static android.net.ConnectivityManager.TYPE_ETHERNET;
import static android.net.ConnectivityManager.TYPE_MOBILE;
@@ -105,6 +108,7 @@
import android.app.AppOpsManager;
import android.app.BroadcastOptions;
import android.app.PendingIntent;
+import android.app.admin.DevicePolicyManager;
import android.app.usage.NetworkStatsManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -255,6 +259,7 @@
import com.android.net.module.util.netlink.InetDiagMessage;
import com.android.server.connectivity.AutodestructReference;
import com.android.server.connectivity.CarrierPrivilegeAuthenticator;
+import com.android.server.connectivity.ClatCoordinator;
import com.android.server.connectivity.ConnectivityFlags;
import com.android.server.connectivity.DnsManager;
import com.android.server.connectivity.DnsManager.PrivateDnsValidationUpdate;
@@ -605,13 +610,6 @@
// Handle private DNS validation status updates.
private static final int EVENT_PRIVATE_DNS_VALIDATION_UPDATE = 38;
- /**
- * used to remove a network request, either a listener or a real request and call unavailable
- * arg1 = UID of caller
- * obj = NetworkRequest
- */
- private static final int EVENT_RELEASE_NETWORK_REQUEST_AND_CALL_UNAVAILABLE = 39;
-
/**
* Event for NetworkMonitor/NetworkAgentInfo to inform ConnectivityService that the network has
* been tested.
@@ -750,7 +748,7 @@
* The BPF program attached to the tc-police hook to account for to-be-dropped traffic.
*/
private static final String TC_POLICE_BPF_PROG_PATH =
- "/sys/fs/bpf/prog_netd_schedact_ingress_account";
+ "/sys/fs/bpf/netd_shared/prog_netd_schedact_ingress_account";
private static String eventName(int what) {
return sMagicDecoderRing.get(what, Integer.toString(what));
@@ -1189,6 +1187,7 @@
/**
* Keeps track of the number of requests made under different uids.
*/
+ // TODO: Remove the hack and use com.android.net.module.util.PerUidCounter instead.
public static class PerUidCounter {
private final int mMaxCountPerUid;
@@ -1402,6 +1401,19 @@
}
/**
+ * @see ClatCoordinator
+ */
+ public ClatCoordinator getClatCoordinator(INetd netd) {
+ return new ClatCoordinator(
+ new ClatCoordinator.Dependencies() {
+ @NonNull
+ public INetd getNetd() {
+ return netd;
+ }
+ });
+ }
+
+ /**
* Wraps {@link TcUtils#tcFilterAddDevIngressPolice}
*/
public void enableIngressRateLimit(String iface, long rateInBytesPerSecond) {
@@ -2235,6 +2247,13 @@
callingAttributionTag);
}
+ private void redactUnderlyingNetworksForCapabilities(NetworkCapabilities nc, int pid, int uid) {
+ if (nc.getUnderlyingNetworks() != null
+ && !checkNetworkFactoryOrSettingsPermission(pid, uid)) {
+ nc.setUnderlyingNetworks(null);
+ }
+ }
+
@VisibleForTesting
NetworkCapabilities networkCapabilitiesRestrictedForCallerPermissions(
NetworkCapabilities nc, int callerPid, int callerUid) {
@@ -2247,8 +2266,6 @@
if (!checkSettingsPermission(callerPid, callerUid)) {
newNc.setUids(null);
newNc.setSSID(null);
- // TODO: Processes holding NETWORK_FACTORY should be able to see the underlying networks
- newNc.setUnderlyingNetworks(null);
}
if (newNc.getNetworkSpecifier() != null) {
newNc.setNetworkSpecifier(newNc.getNetworkSpecifier().redact());
@@ -2262,6 +2279,7 @@
newNc.setAllowedUids(new ArraySet<>());
newNc.setSubscriptionIds(Collections.emptySet());
}
+ redactUnderlyingNetworksForCapabilities(newNc, callerPid, callerUid);
return newNc;
}
@@ -2604,7 +2622,7 @@
verifyCallingUidAndPackage(callingPackageName, mDeps.getCallingUid());
enforceChangePermission(callingPackageName, callingAttributionTag);
if (mProtectedNetworks.contains(networkType)) {
- enforceConnectivityRestrictedNetworksPermission();
+ enforceConnectivityRestrictedNetworksPermission(true /* checkUidsAllowedList */);
}
InetAddress addr;
@@ -2855,12 +2873,16 @@
}
private void enforceNetworkFactoryPermission() {
+ // TODO: Check for the BLUETOOTH_STACK permission once that is in the API surface.
+ if (UserHandle.getAppId(getCallingUid()) == Process.BLUETOOTH_UID) return;
enforceAnyPermissionOf(
android.Manifest.permission.NETWORK_FACTORY,
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
}
private void enforceNetworkFactoryOrSettingsPermission() {
+ // TODO: Check for the BLUETOOTH_STACK permission once that is in the API surface.
+ if (UserHandle.getAppId(getCallingUid()) == Process.BLUETOOTH_UID) return;
enforceAnyPermissionOf(
android.Manifest.permission.NETWORK_SETTINGS,
android.Manifest.permission.NETWORK_FACTORY,
@@ -2868,12 +2890,24 @@
}
private void enforceNetworkFactoryOrTestNetworksPermission() {
+ // TODO: Check for the BLUETOOTH_STACK permission once that is in the API surface.
+ if (UserHandle.getAppId(getCallingUid()) == Process.BLUETOOTH_UID) return;
enforceAnyPermissionOf(
android.Manifest.permission.MANAGE_TEST_NETWORKS,
android.Manifest.permission.NETWORK_FACTORY,
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
}
+ private boolean checkNetworkFactoryOrSettingsPermission(int pid, int uid) {
+ return PERMISSION_GRANTED == mContext.checkPermission(
+ android.Manifest.permission.NETWORK_FACTORY, pid, uid)
+ || PERMISSION_GRANTED == mContext.checkPermission(
+ android.Manifest.permission.NETWORK_SETTINGS, pid, uid)
+ || PERMISSION_GRANTED == mContext.checkPermission(
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, pid, uid)
+ || UserHandle.getAppId(uid) == Process.BLUETOOTH_UID;
+ }
+
private boolean checkSettingsPermission() {
return checkAnyPermissionOf(
android.Manifest.permission.NETWORK_SETTINGS,
@@ -2942,18 +2976,35 @@
android.Manifest.permission.NETWORK_SETTINGS);
}
- private void enforceConnectivityRestrictedNetworksPermission() {
- try {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS,
- "ConnectivityService");
- return;
- } catch (SecurityException e) { /* fallback to ConnectivityInternalPermission */ }
- // TODO: Remove this fallback check after all apps have declared
- // CONNECTIVITY_USE_RESTRICTED_NETWORKS.
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.CONNECTIVITY_INTERNAL,
- "ConnectivityService");
+ private boolean checkConnectivityRestrictedNetworksPermission(int callingUid,
+ boolean checkUidsAllowedList) {
+ if (PermissionUtils.checkAnyPermissionOf(mContext,
+ android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS)) {
+ return true;
+ }
+
+ // fallback to ConnectivityInternalPermission
+ // TODO: Remove this fallback check after all apps have declared
+ // CONNECTIVITY_USE_RESTRICTED_NETWORKS.
+ if (PermissionUtils.checkAnyPermissionOf(mContext,
+ android.Manifest.permission.CONNECTIVITY_INTERNAL)) {
+ return true;
+ }
+
+ // Check whether uid is in allowed on restricted networks list.
+ if (checkUidsAllowedList
+ && mPermissionMonitor.isUidAllowedOnRestrictedNetworks(callingUid)) {
+ return true;
+ }
+ return false;
+ }
+
+ private void enforceConnectivityRestrictedNetworksPermission(boolean checkUidsAllowedList) {
+ final int callingUid = mDeps.getCallingUid();
+ if (!checkConnectivityRestrictedNetworksPermission(callingUid, checkUidsAllowedList)) {
+ throw new SecurityException("ConnectivityService: user " + callingUid
+ + " has no permission to access restricted network.");
+ }
}
private void enforceKeepalivePermission() {
@@ -3241,11 +3292,12 @@
return;
}
- pw.print("NetworkProviders for:");
+ pw.println("NetworkProviders for:");
+ pw.increaseIndent();
for (NetworkProviderInfo npi : mNetworkProviderInfos.values()) {
- pw.print(" " + npi.name);
+ pw.println(npi.providerId + ": " + npi.name);
}
- pw.println();
+ pw.decreaseIndent();
pw.println();
final NetworkAgentInfo defaultNai = getDefaultNetwork();
@@ -3294,6 +3346,14 @@
pw.decreaseIndent();
pw.println();
+ pw.println("Network Offers:");
+ pw.increaseIndent();
+ for (final NetworkOfferInfo offerInfo : mNetworkOffers) {
+ pw.println(offerInfo.offer);
+ }
+ pw.decreaseIndent();
+ pw.println();
+
mLegacyTypeTracker.dump(pw);
pw.println();
@@ -3368,6 +3428,10 @@
for (NetworkAgentInfo nai : networksSortedById()) {
pw.println(nai.toString());
pw.increaseIndent();
+ pw.println("Nat464Xlat:");
+ pw.increaseIndent();
+ nai.dumpNat464Xlat(pw);
+ pw.decreaseIndent();
pw.println(String.format(
"Requests: REQUEST:%d LISTEN:%d BACKGROUND_REQUEST:%d total:%d",
nai.numForegroundNetworkRequests(),
@@ -3644,11 +3708,11 @@
}
case NetworkAgent.EVENT_REMOVE_ALL_DSCP_POLICIES: {
if (mDscpPolicyTracker != null) {
- mDscpPolicyTracker.removeAllDscpPolicies(nai);
+ mDscpPolicyTracker.removeAllDscpPolicies(nai, true);
}
break;
}
- case NetworkAgent.EVENT_DESTROY_AND_AWAIT_REPLACEMENT: {
+ case NetworkAgent.EVENT_UNREGISTER_AFTER_REPLACEMENT: {
// If nai is not yet created, or is already destroyed, ignore.
if (!shouldDestroyNativeNetwork(nai)) break;
@@ -3808,7 +3872,6 @@
}
final boolean wasValidated = nai.lastValidated;
- final boolean wasDefault = isDefaultNetwork(nai);
final boolean wasPartial = nai.partialConnectivity;
nai.partialConnectivity = ((testResult & NETWORK_VALIDATION_RESULT_PARTIAL) != 0);
final boolean partialConnectivityChanged =
@@ -4213,7 +4276,7 @@
}
private boolean shouldIgnoreValidationFailureAfterRoam(NetworkAgentInfo nai) {
- // T+ devices should use destroyAndAwaitReplacement.
+ // T+ devices should use unregisterAfterReplacement.
if (SdkLevel.isAtLeastT()) return false;
final long blockTimeOut = Long.valueOf(mResources.get().getInteger(
R.integer.config_validationFailureAfterRoamIgnoreTimeMillis));
@@ -4385,6 +4448,9 @@
}
private void destroyNativeNetwork(@NonNull NetworkAgentInfo nai) {
+ if (mDscpPolicyTracker != null) {
+ mDscpPolicyTracker.removeAllDscpPolicies(nai, false);
+ }
try {
mNetd.networkDestroy(nai.network.getNetId());
} catch (RemoteException | ServiceSpecificException e) {
@@ -4439,7 +4505,7 @@
private boolean hasCarrierPrivilegeForNetworkCaps(final int callingUid,
@NonNull final NetworkCapabilities caps) {
- if (SdkLevel.isAtLeastT() && mCarrierPrivilegeAuthenticator != null) {
+ if (mCarrierPrivilegeAuthenticator != null) {
return mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
callingUid, caps);
}
@@ -4469,7 +4535,6 @@
private void handleRegisterNetworkRequests(@NonNull final Set<NetworkRequestInfo> nris) {
ensureRunningOnConnectivityServiceThread();
- NetworkRequest requestToBeReleased = null;
for (final NetworkRequestInfo nri : nris) {
mNetworkRequestInfoLogs.log("REGISTER " + nri);
checkNrisConsistency(nri);
@@ -4484,13 +4549,6 @@
}
}
}
- if (req.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) {
- if (!hasCarrierPrivilegeForNetworkCaps(nri.mUid, req.networkCapabilities)
- && !checkConnectivityRestrictedNetworksPermission(
- nri.mPid, nri.mUid)) {
- requestToBeReleased = req;
- }
- }
}
// If this NRI has a satisfier already, it is replacing an older request that
@@ -4502,11 +4560,6 @@
}
}
- if (requestToBeReleased != null) {
- releaseNetworkRequestAndCallOnUnavailable(requestToBeReleased);
- return;
- }
-
if (mFlags.noRematchAllRequestsOnRegister()) {
rematchNetworksAndRequests(nris);
} else {
@@ -5346,11 +5399,6 @@
/* callOnUnavailable */ false);
break;
}
- case EVENT_RELEASE_NETWORK_REQUEST_AND_CALL_UNAVAILABLE: {
- handleReleaseNetworkRequest((NetworkRequest) msg.obj, msg.arg1,
- /* callOnUnavailable */ true);
- break;
- }
case EVENT_SET_ACCEPT_UNVALIDATED: {
Network network = (Network) msg.obj;
handleSetAcceptUnvalidated(network, toBool(msg.arg1), toBool(msg.arg2));
@@ -5920,6 +5968,10 @@
+ Arrays.toString(ranges) + "): netd command failed: " + e);
}
+ if (SdkLevel.isAtLeastT()) {
+ mPermissionMonitor.updateVpnLockdownUidRanges(requireVpn, ranges);
+ }
+
for (final NetworkAgentInfo nai : mNetworkAgentInfos) {
final boolean curMetered = nai.networkCapabilities.isMetered();
maybeNotifyNetworkBlocked(nai, curMetered, curMetered,
@@ -6569,7 +6621,7 @@
case REQUEST:
networkCapabilities = new NetworkCapabilities(networkCapabilities);
enforceNetworkRequestPermissions(networkCapabilities, callingPackageName,
- callingAttributionTag);
+ callingAttributionTag, callingUid);
// TODO: this is incorrect. We mark the request as metered or not depending on
// the state of the app when the request is filed, but we never change the
// request if the app changes network state. http://b/29964605
@@ -6659,26 +6711,19 @@
}
private void enforceNetworkRequestPermissions(NetworkCapabilities networkCapabilities,
- String callingPackageName, String callingAttributionTag) {
+ String callingPackageName, String callingAttributionTag, final int callingUid) {
if (networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED) == false) {
- if (!networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) {
- enforceConnectivityRestrictedNetworksPermission();
+ // For T+ devices, callers with carrier privilege could request with CBS capabilities.
+ if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)
+ && hasCarrierPrivilegeForNetworkCaps(callingUid, networkCapabilities)) {
+ return;
}
+ enforceConnectivityRestrictedNetworksPermission(true /* checkUidsAllowedList */);
} else {
enforceChangePermission(callingPackageName, callingAttributionTag);
}
}
- private boolean checkConnectivityRestrictedNetworksPermission(int callerPid, int callerUid) {
- if (checkAnyPermissionOf(callerPid, callerUid,
- android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS)
- || checkAnyPermissionOf(callerPid, callerUid,
- android.Manifest.permission.CONNECTIVITY_INTERNAL)) {
- return true;
- }
- return false;
- }
-
@Override
public boolean requestBandwidthUpdate(Network network) {
enforceAccessPermission();
@@ -6737,7 +6782,7 @@
final int callingUid = mDeps.getCallingUid();
networkCapabilities = new NetworkCapabilities(networkCapabilities);
enforceNetworkRequestPermissions(networkCapabilities, callingPackageName,
- callingAttributionTag);
+ callingAttributionTag, callingUid);
enforceMeteredApnPolicy(networkCapabilities);
ensureRequestableCapabilities(networkCapabilities);
ensureSufficientPermissionsForRequest(networkCapabilities,
@@ -6860,13 +6905,6 @@
EVENT_RELEASE_NETWORK_REQUEST, mDeps.getCallingUid(), 0, networkRequest));
}
- private void releaseNetworkRequestAndCallOnUnavailable(NetworkRequest networkRequest) {
- ensureNetworkRequestHasType(networkRequest);
- mHandler.sendMessage(mHandler.obtainMessage(
- EVENT_RELEASE_NETWORK_REQUEST_AND_CALL_UNAVAILABLE, mDeps.getCallingUid(), 0,
- networkRequest));
- }
-
private void handleRegisterNetworkProvider(NetworkProviderInfo npi) {
if (mNetworkProviderInfos.containsKey(npi.messenger)) {
// Avoid creating duplicates. even if an app makes a direct AIDL call.
@@ -7697,10 +7735,10 @@
private void updateVpnFiltering(LinkProperties newLp, LinkProperties oldLp,
NetworkAgentInfo nai) {
- final String oldIface = oldLp != null ? oldLp.getInterfaceName() : null;
- final String newIface = newLp != null ? newLp.getInterfaceName() : null;
- final boolean wasFiltering = requiresVpnIsolation(nai, nai.networkCapabilities, oldLp);
- final boolean needsFiltering = requiresVpnIsolation(nai, nai.networkCapabilities, newLp);
+ final String oldIface = getVpnIsolationInterface(nai, nai.networkCapabilities, oldLp);
+ final String newIface = getVpnIsolationInterface(nai, nai.networkCapabilities, newLp);
+ final boolean wasFiltering = requiresVpnAllowRule(nai, oldLp, oldIface);
+ final boolean needsFiltering = requiresVpnAllowRule(nai, newLp, newIface);
if (!wasFiltering && !needsFiltering) {
// Nothing to do.
@@ -7713,11 +7751,19 @@
}
final Set<UidRange> ranges = nai.networkCapabilities.getUidRanges();
+ if (ranges == null || ranges.isEmpty()) {
+ return;
+ }
+
final int vpnAppUid = nai.networkCapabilities.getOwnerUid();
// TODO: this create a window of opportunity for apps to receive traffic between the time
// when the old rules are removed and the time when new rules are added. To fix this,
// make eBPF support two allowlisted interfaces so here new rules can be added before the
// old rules are being removed.
+
+ // Null iface given to onVpnUidRangesAdded/Removed is a wildcard to allow apps to receive
+ // packets on all interfaces. This is required to accept incoming traffic in Lockdown mode
+ // by overriding the Lockdown blocking rule.
if (wasFiltering) {
mPermissionMonitor.onVpnUidRangesRemoved(oldIface, ranges, vpnAppUid);
}
@@ -7778,6 +7824,7 @@
}
nai.declaredCapabilities = new NetworkCapabilities(nc);
NetworkAgentInfo.restrictCapabilitiesFromNetworkAgent(nc, nai.creatorUid,
+ mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE),
mCarrierPrivilegeAuthenticator);
}
@@ -8006,15 +8053,14 @@
}
/**
- * Returns whether VPN isolation (ingress interface filtering) should be applied on the given
- * network.
+ * Returns the interface which requires VPN isolation (ingress interface filtering).
*
* Ingress interface filtering enforces that all apps under the given network can only receive
* packets from the network's interface (and loopback). This is important for VPNs because
* apps that cannot bypass a fully-routed VPN shouldn't be able to receive packets from any
* non-VPN interfaces.
*
- * As a result, this method should return true iff
+ * As a result, this method should return Non-null interface iff
* 1. the network is an app VPN (not legacy VPN)
* 2. the VPN does not allow bypass
* 3. the VPN is fully-routed
@@ -8023,15 +8069,32 @@
* @see INetd#firewallAddUidInterfaceRules
* @see INetd#firewallRemoveUidInterfaceRules
*/
- private boolean requiresVpnIsolation(@NonNull NetworkAgentInfo nai, NetworkCapabilities nc,
+ @Nullable
+ private String getVpnIsolationInterface(@NonNull NetworkAgentInfo nai, NetworkCapabilities nc,
LinkProperties lp) {
- if (nc == null || lp == null) return false;
- return nai.isVPN()
+ if (nc == null || lp == null) return null;
+ if (nai.isVPN()
&& !nai.networkAgentConfig.allowBypass
&& nc.getOwnerUid() != Process.SYSTEM_UID
&& lp.getInterfaceName() != null
&& (lp.hasIpv4DefaultRoute() || lp.hasIpv4UnreachableDefaultRoute())
- && (lp.hasIpv6DefaultRoute() || lp.hasIpv6UnreachableDefaultRoute());
+ && (lp.hasIpv6DefaultRoute() || lp.hasIpv6UnreachableDefaultRoute())
+ && !lp.hasExcludeRoute()) {
+ return lp.getInterfaceName();
+ }
+ return null;
+ }
+
+ /**
+ * Returns whether we need to set interface filtering rule or not
+ */
+ private boolean requiresVpnAllowRule(NetworkAgentInfo nai, LinkProperties lp,
+ String filterIface) {
+ // Only filter if lp has an interface.
+ if (lp == null || lp.getInterfaceName() == null) return false;
+ // Before T, allow rules are only needed if VPN isolation is enabled.
+ // T and After T, allow rules are needed for all VPNs.
+ return filterIface != null || (nai.isVPN() && SdkLevel.isAtLeastT());
}
private static UidRangeParcel[] toUidRangeStableParcels(final @NonNull Set<UidRange> ranges) {
@@ -8159,9 +8222,10 @@
if (!prevRanges.isEmpty()) {
updateVpnUidRanges(false, nai, prevRanges);
}
- final boolean wasFiltering = requiresVpnIsolation(nai, prevNc, nai.linkProperties);
- final boolean shouldFilter = requiresVpnIsolation(nai, newNc, nai.linkProperties);
- final String iface = nai.linkProperties.getInterfaceName();
+ final String oldIface = getVpnIsolationInterface(nai, prevNc, nai.linkProperties);
+ final String newIface = getVpnIsolationInterface(nai, newNc, nai.linkProperties);
+ final boolean wasFiltering = requiresVpnAllowRule(nai, nai.linkProperties, oldIface);
+ final boolean shouldFilter = requiresVpnAllowRule(nai, nai.linkProperties, newIface);
// For VPN uid interface filtering, old ranges need to be removed before new ranges can
// be added, due to the range being expanded and stored as individual UIDs. For example
// the UIDs might be updated from [0, 99999] to ([0, 10012], [10014, 99999]) which means
@@ -8173,11 +8237,16 @@
// above, where the addition of new ranges happens before the removal of old ranges.
// TODO Fix this window by computing an accurate diff on Set<UidRange>, so the old range
// to be removed will never overlap with the new range to be added.
+
+ // Null iface given to onVpnUidRangesAdded/Removed is a wildcard to allow apps to
+ // receive packets on all interfaces. This is required to accept incoming traffic in
+ // Lockdown mode by overriding the Lockdown blocking rule.
if (wasFiltering && !prevRanges.isEmpty()) {
- mPermissionMonitor.onVpnUidRangesRemoved(iface, prevRanges, prevNc.getOwnerUid());
+ mPermissionMonitor.onVpnUidRangesRemoved(oldIface, prevRanges,
+ prevNc.getOwnerUid());
}
if (shouldFilter && !newRanges.isEmpty()) {
- mPermissionMonitor.onVpnUidRangesAdded(iface, newRanges, newNc.getOwnerUid());
+ mPermissionMonitor.onVpnUidRangesAdded(newIface, newRanges, newNc.getOwnerUid());
}
} catch (Exception e) {
// Never crash!
@@ -9182,7 +9251,18 @@
params.networkCapabilities = networkAgent.networkCapabilities;
params.linkProperties = new LinkProperties(networkAgent.linkProperties,
true /* parcelSensitiveFields */);
- networkAgent.networkMonitor().notifyNetworkConnected(params);
+ // isAtLeastT() is conservative here, as recent versions of NetworkStack support the
+ // newer callback even before T. However getInterfaceVersion is a synchronized binder
+ // call that would cause a Log.wtf to be emitted from the system_server process, and
+ // in the absence of a satisfactory, scalable solution which follows an easy/standard
+ // process to check the interface version, just use an SDK check. NetworkStack will
+ // always be new enough when running on T+.
+ if (SdkLevel.isAtLeastT()) {
+ networkAgent.networkMonitor().notifyNetworkConnected(params);
+ } else {
+ networkAgent.networkMonitor().notifyNetworkConnected(params.linkProperties,
+ params.networkCapabilities);
+ }
scheduleUnvalidatedPrompt(networkAgent);
// Whether a particular NetworkRequest listen should cause signal strength thresholds to
@@ -10568,7 +10648,11 @@
if (callback == null) throw new IllegalArgumentException("callback must be non-null");
if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)) {
- enforceConnectivityRestrictedNetworksPermission();
+ // TODO: Check allowed list here and ensure that either a) any QoS callback registered
+ // on this network is unregistered when the app loses permission or b) no QoS
+ // callbacks are sent for restricted networks unless the app currently has permission
+ // to access restricted networks.
+ enforceConnectivityRestrictedNetworksPermission(false /* checkUidsAllowedList */);
}
mQosCallbackTracker.registerCallback(callback, filter, nai);
}
@@ -10584,13 +10668,29 @@
mQosCallbackTracker.unregisterCallback(callback);
}
+ private boolean isNetworkPreferenceAllowedForProfile(@NonNull UserHandle profile) {
+ // UserManager.isManagedProfile returns true for all apps in managed user profiles.
+ // Enterprise device can be fully managed like device owner and such use case
+ // also should be supported. Calling app check for work profile and fully managed device
+ // is already done in DevicePolicyManager.
+ // This check is an extra caution to be sure device is fully managed or not.
+ final UserManager um = mContext.getSystemService(UserManager.class);
+ final DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+ if (um.isManagedProfile(profile.getIdentifier())) {
+ return true;
+ }
+ if (SdkLevel.isAtLeastT() && dpm.getDeviceOwner() != null) return true;
+ return false;
+ }
+
/**
- * Request that a user profile is put by default on a network matching a given preference.
+ * Set a list of default network selection policies for a user profile or device owner.
*
* See the documentation for the individual preferences for a description of the supported
* behaviors.
*
- * @param profile the user profile for whih the preference is being set.
+ * @param profile If the device owner is set, any profile is allowed.
+ Otherwise, the given profile can only be managed profile.
* @param preferences the list of profile network preferences for the
* provided profile.
* @param listener an optional listener to listen for completion of the operation.
@@ -10604,7 +10704,10 @@
Objects.requireNonNull(profile);
if (preferences.size() == 0) {
- preferences.add((new ProfileNetworkPreference.Builder()).build());
+ final ProfileNetworkPreference pref = new ProfileNetworkPreference.Builder()
+ .setPreference(ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT)
+ .build();
+ preferences.add(pref);
}
PermissionUtils.enforceNetworkStackPermission(mContext);
@@ -10615,19 +10718,21 @@
throw new IllegalArgumentException("Must explicitly specify a user handle ("
+ "UserHandle.CURRENT not supported)");
}
- final UserManager um = mContext.getSystemService(UserManager.class);
- if (!um.isManagedProfile(profile.getIdentifier())) {
- throw new IllegalArgumentException("Profile must be a managed profile");
+ if (!isNetworkPreferenceAllowedForProfile(profile)) {
+ throw new IllegalArgumentException("Profile must be a managed profile "
+ + "or the device owner must be set. ");
}
final List<ProfileNetworkPreferenceList.Preference> preferenceList =
new ArrayList<ProfileNetworkPreferenceList.Preference>();
- boolean allowFallback = true;
+ boolean hasDefaultPreference = false;
for (final ProfileNetworkPreference preference : preferences) {
final NetworkCapabilities nc;
+ boolean allowFallback = true;
switch (preference.getPreference()) {
case ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT:
nc = null;
+ hasDefaultPreference = true;
if (preference.getPreferenceEnterpriseId() != 0) {
throw new IllegalArgumentException(
"Invalid enterprise identifier in setProfileNetworkPreferences");
@@ -10637,6 +10742,14 @@
allowFallback = false;
// continue to process the enterprise preference.
case ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE:
+ // This code is needed even though there is a check later on,
+ // because isRangeAlreadyInPreferenceList assumes that every preference
+ // has a UID list.
+ if (hasDefaultPreference) {
+ throw new IllegalArgumentException(
+ "Default profile preference should not be set along with other "
+ + "preference");
+ }
if (!isEnterpriseIdentifierValid(preference.getPreferenceEnterpriseId())) {
throw new IllegalArgumentException(
"Invalid enterprise identifier in setProfileNetworkPreferences");
@@ -10660,6 +10773,10 @@
}
preferenceList.add(new ProfileNetworkPreferenceList.Preference(
profile, nc, allowFallback));
+ if (hasDefaultPreference && preferenceList.size() > 1) {
+ throw new IllegalArgumentException(
+ "Default profile preference should not be set along with other preference");
+ }
}
mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_PROFILE_NETWORK_PREFERENCE,
new Pair<>(preferenceList, listener)));
@@ -10704,12 +10821,6 @@
return false;
}
- private void validateNetworkCapabilitiesOfProfileNetworkPreference(
- @Nullable final NetworkCapabilities nc) {
- if (null == nc) return; // Null caps are always allowed. It means to remove the setting.
- ensureRequestableCapabilities(nc);
- }
-
private ArraySet<NetworkRequestInfo> createNrisFromProfileNetworkPreferences(
@NonNull final ProfileNetworkPreferenceList prefs) {
final ArraySet<NetworkRequestInfo> result = new ArraySet<>();
@@ -10760,10 +10871,19 @@
private void handleSetProfileNetworkPreference(
@NonNull final List<ProfileNetworkPreferenceList.Preference> preferenceList,
@Nullable final IOnCompleteListener listener) {
+ /*
+ * handleSetProfileNetworkPreference is always called for single user.
+ * preferenceList only contains preferences for different uids within the same user
+ * (enforced by getUidListToBeAppliedForNetworkPreference).
+ * Clear all the existing preferences for the user before applying new preferences.
+ *
+ */
+ mProfileNetworkPreferences = mProfileNetworkPreferences.withoutUser(
+ preferenceList.get(0).user);
for (final ProfileNetworkPreferenceList.Preference preference : preferenceList) {
- validateNetworkCapabilitiesOfProfileNetworkPreference(preference.capabilities);
mProfileNetworkPreferences = mProfileNetworkPreferences.plus(preference);
}
+
removeDefaultNetworkRequestsForPreference(PREFERENCE_ORDER_PROFILE);
addPerAppDefaultNetworkRequests(
createNrisFromProfileNetworkPreferences(mProfileNetworkPreferences));
@@ -11218,17 +11338,45 @@
}
@Override
- public void updateFirewallRule(final int chain, final int uid, final boolean allow) {
+ public void setUidFirewallRule(final int chain, final int uid, final int rule) {
enforceNetworkStackOrSettingsPermission();
+ // There are only two type of firewall rule: FIREWALL_RULE_ALLOW or FIREWALL_RULE_DENY
+ int firewallRule = getFirewallRuleType(chain, rule);
+
+ if (firewallRule != FIREWALL_RULE_ALLOW && firewallRule != FIREWALL_RULE_DENY) {
+ throw new IllegalArgumentException("setUidFirewallRule with invalid rule: " + rule);
+ }
+
try {
- mBpfNetMaps.setUidRule(chain, uid,
- allow ? INetd.FIREWALL_RULE_ALLOW : INetd.FIREWALL_RULE_DENY);
+ mBpfNetMaps.setUidRule(chain, uid, firewallRule);
} catch (ServiceSpecificException e) {
throw new IllegalStateException(e);
}
}
+ private int getFirewallRuleType(int chain, int rule) {
+ final int defaultRule;
+ switch (chain) {
+ case ConnectivityManager.FIREWALL_CHAIN_STANDBY:
+ case ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_1:
+ case ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_2:
+ defaultRule = FIREWALL_RULE_ALLOW;
+ break;
+ case ConnectivityManager.FIREWALL_CHAIN_DOZABLE:
+ case ConnectivityManager.FIREWALL_CHAIN_POWERSAVE:
+ case ConnectivityManager.FIREWALL_CHAIN_RESTRICTED:
+ case ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY:
+ defaultRule = FIREWALL_RULE_DENY;
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported firewall chain: " + chain);
+ }
+ if (rule == FIREWALL_RULE_DEFAULT) rule = defaultRule;
+
+ return rule;
+ }
+
@Override
public void setFirewallChainEnabled(final int chain, final boolean enable) {
enforceNetworkStackOrSettingsPermission();
@@ -11262,6 +11410,12 @@
mBpfNetMaps.replaceUidChain("fw_low_power_standby", true /* isAllowList */,
uids);
break;
+ case ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_1:
+ mBpfNetMaps.replaceUidChain("fw_oem_deny_1", false /* isAllowList */, uids);
+ break;
+ case ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_2:
+ mBpfNetMaps.replaceUidChain("fw_oem_deny_2", false /* isAllowList */, uids);
+ break;
default:
throw new IllegalArgumentException("replaceFirewallChain with invalid chain: "
+ chain);
diff --git a/service/src/com/android/server/TestNetworkService.java b/service/src/com/android/server/TestNetworkService.java
index a0bfb4a..e12190c 100644
--- a/service/src/com/android/server/TestNetworkService.java
+++ b/service/src/com/android/server/TestNetworkService.java
@@ -16,6 +16,7 @@
package com.android.server;
+import static android.net.TestNetworkManager.CLAT_INTERFACE_PREFIX;
import static android.net.TestNetworkManager.TEST_TAP_PREFIX;
import static android.net.TestNetworkManager.TEST_TUN_PREFIX;
@@ -98,6 +99,14 @@
}
}
+ // TODO: find a way to allow the caller to pass in non-clat interface names, ensuring that
+ // those names do not conflict with names created by callers that do not pass in an interface
+ // name.
+ private static boolean isValidInterfaceName(@NonNull final String iface) {
+ return iface.startsWith(CLAT_INTERFACE_PREFIX + TEST_TUN_PREFIX)
+ || iface.startsWith(CLAT_INTERFACE_PREFIX + TEST_TAP_PREFIX);
+ }
+
/**
* Create a TUN or TAP interface with the specified parameters.
*
@@ -106,29 +115,35 @@
*/
@Override
public TestNetworkInterface createInterface(boolean isTun, boolean bringUp,
- LinkAddress[] linkAddrs) {
+ LinkAddress[] linkAddrs, @Nullable String iface) {
enforceTestNetworkPermissions(mContext);
Objects.requireNonNull(linkAddrs, "missing linkAddrs");
- String ifacePrefix = isTun ? TEST_TUN_PREFIX : TEST_TAP_PREFIX;
- String iface = ifacePrefix + sTestTunIndex.getAndIncrement();
+ String interfaceName = iface;
+ if (iface == null) {
+ String ifacePrefix = isTun ? TEST_TUN_PREFIX : TEST_TAP_PREFIX;
+ interfaceName = ifacePrefix + sTestTunIndex.getAndIncrement();
+ } else if (!isValidInterfaceName(iface)) {
+ throw new IllegalArgumentException("invalid interface name requested: " + iface);
+ }
+
final long token = Binder.clearCallingIdentity();
try {
ParcelFileDescriptor tunIntf =
- ParcelFileDescriptor.adoptFd(jniCreateTunTap(isTun, iface));
+ ParcelFileDescriptor.adoptFd(jniCreateTunTap(isTun, interfaceName));
for (LinkAddress addr : linkAddrs) {
mNetd.interfaceAddAddress(
- iface,
+ interfaceName,
addr.getAddress().getHostAddress(),
addr.getPrefixLength());
}
if (bringUp) {
- NetdUtils.setInterfaceUp(mNetd, iface);
+ NetdUtils.setInterfaceUp(mNetd, interfaceName);
}
- return new TestNetworkInterface(tunIntf, iface);
+ return new TestNetworkInterface(tunIntf, interfaceName);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} finally {
@@ -231,6 +246,7 @@
nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED);
nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
+ nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
nc.setNetworkSpecifier(new TestNetworkSpecifier(iface));
nc.setAdministratorUids(administratorUids);
if (!isMetered) {
diff --git a/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java b/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
index b761762..b06c8aa 100644
--- a/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
+++ b/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
@@ -19,8 +19,6 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
-import static com.android.networkstack.apishim.ConstantsShim.RECEIVER_NOT_EXPORTED;
-
import android.annotation.NonNull;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -31,7 +29,6 @@
import android.net.NetworkCapabilities;
import android.net.NetworkSpecifier;
import android.net.TelephonyNetworkSpecifier;
-import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Process;
@@ -96,11 +93,7 @@
@NonNull final TelephonyManager t) {
mContext = c;
mTelephonyManager = t;
- if (Build.VERSION.SDK_INT > Build.VERSION_CODES.S) {
- mTelephonyManagerShim = new TelephonyManagerShimImpl(mTelephonyManager);
- } else {
- mTelephonyManagerShim = null;
- }
+ mTelephonyManagerShim = TelephonyManagerShimImpl.newInstance(mTelephonyManager);
mThread = new HandlerThread(TAG);
mThread.start();
mHandler = new Handler(mThread.getLooper()) {};
@@ -160,7 +153,7 @@
private void registerForCarrierChanges() {
final IntentFilter filter = new IntentFilter();
filter.addAction(TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED);
- mContext.registerReceiver(this, filter, null, mHandler, RECEIVER_NOT_EXPORTED /* flags */);
+ mContext.registerReceiver(this, filter, null, mHandler);
registerCarrierPrivilegesListeners();
}
@@ -194,36 +187,30 @@
private void addCarrierPrivilegesListener(int logicalSlotIndex, Executor executor,
CarrierPrivilegesListenerShim listener) {
- if (mTelephonyManagerShim == null) {
- return;
- }
try {
mTelephonyManagerShim.addCarrierPrivilegesListener(
logicalSlotIndex, executor, listener);
} catch (UnsupportedApiLevelException unsupportedApiLevelException) {
+ // Should not happen since CarrierPrivilegeAuthenticator is only used on T+
Log.e(TAG, "addCarrierPrivilegesListener API is not available");
}
}
private void removeCarrierPrivilegesListener(CarrierPrivilegesListenerShim listener) {
- if (mTelephonyManagerShim == null) {
- return;
- }
try {
mTelephonyManagerShim.removeCarrierPrivilegesListener(listener);
} catch (UnsupportedApiLevelException unsupportedApiLevelException) {
+ // Should not happen since CarrierPrivilegeAuthenticator is only used on T+
Log.e(TAG, "removeCarrierPrivilegesListener API is not available");
}
}
private String getCarrierServicePackageNameForLogicalSlot(int logicalSlotIndex) {
- if (mTelephonyManagerShim == null) {
- return null;
- }
try {
return mTelephonyManagerShim.getCarrierServicePackageNameForLogicalSlot(
logicalSlotIndex);
} catch (UnsupportedApiLevelException unsupportedApiLevelException) {
+ // Should not happen since CarrierPrivilegeAuthenticator is only used on T+
Log.e(TAG, "getCarrierServicePackageNameForLogicalSlot API is not available");
}
return null;
diff --git a/service/src/com/android/server/connectivity/ClatCoordinator.java b/service/src/com/android/server/connectivity/ClatCoordinator.java
index c1a8195..8cefd47 100644
--- a/service/src/com/android/server/connectivity/ClatCoordinator.java
+++ b/service/src/com/android/server/connectivity/ClatCoordinator.java
@@ -18,26 +18,42 @@
import static android.net.INetd.IF_STATE_UP;
import static android.net.INetd.PERMISSION_SYSTEM;
+import static android.system.OsConstants.ETH_P_IP;
+import static android.system.OsConstants.ETH_P_IPV6;
import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.INetd;
+import android.net.InetAddresses;
import android.net.InterfaceConfigurationParcel;
import android.net.IpPrefix;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
+import android.system.ErrnoException;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.BpfMap;
+import com.android.net.module.util.IBpfMap;
import com.android.net.module.util.InterfaceParams;
+import com.android.net.module.util.TcUtils;
+import com.android.net.module.util.bpf.ClatEgress4Key;
+import com.android.net.module.util.bpf.ClatEgress4Value;
+import com.android.net.module.util.bpf.ClatIngress6Key;
+import com.android.net.module.util.bpf.ClatIngress6Value;
import java.io.FileDescriptor;
import java.io.IOException;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
import java.net.InetAddress;
import java.nio.ByteBuffer;
+import java.util.Objects;
/**
* This coordinator is responsible for providing clat relevant functionality.
@@ -66,26 +82,53 @@
private static final InetAddress GOOGLE_DNS_4 = InetAddress.parseNumericAddress("8.8.8.8");
private static final int INVALID_IFINDEX = 0;
- private static final int INVALID_PID = 0;
- private static final long INVALID_COOKIE = 0;
+
+ // For better code clarity when used for 'bool ingress' parameter.
+ @VisibleForTesting
+ static final boolean EGRESS = false;
+ @VisibleForTesting
+ static final boolean INGRESS = true;
+
+ // For better code clarity when used for 'bool ether' parameter.
+ static final boolean RAWIP = false;
+ static final boolean ETHER = true;
+
+ // The priority of clat hook - must be after tethering.
+ @VisibleForTesting
+ static final int PRIO_CLAT = 4;
+
+ private static final String CLAT_EGRESS4_MAP_PATH = makeMapPath("egress4");
+ private static final String CLAT_INGRESS6_MAP_PATH = makeMapPath("ingress6");
+
+ private static String makeMapPath(String which) {
+ return "/sys/fs/bpf/net_shared/map_clatd_clat_" + which + "_map";
+ }
+
+ private static String makeProgPath(boolean ingress, boolean ether) {
+ String path = "/sys/fs/bpf/net_shared/prog_clatd_schedcls_"
+ + (ingress ? "ingress6" : "egress4")
+ + "_clat_"
+ + (ether ? "ether" : "rawip");
+ return path;
+ }
@NonNull
private final INetd mNetd;
@NonNull
private final Dependencies mDeps;
@Nullable
- private String mIface = null;
+ private final IBpfMap<ClatIngress6Key, ClatIngress6Value> mIngressMap;
@Nullable
- private String mNat64Prefix = null;
+ private final IBpfMap<ClatEgress4Key, ClatEgress4Value> mEgressMap;
@Nullable
- private String mXlatLocalAddress4 = null;
- @Nullable
- private String mXlatLocalAddress6 = null;
- private int mPid = INVALID_PID;
- private long mCookie = INVALID_COOKIE;
+ private ClatdTracker mClatdTracker = null;
+ /**
+ * Dependencies of ClatCoordinator which makes ConnectivityService injection
+ * in tests.
+ */
@VisibleForTesting
- abstract static class Dependencies {
+ public abstract static class Dependencies {
/**
* Get netd.
*/
@@ -201,9 +244,112 @@
public void untagSocket(long cookie) throws IOException {
native_untagSocket(cookie);
}
+
+ /** Get ingress6 BPF map. */
+ @Nullable
+ public IBpfMap<ClatIngress6Key, ClatIngress6Value> getBpfIngress6Map() {
+ // Pre-T devices don't use ClatCoordinator to access clat map. Since Nat464Xlat
+ // initializes a ClatCoordinator object to avoid redundant null pointer check
+ // while using, ignore the BPF map initialization on pre-T devices.
+ // TODO: probably don't initialize ClatCoordinator object on pre-T devices.
+ if (!SdkLevel.isAtLeastT()) return null;
+ try {
+ return new BpfMap<>(CLAT_INGRESS6_MAP_PATH,
+ BpfMap.BPF_F_RDWR, ClatIngress6Key.class, ClatIngress6Value.class);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Cannot create ingress6 map: " + e);
+ return null;
+ }
+ }
+
+ /** Get egress4 BPF map. */
+ @Nullable
+ public IBpfMap<ClatEgress4Key, ClatEgress4Value> getBpfEgress4Map() {
+ // Pre-T devices don't use ClatCoordinator to access clat map. Since Nat464Xlat
+ // initializes a ClatCoordinator object to avoid redundant null pointer check
+ // while using, ignore the BPF map initialization on pre-T devices.
+ // TODO: probably don't initialize ClatCoordinator object on pre-T devices.
+ if (!SdkLevel.isAtLeastT()) return null;
+ try {
+ return new BpfMap<>(CLAT_EGRESS4_MAP_PATH,
+ BpfMap.BPF_F_RDWR, ClatEgress4Key.class, ClatEgress4Value.class);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Cannot create egress4 map: " + e);
+ return null;
+ }
+ }
+
+ /** Checks if the network interface uses an ethernet L2 header. */
+ public boolean isEthernet(String iface) throws IOException {
+ return TcUtils.isEthernet(iface);
+ }
+
+ /** Add a clsact qdisc. */
+ public void tcQdiscAddDevClsact(int ifIndex) throws IOException {
+ TcUtils.tcQdiscAddDevClsact(ifIndex);
+ }
+
+ /** Attach a tc bpf filter. */
+ public void tcFilterAddDevBpf(int ifIndex, boolean ingress, short prio, short proto,
+ String bpfProgPath) throws IOException {
+ TcUtils.tcFilterAddDevBpf(ifIndex, ingress, prio, proto, bpfProgPath);
+ }
+
+ /** Delete a tc filter. */
+ public void tcFilterDelDev(int ifIndex, boolean ingress, short prio, short proto)
+ throws IOException {
+ TcUtils.tcFilterDelDev(ifIndex, ingress, prio, proto);
+ }
}
@VisibleForTesting
+ static class ClatdTracker {
+ @NonNull
+ public final String iface;
+ public final int ifIndex;
+ @NonNull
+ public final String v4iface;
+ public final int v4ifIndex;
+ @NonNull
+ public final Inet4Address v4;
+ @NonNull
+ public final Inet6Address v6;
+ @NonNull
+ public final Inet6Address pfx96;
+ public final int pid;
+ public final long cookie;
+
+ ClatdTracker(@NonNull String iface, int ifIndex, @NonNull String v4iface,
+ int v4ifIndex, @NonNull Inet4Address v4, @NonNull Inet6Address v6,
+ @NonNull Inet6Address pfx96, int pid, long cookie) {
+ this.iface = iface;
+ this.ifIndex = ifIndex;
+ this.v4iface = v4iface;
+ this.v4ifIndex = v4ifIndex;
+ this.v4 = v4;
+ this.v6 = v6;
+ this.pfx96 = pfx96;
+ this.pid = pid;
+ this.cookie = cookie;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof ClatdTracker)) return false;
+ ClatdTracker that = (ClatdTracker) o;
+ return Objects.equals(this.iface, that.iface)
+ && this.ifIndex == that.ifIndex
+ && Objects.equals(this.v4iface, that.v4iface)
+ && this.v4ifIndex == that.v4ifIndex
+ && Objects.equals(this.v4, that.v4)
+ && Objects.equals(this.v6, that.v6)
+ && Objects.equals(this.pfx96, that.pfx96)
+ && this.pid == that.pid
+ && this.cookie == that.cookie;
+ }
+ };
+
+ @VisibleForTesting
static int getFwmark(int netId) {
// See union Fwmark in system/netd/include/Fwmark.h
return (netId & 0xffff)
@@ -227,6 +373,129 @@
public ClatCoordinator(@NonNull Dependencies deps) {
mDeps = deps;
mNetd = mDeps.getNetd();
+ mIngressMap = mDeps.getBpfIngress6Map();
+ mEgressMap = mDeps.getBpfEgress4Map();
+ }
+
+ private void maybeStartBpf(final ClatdTracker tracker) {
+ if (mIngressMap == null || mEgressMap == null) return;
+
+ final boolean isEthernet;
+ try {
+ isEthernet = mDeps.isEthernet(tracker.iface);
+ } catch (IOException e) {
+ Log.e(TAG, "Fail to call isEthernet for interface " + tracker.iface);
+ return;
+ }
+
+ final ClatEgress4Key txKey = new ClatEgress4Key(tracker.v4ifIndex, tracker.v4);
+ final ClatEgress4Value txValue = new ClatEgress4Value(tracker.ifIndex, tracker.v6,
+ tracker.pfx96, (short) (isEthernet ? 1 /* ETHER */ : 0 /* RAWIP */));
+ try {
+ mEgressMap.insertEntry(txKey, txValue);
+ } catch (ErrnoException | IllegalStateException e) {
+ Log.e(TAG, "Could not insert entry (" + txKey + ", " + txValue + ") on egress map: "
+ + e);
+ return;
+ }
+
+ final ClatIngress6Key rxKey = new ClatIngress6Key(tracker.ifIndex, tracker.pfx96,
+ tracker.v6);
+ final ClatIngress6Value rxValue = new ClatIngress6Value(tracker.v4ifIndex,
+ tracker.v4);
+ try {
+ mIngressMap.insertEntry(rxKey, rxValue);
+ } catch (ErrnoException | IllegalStateException e) {
+ Log.e(TAG, "Could not insert entry (" + rxKey + ", " + rxValue + ") ingress map: "
+ + e);
+ try {
+ mEgressMap.deleteEntry(txKey);
+ } catch (ErrnoException | IllegalStateException e2) {
+ Log.e(TAG, "Could not delete entry (" + txKey + ") from egress map: " + e2);
+ }
+ return;
+ }
+
+ // Usually the clsact will be added in netd RouteController::addInterfaceToPhysicalNetwork.
+ // But clat is started before the v4- interface is added to the network. The clat startup
+ // have to add clsact of v4- tun interface first for adding bpf filter in maybeStartBpf.
+ try {
+ // tc qdisc add dev .. clsact
+ mDeps.tcQdiscAddDevClsact(tracker.v4ifIndex);
+ } catch (IOException e) {
+ Log.e(TAG, "tc qdisc add dev (" + tracker.v4ifIndex + "[" + tracker.v4iface
+ + "]) failure: " + e);
+ try {
+ mEgressMap.deleteEntry(txKey);
+ } catch (ErrnoException | IllegalStateException e2) {
+ Log.e(TAG, "Could not delete entry (" + txKey + ") from egress map: " + e2);
+ }
+ try {
+ mIngressMap.deleteEntry(rxKey);
+ } catch (ErrnoException | IllegalStateException e3) {
+ Log.e(TAG, "Could not delete entry (" + rxKey + ") from ingress map: " + e3);
+ }
+ return;
+ }
+
+ // This program will be attached to the v4-* interface which is a TUN and thus always rawip.
+ try {
+ // tc filter add dev .. egress prio 4 protocol ip bpf object-pinned /sys/fs/bpf/...
+ // direct-action
+ mDeps.tcFilterAddDevBpf(tracker.v4ifIndex, EGRESS, (short) PRIO_CLAT, (short) ETH_P_IP,
+ makeProgPath(EGRESS, RAWIP));
+ } catch (IOException e) {
+ Log.e(TAG, "tc filter add dev (" + tracker.v4ifIndex + "[" + tracker.v4iface
+ + "]) egress prio PRIO_CLAT protocol ip failure: " + e);
+
+ // The v4- interface clsact is not deleted for unwinding error because once it is
+ // created with interface addition, the lifetime is till interface deletion. Moreover,
+ // the clsact has no clat filter now. It should not break anything.
+
+ try {
+ mEgressMap.deleteEntry(txKey);
+ } catch (ErrnoException | IllegalStateException e2) {
+ Log.e(TAG, "Could not delete entry (" + txKey + ") from egress map: " + e2);
+ }
+ try {
+ mIngressMap.deleteEntry(rxKey);
+ } catch (ErrnoException | IllegalStateException e3) {
+ Log.e(TAG, "Could not delete entry (" + rxKey + ") from ingress map: " + e3);
+ }
+ return;
+ }
+
+ try {
+ // tc filter add dev .. ingress prio 4 protocol ipv6 bpf object-pinned /sys/fs/bpf/...
+ // direct-action
+ mDeps.tcFilterAddDevBpf(tracker.ifIndex, INGRESS, (short) PRIO_CLAT,
+ (short) ETH_P_IPV6, makeProgPath(INGRESS, isEthernet));
+ } catch (IOException e) {
+ Log.e(TAG, "tc filter add dev (" + tracker.ifIndex + "[" + tracker.iface
+ + "]) ingress prio PRIO_CLAT protocol ipv6 failure: " + e);
+
+ // The v4- interface clsact is not deleted. See the reason in the error unwinding code
+ // of the egress filter attaching of v4- tun interface.
+
+ try {
+ mDeps.tcFilterDelDev(tracker.v4ifIndex, EGRESS, (short) PRIO_CLAT,
+ (short) ETH_P_IP);
+ } catch (IOException e2) {
+ Log.e(TAG, "tc filter del dev (" + tracker.v4ifIndex + "[" + tracker.v4iface
+ + "]) egress prio PRIO_CLAT protocol ip failure: " + e2);
+ }
+ try {
+ mEgressMap.deleteEntry(txKey);
+ } catch (ErrnoException | IllegalStateException e3) {
+ Log.e(TAG, "Could not delete entry (" + txKey + ") from egress map: " + e3);
+ }
+ try {
+ mIngressMap.deleteEntry(rxKey);
+ } catch (ErrnoException | IllegalStateException e4) {
+ Log.e(TAG, "Could not delete entry (" + rxKey + ") from ingress map: " + e4);
+ }
+ return;
+ }
}
/**
@@ -235,30 +504,46 @@
public String clatStart(final String iface, final int netId,
@NonNull final IpPrefix nat64Prefix)
throws IOException {
- if (mIface != null || mPid != INVALID_PID) {
- throw new IOException("Clatd is already running on " + mIface + " (pid " + mPid + ")");
+ if (mClatdTracker != null) {
+ throw new IOException("Clatd is already running on " + mClatdTracker.iface
+ + " (pid " + mClatdTracker.pid + ")");
}
if (nat64Prefix.getPrefixLength() != 96) {
throw new IOException("Prefix must be 96 bits long: " + nat64Prefix);
}
// [1] Pick an IPv4 address from 192.0.0.4, 192.0.0.5, 192.0.0.6 ..
- final String v4;
+ final String v4Str;
try {
- v4 = mDeps.selectIpv4Address(INIT_V4ADDR_STRING, INIT_V4ADDR_PREFIX_LEN);
+ v4Str = mDeps.selectIpv4Address(INIT_V4ADDR_STRING, INIT_V4ADDR_PREFIX_LEN);
} catch (IOException e) {
throw new IOException("no IPv4 addresses were available for clat: " + e);
}
- // [2] Generate a checksum-neutral IID.
- final String pfx96 = nat64Prefix.getAddress().getHostAddress();
- final String v6;
+ final Inet4Address v4;
try {
- v6 = mDeps.generateIpv6Address(iface, v4, pfx96);
+ v4 = (Inet4Address) InetAddresses.parseNumericAddress(v4Str);
+ } catch (ClassCastException | IllegalArgumentException | NullPointerException e) {
+ throw new IOException("Invalid IPv4 address " + v4Str);
+ }
+
+ // [2] Generate a checksum-neutral IID.
+ final String pfx96Str = nat64Prefix.getAddress().getHostAddress();
+ final String v6Str;
+ try {
+ v6Str = mDeps.generateIpv6Address(iface, v4Str, pfx96Str);
} catch (IOException e) {
throw new IOException("no IPv6 addresses were available for clat: " + e);
}
+ final Inet6Address pfx96 = (Inet6Address) nat64Prefix.getAddress();
+ final Inet6Address v6;
+ try {
+ v6 = (Inet6Address) InetAddresses.parseNumericAddress(v6Str);
+ } catch (ClassCastException | IllegalArgumentException | NullPointerException e) {
+ throw new IOException("Invalid IPv6 address " + v6Str);
+ }
+
// [3] Open, configure and bring up the tun interface.
// Create the v4-... tun interface.
final String tunIface = CLAT_PREFIX + iface;
@@ -269,6 +554,12 @@
throw new IOException("Create tun interface " + tunIface + " failed: " + e);
}
+ final int tunIfIndex = mDeps.getInterfaceIndex(tunIface);
+ if (tunIfIndex == INVALID_IFINDEX) {
+ tunFd.close();
+ throw new IOException("Fail to get interface index for interface " + tunIface);
+ }
+
// disable IPv6 on it - failing to do so is not a critical error
try {
mNetd.interfaceSetEnableIPv6(tunIface, false /* enabled */);
@@ -279,8 +570,14 @@
// Detect ipv4 mtu.
final Integer fwmark = getFwmark(netId);
- final int detectedMtu = mDeps.detectMtu(pfx96,
+ final int detectedMtu;
+ try {
+ detectedMtu = mDeps.detectMtu(pfx96Str,
ByteBuffer.wrap(GOOGLE_DNS_4.getAddress()).getInt(), fwmark);
+ } catch (IOException e) {
+ tunFd.close();
+ throw new IOException("Detect MTU on " + tunIface + " failed: " + e);
+ }
final int mtu = adjustMtu(detectedMtu);
Log.i(TAG, "ipv4 mtu is " + mtu);
@@ -295,7 +592,7 @@
}
final InterfaceConfigurationParcel ifConfig = new InterfaceConfigurationParcel();
ifConfig.ifName = tunIface;
- ifConfig.ipv4Addr = v4;
+ ifConfig.ipv4Addr = v4Str;
ifConfig.prefixLength = 32;
ifConfig.hwAddr = "";
ifConfig.flags = new String[] {IF_STATE_UP};
@@ -333,8 +630,8 @@
throw new IOException("Open raw socket failed: " + e);
}
- final int ifaceIndex = mDeps.getInterfaceIndex(iface);
- if (ifaceIndex == INVALID_IFINDEX) {
+ final int ifIndex = mDeps.getInterfaceIndex(iface);
+ if (ifIndex == INVALID_IFINDEX) {
tunFd.close();
readSock6.close();
writeSock6.close();
@@ -343,7 +640,7 @@
// Start translating packets to the new prefix.
try {
- mDeps.addAnycastSetsockopt(writeSock6.getFileDescriptor(), v6, ifaceIndex);
+ mDeps.addAnycastSetsockopt(writeSock6.getFileDescriptor(), v6Str, ifIndex);
} catch (IOException e) {
tunFd.close();
readSock6.close();
@@ -352,7 +649,7 @@
}
// Tag socket as AID_CLAT to avoid duplicated CLAT data usage accounting.
- long cookie;
+ final long cookie;
try {
cookie = mDeps.tagSocketAsClat(writeSock6.getFileDescriptor());
} catch (IOException e) {
@@ -364,7 +661,7 @@
// Update our packet socket filter to reflect the new 464xlat IP address.
try {
- mDeps.configurePacketSocket(readSock6.getFileDescriptor(), v6, ifaceIndex);
+ mDeps.configurePacketSocket(readSock6.getFileDescriptor(), v6Str, ifIndex);
} catch (IOException e) {
tunFd.close();
readSock6.close();
@@ -373,15 +670,12 @@
}
// [5] Start clatd.
+ final int pid;
try {
- mPid = mDeps.startClatd(tunFd.getFileDescriptor(), readSock6.getFileDescriptor(),
- writeSock6.getFileDescriptor(), iface, pfx96, v4, v6);
- mIface = iface;
- mNat64Prefix = pfx96;
- mXlatLocalAddress4 = v4;
- mXlatLocalAddress6 = v6;
- mCookie = cookie;
+ pid = mDeps.startClatd(tunFd.getFileDescriptor(), readSock6.getFileDescriptor(),
+ writeSock6.getFileDescriptor(), iface, pfx96Str, v4Str, v6Str);
} catch (IOException e) {
+ // TODO: probably refactor to handle the exception of #untagSocket if any.
mDeps.untagSocket(cookie);
throw new IOException("Error start clatd on " + iface + ": " + e);
} finally {
@@ -390,29 +684,141 @@
writeSock6.close();
}
- return v6;
+ // [6] Initialize and store clatd tracker object.
+ mClatdTracker = new ClatdTracker(iface, ifIndex, tunIface, tunIfIndex, v4, v6, pfx96,
+ pid, cookie);
+
+ // [7] Start BPF
+ maybeStartBpf(mClatdTracker);
+
+ return v6Str;
+ }
+
+ private void maybeStopBpf(final ClatdTracker tracker) {
+ if (mIngressMap == null || mEgressMap == null) return;
+
+ try {
+ mDeps.tcFilterDelDev(tracker.ifIndex, INGRESS, (short) PRIO_CLAT, (short) ETH_P_IPV6);
+ } catch (IOException e) {
+ Log.e(TAG, "tc filter del dev (" + tracker.ifIndex + "[" + tracker.iface
+ + "]) ingress prio PRIO_CLAT protocol ipv6 failure: " + e);
+ }
+
+ try {
+ mDeps.tcFilterDelDev(tracker.v4ifIndex, EGRESS, (short) PRIO_CLAT, (short) ETH_P_IP);
+ } catch (IOException e) {
+ Log.e(TAG, "tc filter del dev (" + tracker.v4ifIndex + "[" + tracker.v4iface
+ + "]) egress prio PRIO_CLAT protocol ip failure: " + e);
+ }
+
+ // We cleanup the maps last, so scanning through them can be used to
+ // determine what still needs cleanup.
+
+ final ClatEgress4Key txKey = new ClatEgress4Key(tracker.v4ifIndex, tracker.v4);
+ try {
+ mEgressMap.deleteEntry(txKey);
+ } catch (ErrnoException | IllegalStateException e) {
+ Log.e(TAG, "Could not delete entry (" + txKey + "): " + e);
+ }
+
+ final ClatIngress6Key rxKey = new ClatIngress6Key(tracker.ifIndex, tracker.pfx96,
+ tracker.v6);
+ try {
+ mIngressMap.deleteEntry(rxKey);
+ } catch (ErrnoException | IllegalStateException e) {
+ Log.e(TAG, "Could not delete entry (" + rxKey + "): " + e);
+ }
}
/**
* Stop clatd
*/
public void clatStop() throws IOException {
- if (mPid == INVALID_PID) {
+ if (mClatdTracker == null) {
throw new IOException("Clatd has not started");
}
- Log.i(TAG, "Stopping clatd pid=" + mPid + " on " + mIface);
+ Log.i(TAG, "Stopping clatd pid=" + mClatdTracker.pid + " on " + mClatdTracker.iface);
- mDeps.stopClatd(mIface, mNat64Prefix, mXlatLocalAddress4, mXlatLocalAddress6, mPid);
- mDeps.untagSocket(mCookie);
+ maybeStopBpf(mClatdTracker);
+ mDeps.stopClatd(mClatdTracker.iface, mClatdTracker.pfx96.getHostAddress(),
+ mClatdTracker.v4.getHostAddress(), mClatdTracker.v6.getHostAddress(),
+ mClatdTracker.pid);
+ mDeps.untagSocket(mClatdTracker.cookie);
- Log.i(TAG, "clatd on " + mIface + " stopped");
+ Log.i(TAG, "clatd on " + mClatdTracker.iface + " stopped");
+ mClatdTracker = null;
+ }
- mIface = null;
- mNat64Prefix = null;
- mXlatLocalAddress4 = null;
- mXlatLocalAddress6 = null;
- mPid = INVALID_PID;
- mCookie = INVALID_COOKIE;
+ private void dumpBpfIngress(@NonNull IndentingPrintWriter pw) {
+ if (mIngressMap == null) {
+ pw.println("No BPF ingress6 map");
+ return;
+ }
+
+ try {
+ if (mIngressMap.isEmpty()) {
+ pw.println("<empty>");
+ }
+ pw.println("BPF ingress map: iif nat64Prefix v6Addr -> v4Addr oif");
+ pw.increaseIndent();
+ mIngressMap.forEach((k, v) -> {
+ // TODO: print interface name
+ pw.println(String.format("%d %s/96 %s -> %s %d", k.iif, k.pfx96, k.local6,
+ v.local4, v.oif));
+ });
+ pw.decreaseIndent();
+ } catch (ErrnoException e) {
+ pw.println("Error dumping BPF ingress6 map: " + e);
+ }
+ }
+
+ private void dumpBpfEgress(@NonNull IndentingPrintWriter pw) {
+ if (mEgressMap == null) {
+ pw.println("No BPF egress4 map");
+ return;
+ }
+
+ try {
+ if (mEgressMap.isEmpty()) {
+ pw.println("<empty>");
+ }
+ pw.println("BPF egress map: iif v4Addr -> v6Addr nat64Prefix oif");
+ pw.increaseIndent();
+ mEgressMap.forEach((k, v) -> {
+ // TODO: print interface name
+ pw.println(String.format("%d %s -> %s %s/96 %d %s", k.iif, k.local4, v.local6,
+ v.pfx96, v.oif, v.oifIsEthernet != 0 ? "ether" : "rawip"));
+ });
+ pw.decreaseIndent();
+ } catch (ErrnoException e) {
+ pw.println("Error dumping BPF egress4 map: " + e);
+ }
+ }
+
+ /**
+ * Dump the cordinator information.
+ *
+ * @param pw print writer.
+ */
+ public void dump(@NonNull IndentingPrintWriter pw) {
+ // TODO: dump ClatdTracker
+ // TODO: move map dump to a global place to avoid duplicate dump while there are two or
+ // more IPv6 only networks.
+ pw.println("Forwarding rules:");
+ pw.increaseIndent();
+ dumpBpfIngress(pw);
+ dumpBpfEgress(pw);
+ pw.decreaseIndent();
+ pw.println();
+ }
+
+ /**
+ * Get clatd tracker. For test only.
+ */
+ @VisibleForTesting
+ @Nullable
+ ClatdTracker getClatdTrackerForTesting() {
+ return mClatdTracker;
}
private static native String native_selectIpv4Address(String v4addr, int prefixlen)
diff --git a/service/src/com/android/server/connectivity/ConnectivityNativeService.java b/service/src/com/android/server/connectivity/ConnectivityNativeService.java
new file mode 100644
index 0000000..c1ba40e
--- /dev/null
+++ b/service/src/com/android/server/connectivity/ConnectivityNativeService.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity;
+
+import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET4_BIND;
+import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET6_BIND;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.net.connectivity.aidl.ConnectivityNative;
+import android.os.Binder;
+import android.os.Process;
+import android.os.ServiceSpecificException;
+import android.system.ErrnoException;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.BpfBitmap;
+import com.android.net.module.util.BpfUtils;
+import com.android.net.module.util.CollectionUtils;
+import com.android.net.module.util.PermissionUtils;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * @hide
+ */
+public class ConnectivityNativeService extends ConnectivityNative.Stub {
+ public static final String SERVICE_NAME = "connectivity_native";
+
+ private static final String TAG = ConnectivityNativeService.class.getSimpleName();
+ private static final String CGROUP_PATH = "/sys/fs/cgroup";
+ private static final String V4_PROG_PATH =
+ "/sys/fs/bpf/net_shared/prog_block_bind4_block_port";
+ private static final String V6_PROG_PATH =
+ "/sys/fs/bpf/net_shared/prog_block_bind6_block_port";
+ private static final String BLOCKED_PORTS_MAP_PATH =
+ "/sys/fs/bpf/net_shared/map_block_blocked_ports_map";
+
+ private final Context mContext;
+
+ // BPF map for port blocking. Exactly 65536 entries long, with one entry per port number
+ @Nullable
+ private final BpfBitmap mBpfBlockedPortsMap;
+
+ /**
+ * Dependencies of ConnectivityNativeService, for injection in tests.
+ */
+ @VisibleForTesting
+ public static class Dependencies {
+ /** Get BPF maps. */
+ @Nullable public BpfBitmap getBlockPortsMap() {
+ try {
+ return new BpfBitmap(BLOCKED_PORTS_MAP_PATH);
+ } catch (ErrnoException e) {
+ throw new UnsupportedOperationException("Failed to create blocked ports map: "
+ + e);
+ }
+ }
+ }
+
+ private void enforceBlockPortPermission() {
+ final int uid = Binder.getCallingUid();
+ if (uid == Process.ROOT_UID || uid == Process.PHONE_UID) return;
+ PermissionUtils.enforceNetworkStackPermission(mContext);
+ }
+
+ private void ensureValidPortNumber(int port) {
+ if (port < 0 || port > 65535) {
+ throw new IllegalArgumentException("Invalid port number " + port);
+ }
+ }
+
+ public ConnectivityNativeService(final Context context) {
+ this(context, new Dependencies());
+ }
+
+ @VisibleForTesting
+ protected ConnectivityNativeService(final Context context, @NonNull Dependencies deps) {
+ mContext = context;
+ mBpfBlockedPortsMap = deps.getBlockPortsMap();
+ attachProgram();
+ }
+
+ @Override
+ public void blockPortForBind(int port) {
+ enforceBlockPortPermission();
+ ensureValidPortNumber(port);
+ try {
+ mBpfBlockedPortsMap.set(port);
+ } catch (ErrnoException e) {
+ throw new ServiceSpecificException(e.errno, e.getMessage());
+ }
+ }
+
+ @Override
+ public void unblockPortForBind(int port) {
+ enforceBlockPortPermission();
+ ensureValidPortNumber(port);
+ try {
+ mBpfBlockedPortsMap.unset(port);
+ } catch (ErrnoException e) {
+ throw new ServiceSpecificException(e.errno,
+ "Could not unset bitmap value for (port: " + port + "): " + e);
+ }
+ }
+
+ @Override
+ public void unblockAllPortsForBind() {
+ enforceBlockPortPermission();
+ try {
+ mBpfBlockedPortsMap.clear();
+ } catch (ErrnoException e) {
+ throw new ServiceSpecificException(e.errno, "Could not clear map: " + e);
+ }
+ }
+
+ @Override
+ public int[] getPortsBlockedForBind() {
+ enforceBlockPortPermission();
+
+ ArrayList<Integer> portMap = new ArrayList<Integer>();
+ for (int i = 0; i <= 65535; i++) {
+ try {
+ if (mBpfBlockedPortsMap.get(i)) portMap.add(i);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Failed to get index " + i, e);
+ }
+ }
+ return CollectionUtils.toIntArray(portMap);
+ }
+
+ @Override
+ public int getInterfaceVersion() {
+ return this.VERSION;
+ }
+
+ @Override
+ public String getInterfaceHash() {
+ return this.HASH;
+ }
+
+ /**
+ * Attach BPF program
+ */
+ private void attachProgram() {
+ try {
+ BpfUtils.attachProgram(BPF_CGROUP_INET4_BIND, V4_PROG_PATH, CGROUP_PATH, 0);
+ } catch (IOException e) {
+ throw new UnsupportedOperationException("Unable to attach to BPF_CGROUP_INET4_BIND: "
+ + e);
+ }
+ try {
+ BpfUtils.attachProgram(BPF_CGROUP_INET6_BIND, V6_PROG_PATH, CGROUP_PATH, 0);
+ } catch (IOException e) {
+ throw new UnsupportedOperationException("Unable to attach to BPF_CGROUP_INET6_BIND: "
+ + e);
+ }
+ Log.d(TAG, "Attached BPF_CGROUP_INET4_BIND and BPF_CGROUP_INET6_BIND programs");
+ }
+}
diff --git a/service/src/com/android/server/connectivity/DscpPolicyTracker.java b/service/src/com/android/server/connectivity/DscpPolicyTracker.java
index 43cfc8f..7829d1a 100644
--- a/service/src/com/android/server/connectivity/DscpPolicyTracker.java
+++ b/service/src/com/android/server/connectivity/DscpPolicyTracker.java
@@ -16,10 +16,11 @@
package com.android.server.connectivity;
-import static android.net.DscpPolicy.STATUS_DELETED;
-import static android.net.DscpPolicy.STATUS_INSUFFICIENT_PROCESSING_RESOURCES;
-import static android.net.DscpPolicy.STATUS_POLICY_NOT_FOUND;
-import static android.net.DscpPolicy.STATUS_SUCCESS;
+import static android.net.NetworkAgent.DSCP_POLICY_STATUS_DELETED;
+import static android.net.NetworkAgent.DSCP_POLICY_STATUS_INSUFFICIENT_PROCESSING_RESOURCES;
+import static android.net.NetworkAgent.DSCP_POLICY_STATUS_POLICY_NOT_FOUND;
+import static android.net.NetworkAgent.DSCP_POLICY_STATUS_REQUEST_DECLINED;
+import static android.net.NetworkAgent.DSCP_POLICY_STATUS_SUCCESS;
import static android.system.OsConstants.ETH_P_ALL;
import android.annotation.NonNull;
@@ -37,6 +38,7 @@
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.NetworkInterface;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
@@ -50,7 +52,7 @@
private static final String TAG = DscpPolicyTracker.class.getSimpleName();
private static final String PROG_PATH =
- "/sys/fs/bpf/prog_dscp_policy_schedcls_set_dscp";
+ "/sys/fs/bpf/net_shared/prog_dscp_policy_schedcls_set_dscp";
// Name is "map + *.o + map_name + map". Can probably shorten this
private static final String IPV4_POLICY_MAP_PATH = makeMapPath(
"dscp_policy_ipv4_dscp_policies");
@@ -59,37 +61,75 @@
private static final int MAX_POLICIES = 16;
private static String makeMapPath(String which) {
- return "/sys/fs/bpf/map_" + which + "_map";
+ return "/sys/fs/bpf/net_shared/map_" + which + "_map";
}
private Set<String> mAttachedIfaces;
private final BpfMap<Struct.U32, DscpPolicyValue> mBpfDscpIpv4Policies;
private final BpfMap<Struct.U32, DscpPolicyValue> mBpfDscpIpv6Policies;
- private final SparseIntArray mPolicyIdToBpfMapIndex;
+
+ // The actual policy rules used by the BPF code to process packets
+ // are in mBpfDscpIpv4Policies and mBpfDscpIpv4Policies. Both of
+ // these can contain up to MAX_POLICIES rules.
+ //
+ // A given policy always consumes one entry in both the IPv4 and
+ // IPv6 maps even if if's an IPv4-only or IPv6-only policy.
+ //
+ // Each interface index has a SparseIntArray of rules which maps a
+ // policy ID to the index of the corresponding rule in the maps.
+ // mIfaceIndexToPolicyIdBpfMapIndex maps the interface index to
+ // the per-interface SparseIntArray.
+ private final HashMap<Integer, SparseIntArray> mIfaceIndexToPolicyIdBpfMapIndex;
public DscpPolicyTracker() throws ErrnoException {
mAttachedIfaces = new HashSet<String>();
-
- mPolicyIdToBpfMapIndex = new SparseIntArray(MAX_POLICIES);
+ mIfaceIndexToPolicyIdBpfMapIndex = new HashMap<Integer, SparseIntArray>();
mBpfDscpIpv4Policies = new BpfMap<Struct.U32, DscpPolicyValue>(IPV4_POLICY_MAP_PATH,
BpfMap.BPF_F_RDWR, Struct.U32.class, DscpPolicyValue.class);
mBpfDscpIpv6Policies = new BpfMap<Struct.U32, DscpPolicyValue>(IPV6_POLICY_MAP_PATH,
BpfMap.BPF_F_RDWR, Struct.U32.class, DscpPolicyValue.class);
}
+ private boolean isUnusedIndex(int index) {
+ for (SparseIntArray ifacePolicies : mIfaceIndexToPolicyIdBpfMapIndex.values()) {
+ if (ifacePolicies.indexOfValue(index) >= 0) return false;
+ }
+ return true;
+ }
+
private int getFirstFreeIndex() {
+ if (mIfaceIndexToPolicyIdBpfMapIndex.size() == 0) return 0;
for (int i = 0; i < MAX_POLICIES; i++) {
- if (mPolicyIdToBpfMapIndex.indexOfValue(i) < 0) return i;
+ if (isUnusedIndex(i)) {
+ return i;
+ }
}
return MAX_POLICIES;
}
+ private int findIndex(int policyId, int ifIndex) {
+ SparseIntArray ifacePolicies = mIfaceIndexToPolicyIdBpfMapIndex.get(ifIndex);
+ if (ifacePolicies != null) {
+ final int existingIndex = ifacePolicies.get(policyId, -1);
+ if (existingIndex != -1) {
+ return existingIndex;
+ }
+ }
+
+ final int firstIndex = getFirstFreeIndex();
+ if (firstIndex >= MAX_POLICIES) {
+ // New policy is being added, but max policies has already been reached.
+ return -1;
+ }
+ return firstIndex;
+ }
+
private void sendStatus(NetworkAgentInfo nai, int policyId, int status) {
try {
nai.networkAgent.onDscpPolicyStatusUpdated(policyId, status);
} catch (RemoteException e) {
- Log.d(TAG, "Failed update policy status: ", e);
+ Log.e(TAG, "Failed update policy status: ", e);
}
}
@@ -107,37 +147,43 @@
|| policy.getSourceAddress() instanceof Inet6Address));
}
- private int addDscpPolicyInternal(DscpPolicy policy) {
+ private int getIfaceIndex(NetworkAgentInfo nai) {
+ String iface = nai.linkProperties.getInterfaceName();
+ NetworkInterface netIface;
+ try {
+ netIface = NetworkInterface.getByName(iface);
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to get iface index for " + iface + ": " + e);
+ netIface = null;
+ }
+ return (netIface != null) ? netIface.getIndex() : 0;
+ }
+
+ private int addDscpPolicyInternal(DscpPolicy policy, int ifIndex) {
// If there is no existing policy with a matching ID, and we are already at
// the maximum number of policies then return INSUFFICIENT_PROCESSING_RESOURCES.
- final int existingIndex = mPolicyIdToBpfMapIndex.get(policy.getPolicyId(), -1);
- if (existingIndex == -1 && mPolicyIdToBpfMapIndex.size() >= MAX_POLICIES) {
- return STATUS_INSUFFICIENT_PROCESSING_RESOURCES;
+ SparseIntArray ifacePolicies = mIfaceIndexToPolicyIdBpfMapIndex.get(ifIndex);
+ if (ifacePolicies == null) {
+ ifacePolicies = new SparseIntArray(MAX_POLICIES);
}
// Currently all classifiers are supported, if any are removed return
- // STATUS_REQUESTED_CLASSIFIER_NOT_SUPPORTED,
- // and for any other generic error STATUS_REQUEST_DECLINED
+ // DSCP_POLICY_STATUS_REQUESTED_CLASSIFIER_NOT_SUPPORTED,
+ // and for any other generic error DSCP_POLICY_STATUS_REQUEST_DECLINED
- int addIndex = 0;
- // If a policy with a matching ID exists, replace it, otherwise use the next free
- // index for the policy.
- if (existingIndex != -1) {
- addIndex = mPolicyIdToBpfMapIndex.get(policy.getPolicyId());
- } else {
- addIndex = getFirstFreeIndex();
+ final int addIndex = findIndex(policy.getPolicyId(), ifIndex);
+ if (addIndex == -1) {
+ return DSCP_POLICY_STATUS_INSUFFICIENT_PROCESSING_RESOURCES;
}
try {
- mPolicyIdToBpfMapIndex.put(policy.getPolicyId(), addIndex);
-
// Add v4 policy to mBpfDscpIpv4Policies if source and destination address
- // are both null or if they are both instances of Inet6Address.
+ // are both null or if they are both instances of Inet4Address.
if (matchesIpv4(policy)) {
mBpfDscpIpv4Policies.insertOrReplaceEntry(
new Struct.U32(addIndex),
new DscpPolicyValue(policy.getSourceAddress(),
- policy.getDestinationAddress(),
+ policy.getDestinationAddress(), ifIndex,
policy.getSourcePort(), policy.getDestinationPortRange(),
(short) policy.getProtocol(), (short) policy.getDscpValue()));
}
@@ -148,49 +194,67 @@
mBpfDscpIpv6Policies.insertOrReplaceEntry(
new Struct.U32(addIndex),
new DscpPolicyValue(policy.getSourceAddress(),
- policy.getDestinationAddress(),
+ policy.getDestinationAddress(), ifIndex,
policy.getSourcePort(), policy.getDestinationPortRange(),
(short) policy.getProtocol(), (short) policy.getDscpValue()));
}
+
+ ifacePolicies.put(policy.getPolicyId(), addIndex);
+ // Only add the policy to the per interface map if the policy was successfully
+ // added to both bpf maps above. It is safe to assume that if insert fails for
+ // one map then it fails for both.
+ mIfaceIndexToPolicyIdBpfMapIndex.put(ifIndex, ifacePolicies);
} catch (ErrnoException e) {
Log.e(TAG, "Failed to insert policy into map: ", e);
- return STATUS_INSUFFICIENT_PROCESSING_RESOURCES;
+ return DSCP_POLICY_STATUS_INSUFFICIENT_PROCESSING_RESOURCES;
}
- return STATUS_SUCCESS;
+ return DSCP_POLICY_STATUS_SUCCESS;
}
/**
* Add the provided DSCP policy to the bpf map. Attach bpf program dscp_policy to iface
* if not already attached. Response will be sent back to nai with status.
*
- * STATUS_SUCCESS - if policy was added successfully
- * STATUS_INSUFFICIENT_PROCESSING_RESOURCES - if max policies were already set
+ * DSCP_POLICY_STATUS_SUCCESS - if policy was added successfully
+ * DSCP_POLICY_STATUS_INSUFFICIENT_PROCESSING_RESOURCES - if max policies were already set
+ * DSCP_POLICY_STATUS_REQUEST_DECLINED - Interface index was invalid
*/
public void addDscpPolicy(NetworkAgentInfo nai, DscpPolicy policy) {
if (!mAttachedIfaces.contains(nai.linkProperties.getInterfaceName())) {
if (!attachProgram(nai.linkProperties.getInterfaceName())) {
Log.e(TAG, "Unable to attach program");
- sendStatus(nai, policy.getPolicyId(), STATUS_INSUFFICIENT_PROCESSING_RESOURCES);
+ sendStatus(nai, policy.getPolicyId(),
+ DSCP_POLICY_STATUS_INSUFFICIENT_PROCESSING_RESOURCES);
return;
}
}
- int status = addDscpPolicyInternal(policy);
+ final int ifIndex = getIfaceIndex(nai);
+ if (ifIndex == 0) {
+ Log.e(TAG, "Iface index is invalid");
+ sendStatus(nai, policy.getPolicyId(), DSCP_POLICY_STATUS_REQUEST_DECLINED);
+ return;
+ }
+
+ int status = addDscpPolicyInternal(policy, ifIndex);
sendStatus(nai, policy.getPolicyId(), status);
}
- private void removePolicyFromMap(NetworkAgentInfo nai, int policyId, int index) {
- int status = STATUS_POLICY_NOT_FOUND;
+ private void removePolicyFromMap(NetworkAgentInfo nai, int policyId, int index,
+ boolean sendCallback) {
+ int status = DSCP_POLICY_STATUS_POLICY_NOT_FOUND;
try {
mBpfDscpIpv4Policies.replaceEntry(new Struct.U32(index), DscpPolicyValue.NONE);
mBpfDscpIpv6Policies.replaceEntry(new Struct.U32(index), DscpPolicyValue.NONE);
- status = STATUS_DELETED;
+ status = DSCP_POLICY_STATUS_DELETED;
} catch (ErrnoException e) {
Log.e(TAG, "Failed to delete policy from map: ", e);
}
- sendStatus(nai, policyId, status);
+ if (sendCallback) {
+ sendStatus(nai, policyId, status);
+ }
}
/**
@@ -199,40 +263,48 @@
public void removeDscpPolicy(NetworkAgentInfo nai, int policyId) {
if (!mAttachedIfaces.contains(nai.linkProperties.getInterfaceName())) {
// Nothing to remove since program is not attached. Send update back for policy id.
- sendStatus(nai, policyId, STATUS_POLICY_NOT_FOUND);
+ sendStatus(nai, policyId, DSCP_POLICY_STATUS_POLICY_NOT_FOUND);
return;
}
- if (mPolicyIdToBpfMapIndex.get(policyId, -1) != -1) {
- removePolicyFromMap(nai, policyId, mPolicyIdToBpfMapIndex.get(policyId));
- mPolicyIdToBpfMapIndex.delete(policyId);
+ SparseIntArray ifacePolicies = mIfaceIndexToPolicyIdBpfMapIndex.get(getIfaceIndex(nai));
+ if (ifacePolicies == null) return;
+
+ final int existingIndex = ifacePolicies.get(policyId, -1);
+ if (existingIndex == -1) {
+ Log.e(TAG, "Policy " + policyId + " does not exist in map.");
+ sendStatus(nai, policyId, DSCP_POLICY_STATUS_POLICY_NOT_FOUND);
+ return;
}
- // TODO: detach should only occur if no more policies are present on the nai's iface.
- if (mPolicyIdToBpfMapIndex.size() == 0) {
+ removePolicyFromMap(nai, policyId, existingIndex, true);
+ ifacePolicies.delete(policyId);
+
+ if (ifacePolicies.size() == 0) {
detachProgram(nai.linkProperties.getInterfaceName());
}
}
/**
- * Remove all DSCP policies and detach program.
+ * Remove all DSCP policies and detach program. Send callback if requested.
*/
- // TODO: Remove all should only remove policies from corresponding nai iface.
- public void removeAllDscpPolicies(NetworkAgentInfo nai) {
+ public void removeAllDscpPolicies(NetworkAgentInfo nai, boolean sendCallback) {
if (!mAttachedIfaces.contains(nai.linkProperties.getInterfaceName())) {
// Nothing to remove since program is not attached. Send update for policy
// id 0. The status update must contain a policy ID, and 0 is an invalid id.
- sendStatus(nai, 0, STATUS_SUCCESS);
+ if (sendCallback) {
+ sendStatus(nai, 0, DSCP_POLICY_STATUS_SUCCESS);
+ }
return;
}
- for (int i = 0; i < mPolicyIdToBpfMapIndex.size(); i++) {
- removePolicyFromMap(nai, mPolicyIdToBpfMapIndex.keyAt(i),
- mPolicyIdToBpfMapIndex.valueAt(i));
+ SparseIntArray ifacePolicies = mIfaceIndexToPolicyIdBpfMapIndex.get(getIfaceIndex(nai));
+ if (ifacePolicies == null) return;
+ for (int i = 0; i < ifacePolicies.size(); i++) {
+ removePolicyFromMap(nai, ifacePolicies.keyAt(i), ifacePolicies.valueAt(i),
+ sendCallback);
}
- mPolicyIdToBpfMapIndex.clear();
-
- // Can detach program since no policies are active.
+ ifacePolicies.clear();
detachProgram(nai.linkProperties.getInterfaceName());
}
@@ -240,12 +312,12 @@
* Attach BPF program
*/
private boolean attachProgram(@NonNull String iface) {
- // TODO: attach needs to be per iface not program.
-
try {
NetworkInterface netIface = NetworkInterface.getByName(iface);
+ boolean isEth = TcUtils.isEthernet(iface);
+ String path = PROG_PATH + (isEth ? "_ether" : "_raw_ip");
TcUtils.tcFilterAddDevBpf(netIface.getIndex(), false, PRIO_DSCP, (short) ETH_P_ALL,
- PROG_PATH);
+ path);
} catch (IOException e) {
Log.e(TAG, "Unable to attach to TC on " + iface + ": " + e);
return false;
@@ -263,9 +335,9 @@
if (netIface != null) {
TcUtils.tcFilterDelDev(netIface.getIndex(), false, PRIO_DSCP, (short) ETH_P_ALL);
}
+ mAttachedIfaces.remove(iface);
} catch (IOException e) {
Log.e(TAG, "Unable to detach to TC on " + iface + ": " + e);
}
- mAttachedIfaces.remove(iface);
}
}
diff --git a/service/src/com/android/server/connectivity/DscpPolicyValue.java b/service/src/com/android/server/connectivity/DscpPolicyValue.java
index cb40306..6e4e7eb 100644
--- a/service/src/com/android/server/connectivity/DscpPolicyValue.java
+++ b/service/src/com/android/server/connectivity/DscpPolicyValue.java
@@ -31,29 +31,31 @@
public class DscpPolicyValue extends Struct {
private static final String TAG = DscpPolicyValue.class.getSimpleName();
- // TODO: add the interface index.
@Field(order = 0, type = Type.ByteArray, arraysize = 16)
public final byte[] src46;
@Field(order = 1, type = Type.ByteArray, arraysize = 16)
public final byte[] dst46;
- @Field(order = 2, type = Type.UBE16)
- public final int srcPort;
+ @Field(order = 2, type = Type.U32)
+ public final long ifIndex;
@Field(order = 3, type = Type.UBE16)
- public final int dstPortStart;
+ public final int srcPort;
@Field(order = 4, type = Type.UBE16)
+ public final int dstPortStart;
+
+ @Field(order = 5, type = Type.UBE16)
public final int dstPortEnd;
- @Field(order = 5, type = Type.U8)
+ @Field(order = 6, type = Type.U8)
public final short proto;
- @Field(order = 6, type = Type.U8)
+ @Field(order = 7, type = Type.U8)
public final short dscp;
- @Field(order = 7, type = Type.U8, padding = 3)
+ @Field(order = 8, type = Type.U8, padding = 3)
public final short mask;
private static final int SRC_IP_MASK = 0x1;
@@ -69,6 +71,7 @@
return true;
}
+ // TODO: move to frameworks/libs/net and have this and BpfCoordinator import it.
private byte[] toIpv4MappedAddressBytes(InetAddress ia) {
final byte[] addr6 = new byte[16];
if (ia != null) {
@@ -117,13 +120,12 @@
return mask;
}
- // This constructor is necessary for BpfMap#getValue since all values must be
- // in the constructor.
- public DscpPolicyValue(final InetAddress src46, final InetAddress dst46, final int srcPort,
- final int dstPortStart, final int dstPortEnd, final short proto,
+ private DscpPolicyValue(final InetAddress src46, final InetAddress dst46, final long ifIndex,
+ final int srcPort, final int dstPortStart, final int dstPortEnd, final short proto,
final short dscp) {
this.src46 = toAddressField(src46);
this.dst46 = toAddressField(dst46);
+ this.ifIndex = ifIndex;
// These params need to be stored as 0 because uints are used in BpfMap.
// If they are -1 BpfMap write will throw errors.
@@ -138,15 +140,15 @@
this.mask = makeMask(this.src46, this.dst46, srcPort, dstPortStart, proto, dscp);
}
- public DscpPolicyValue(final InetAddress src46, final InetAddress dst46, final int srcPort,
- final Range<Integer> dstPort, final short proto,
+ public DscpPolicyValue(final InetAddress src46, final InetAddress dst46, final long ifIndex,
+ final int srcPort, final Range<Integer> dstPort, final short proto,
final short dscp) {
- this(src46, dst46, srcPort, dstPort != null ? dstPort.getLower() : -1,
+ this(src46, dst46, ifIndex, srcPort, dstPort != null ? dstPort.getLower() : -1,
dstPort != null ? dstPort.getUpper() : -1, proto, dscp);
}
public static final DscpPolicyValue NONE = new DscpPolicyValue(
- null /* src46 */, null /* dst46 */, -1 /* srcPort */,
+ null /* src46 */, null /* dst46 */, 0 /* ifIndex */, -1 /* srcPort */,
-1 /* dstPortStart */, -1 /* dstPortEnd */, (short) -1 /* proto */,
(short) 0 /* dscp */);
@@ -170,9 +172,9 @@
try {
return String.format(
- "src46: %s, dst46: %s, srcPort: %d, dstPortStart: %d, dstPortEnd: %d,"
- + " protocol: %d, dscp %s", srcIpString, dstIpString, srcPort, dstPortStart,
- dstPortEnd, proto, dscp);
+ "src46: %s, dst46: %s, ifIndex: %d, srcPort: %d, dstPortStart: %d,"
+ + " dstPortEnd: %d, protocol: %d, dscp %s", srcIpString, dstIpString,
+ ifIndex, srcPort, dstPortStart, dstPortEnd, proto, dscp);
} catch (IllegalArgumentException e) {
return String.format("String format error: " + e);
}
diff --git a/service/src/com/android/server/connectivity/FullScore.java b/service/src/com/android/server/connectivity/FullScore.java
index 799f46b..b13ba93 100644
--- a/service/src/com/android/server/connectivity/FullScore.java
+++ b/service/src/com/android/server/connectivity/FullScore.java
@@ -29,6 +29,7 @@
import android.net.NetworkCapabilities;
import android.net.NetworkScore;
import android.net.NetworkScore.KeepConnectedReason;
+import android.util.Log;
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
@@ -46,6 +47,8 @@
* they are handling a score that had the CS-managed bits set.
*/
public class FullScore {
+ private static final String TAG = FullScore.class.getSimpleName();
+
// This will be removed soon. Do *NOT* depend on it for any new code that is not part of
// a migration.
private final int mLegacyInt;
@@ -126,7 +129,15 @@
@VisibleForTesting
static @NonNull String policyNameOf(final int policy) {
final String name = sMessageNames.get(policy);
- if (name == null) throw new IllegalArgumentException("Unknown policy: " + policy);
+ if (name == null) {
+ // Don't throw here because name might be null due to proguard stripping out the
+ // POLICY_* constants, potentially causing a crash only on user builds because proguard
+ // does not run on userdebug builds.
+ // TODO: make MessageUtils safer by not returning the array and instead storing it
+ // internally and providing a getter (that does not throw) for individual values.
+ Log.wtf(TAG, "Unknown policy: " + policy);
+ return Integer.toString(policy);
+ }
return name.substring("POLICY_".length());
}
diff --git a/service/src/com/android/server/connectivity/Nat464Xlat.java b/service/src/com/android/server/connectivity/Nat464Xlat.java
index 7b06682..e4ad391 100644
--- a/service/src/com/android/server/connectivity/Nat464Xlat.java
+++ b/service/src/com/android/server/connectivity/Nat464Xlat.java
@@ -17,6 +17,7 @@
package com.android.server.connectivity;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static com.android.net.module.util.CollectionUtils.contains;
@@ -36,9 +37,12 @@
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.NetworkStackConstants;
import com.android.server.ConnectivityService;
+import java.io.IOException;
import java.net.Inet6Address;
import java.util.Objects;
@@ -96,6 +100,7 @@
private String mIface;
private Inet6Address mIPv6Address;
private State mState = State.IDLE;
+ private ClatCoordinator mClatCoordinator;
private boolean mEnableClatOnCellular;
private boolean mPrefixDiscoveryRunning;
@@ -106,6 +111,7 @@
mNetd = netd;
mNetwork = nai;
mEnableClatOnCellular = deps.getCellular464XlatEnabled();
+ mClatCoordinator = deps.getClatCoordinator(mNetd);
}
/**
@@ -122,6 +128,11 @@
final boolean supported = contains(NETWORK_TYPES, nai.networkInfo.getType());
final boolean connected = contains(NETWORK_STATES, nai.networkInfo.getState());
+ // Allow to run clat on test network.
+ // TODO: merge to boolean "supported" once boolean "supported" is migrated to
+ // NetworkCapabilities.TRANSPORT_*.
+ final boolean isTestNetwork = nai.networkCapabilities.hasTransport(TRANSPORT_TEST);
+
// Only run clat on networks that have a global IPv6 address and don't have a native IPv4
// address.
LinkProperties lp = nai.linkProperties;
@@ -132,8 +143,8 @@
final boolean skip464xlat = (nai.netAgentConfig() != null)
&& nai.netAgentConfig().skip464xlat;
- return supported && connected && isIpv6OnlyNetwork && !skip464xlat && !nai.destroyed
- && (nai.networkCapabilities.hasTransport(TRANSPORT_CELLULAR)
+ return (supported || isTestNetwork) && connected && isIpv6OnlyNetwork && !skip464xlat
+ && !nai.destroyed && (nai.networkCapabilities.hasTransport(TRANSPORT_CELLULAR)
? isCellular464XlatEnabled() : true);
}
@@ -179,10 +190,18 @@
private void enterStartingState(String baseIface) {
mNat64PrefixInUse = selectNat64Prefix();
String addrStr = null;
- try {
- addrStr = mNetd.clatdStart(baseIface, mNat64PrefixInUse.toString());
- } catch (RemoteException | ServiceSpecificException e) {
- Log.e(TAG, "Error starting clatd on " + baseIface + ": " + e);
+ if (SdkLevel.isAtLeastT()) {
+ try {
+ addrStr = mClatCoordinator.clatStart(baseIface, getNetId(), mNat64PrefixInUse);
+ } catch (IOException e) {
+ Log.e(TAG, "Error starting clatd on " + baseIface + ": " + e);
+ }
+ } else {
+ try {
+ addrStr = mNetd.clatdStart(baseIface, mNat64PrefixInUse.toString());
+ } catch (RemoteException | ServiceSpecificException e) {
+ Log.e(TAG, "Error starting clatd on " + baseIface + ": " + e);
+ }
}
mIface = CLAT_PREFIX + baseIface;
mBaseIface = baseIface;
@@ -256,10 +275,18 @@
}
Log.i(TAG, "Stopping clatd on " + mBaseIface);
- try {
- mNetd.clatdStop(mBaseIface);
- } catch (RemoteException | ServiceSpecificException e) {
- Log.e(TAG, "Error stopping clatd on " + mBaseIface + ": " + e);
+ if (SdkLevel.isAtLeastT()) {
+ try {
+ mClatCoordinator.clatStop();
+ } catch (IOException e) {
+ Log.e(TAG, "Error stopping clatd on " + mBaseIface + ": " + e);
+ }
+ } else {
+ try {
+ mNetd.clatdStop(mBaseIface);
+ } catch (RemoteException | ServiceSpecificException e) {
+ Log.e(TAG, "Error stopping clatd on " + mBaseIface + ": " + e);
+ }
}
String iface = mIface;
@@ -506,6 +533,24 @@
mNetwork.handler().post(() -> handleInterfaceRemoved(iface));
}
+ /**
+ * Dump the NAT64 xlat information.
+ *
+ * @param pw print writer.
+ */
+ public void dump(IndentingPrintWriter pw) {
+ if (SdkLevel.isAtLeastT()) {
+ if (isStarted()) {
+ pw.println("ClatCoordinator:");
+ pw.increaseIndent();
+ mClatCoordinator.dump(pw);
+ pw.decreaseIndent();
+ } else {
+ pw.println("<not started>");
+ }
+ }
+ }
+
@Override
public String toString() {
return "mBaseIface: " + mBaseIface + ", mIface: " + mIface + ", mState: " + mState;
diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
index b73e2cc..b40b6e0 100644
--- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
@@ -19,6 +19,7 @@
import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static android.net.NetworkCapabilities.transportNamesOf;
@@ -59,6 +60,7 @@
import android.util.Pair;
import android.util.SparseArray;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.WakeupMessage;
import com.android.modules.utils.build.SdkLevel;
import com.android.server.ConnectivityService;
@@ -736,8 +738,8 @@
}
@Override
- public void sendDestroyAndAwaitReplacement(final int timeoutMillis) {
- mHandler.obtainMessage(NetworkAgent.EVENT_DESTROY_AND_AWAIT_REPLACEMENT,
+ public void sendUnregisterAfterReplacement(final int timeoutMillis) {
+ mHandler.obtainMessage(NetworkAgent.EVENT_UNREGISTER_AFTER_REPLACEMENT,
new Pair<>(NetworkAgentInfo.this, timeoutMillis)).sendToTarget();
}
}
@@ -1186,6 +1188,15 @@
}
/**
+ * Dump the NAT64 xlat information.
+ *
+ * @param pw print writer.
+ */
+ public void dumpNat464Xlat(IndentingPrintWriter pw) {
+ clatd.dump(pw);
+ }
+
+ /**
* Sets the most recent ConnectivityReport for this network.
*
* <p>This should only be called from the ConnectivityService thread.
@@ -1214,20 +1225,22 @@
*
* @param nc the capabilities to sanitize
* @param creatorUid the UID of the process creating this network agent
+ * @param hasAutomotiveFeature true if this device has the automotive feature, false otherwise
* @param authenticator the carrier privilege authenticator to check for telephony constraints
*/
public static void restrictCapabilitiesFromNetworkAgent(@NonNull final NetworkCapabilities nc,
- final int creatorUid, @NonNull final CarrierPrivilegeAuthenticator authenticator) {
+ final int creatorUid, final boolean hasAutomotiveFeature,
+ @Nullable final CarrierPrivilegeAuthenticator authenticator) {
if (nc.hasTransport(TRANSPORT_TEST)) {
nc.restrictCapabilitiesForTestNetwork(creatorUid);
}
- if (!areAllowedUidsAcceptableFromNetworkAgent(nc, authenticator)) {
+ if (!areAllowedUidsAcceptableFromNetworkAgent(nc, hasAutomotiveFeature, authenticator)) {
nc.setAllowedUids(new ArraySet<>());
}
}
private static boolean areAllowedUidsAcceptableFromNetworkAgent(
- @NonNull final NetworkCapabilities nc,
+ @NonNull final NetworkCapabilities nc, final boolean hasAutomotiveFeature,
@Nullable final CarrierPrivilegeAuthenticator carrierPrivilegeAuthenticator) {
// NCs without access UIDs are fine.
if (!nc.hasAllowedUids()) return true;
@@ -1242,6 +1255,11 @@
// access UIDs
if (nc.hasTransport(TRANSPORT_TEST)) return true;
+ // Factories that make ethernet networks can allow UIDs for automotive devices.
+ if (nc.hasSingleTransport(TRANSPORT_ETHERNET) && hasAutomotiveFeature) {
+ return true;
+ }
+
// Factories that make cell networks can allow the UID for the carrier service package.
// This can only work in T where there is support for CarrierPrivilegeAuthenticator
if (null != carrierPrivilegeAuthenticator
@@ -1252,8 +1270,6 @@
return true;
}
- // TODO : accept Railway callers
-
return false;
}
diff --git a/service/src/com/android/server/connectivity/NetworkOffer.java b/service/src/com/android/server/connectivity/NetworkOffer.java
index 1e975dd..eea382e 100644
--- a/service/src/com/android/server/connectivity/NetworkOffer.java
+++ b/service/src/com/android/server/connectivity/NetworkOffer.java
@@ -22,6 +22,7 @@
import android.net.NetworkRequest;
import android.os.RemoteException;
+import java.util.ArrayList;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
@@ -143,6 +144,11 @@
@Override
public String toString() {
- return "NetworkOffer [ Score " + score + " Caps " + caps + "]";
+ final ArrayList<Integer> neededRequestIds = new ArrayList<>();
+ for (final NetworkRequest request : mCurrentlyNeeded) {
+ neededRequestIds.add(request.requestId);
+ }
+ return "NetworkOffer [ Provider Id (" + providerId + ") " + score + " Caps "
+ + caps + " Needed by " + neededRequestIds + "]";
}
}
diff --git a/service/src/com/android/server/connectivity/PermissionMonitor.java b/service/src/com/android/server/connectivity/PermissionMonitor.java
index 2885ba7..e4a2c20 100755
--- a/service/src/com/android/server/connectivity/PermissionMonitor.java
+++ b/service/src/com/android/server/connectivity/PermissionMonitor.java
@@ -23,6 +23,9 @@
import static android.Manifest.permission.UPDATE_DEVICE_STATS;
import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOCKDOWN_VPN;
+import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
+import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
import static android.net.ConnectivitySettingsManager.UIDS_ALLOWED_ON_RESTRICTED_NETWORKS;
import static android.net.INetd.PERMISSION_INTERNET;
import static android.net.INetd.PERMISSION_NETWORK;
@@ -37,6 +40,7 @@
import static com.android.net.module.util.CollectionUtils.toIntArray;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -74,7 +78,6 @@
import com.android.server.BpfNetMaps;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -108,10 +111,19 @@
@GuardedBy("this")
private final SparseIntArray mUidToNetworkPerm = new SparseIntArray();
- // Keys are active non-bypassable and fully-routed VPN's interface name, Values are uid ranges
- // for apps under the VPN
+ // NonNull keys are active non-bypassable and fully-routed VPN's interface name, Values are uid
+ // ranges for apps under the VPNs which enable interface filtering.
+ // If key is null, Values are uid ranges for apps under the VPNs which are connected but do not
+ // enable interface filtering.
@GuardedBy("this")
- private final Map<String, Set<UidRange>> mVpnUidRanges = new HashMap<>();
+ private final Map<String, Set<UidRange>> mVpnInterfaceUidRanges = new ArrayMap<>();
+
+ // Items are uid ranges for apps under the VPN Lockdown
+ // Ranges were given through ConnectivityManager#setRequireVpnForUids, and ranges are allowed to
+ // have duplicates. Also, it is allowed to give ranges that are already subject to lockdown.
+ // So we need to maintain uid range with multiset.
+ @GuardedBy("this")
+ private final MultiSet<UidRange> mVpnLockdownUidRanges = new MultiSet<>();
// A set of appIds for apps across all users on the device. We track appIds instead of uids
// directly to reduce its size and also eliminate the need to update this set when user is
@@ -127,9 +139,19 @@
@GuardedBy("this")
private final Set<Integer> mUidsAllowedOnRestrictedNetworks = new ArraySet<>();
+ // Store PackageManager for each user.
+ // Keys are users, Values are PackageManagers which get from each user.
@GuardedBy("this")
private final Map<UserHandle, PackageManager> mUsersPackageManager = new ArrayMap<>();
+ // Store appIds traffic permissions for each user.
+ // Keys are users, Values are SparseArrays where each entry maps an appId to the permissions
+ // that appId has within that user. The permissions are a bitmask of PERMISSION_INTERNET and
+ // PERMISSION_UPDATE_DEVICE_STATS, or 0 (PERMISSION_NONE) if the app has neither of those
+ // permissions. They can never be PERMISSION_UNINSTALLED.
+ @GuardedBy("this")
+ private final Map<UserHandle, SparseIntArray> mUsersTrafficPermissions = new ArrayMap<>();
+
private static final int SYSTEM_APPID = SYSTEM_UID;
private static final int MAX_PERMISSION_UPDATE_LOGS = 40;
@@ -191,6 +213,38 @@
}
}
+ private static class MultiSet<T> {
+ private final Map<T, Integer> mMap = new ArrayMap<>();
+
+ /**
+ * Returns the number of key in the set before this addition.
+ */
+ public int add(T key) {
+ final int oldCount = mMap.getOrDefault(key, 0);
+ mMap.put(key, oldCount + 1);
+ return oldCount;
+ }
+
+ /**
+ * Return the number of key in the set before this removal.
+ */
+ public int remove(T key) {
+ final int oldCount = mMap.getOrDefault(key, 0);
+ if (oldCount == 0) {
+ Log.wtf(TAG, "Attempt to remove non existing key = " + key.toString());
+ } else if (oldCount == 1) {
+ mMap.remove(key);
+ } else {
+ mMap.put(key, oldCount - 1);
+ }
+ return oldCount;
+ }
+
+ public Set<T> getSet() {
+ return mMap.keySet();
+ }
+ }
+
public PermissionMonitor(@NonNull final Context context, @NonNull final INetd netd,
@NonNull final BpfNetMaps bpfNetMaps) {
this(context, netd, bpfNetMaps, new Dependencies());
@@ -292,14 +346,24 @@
sendUidsNetworkPermission(uids, true /* add */);
}
- private void updateAppIdsTrafficPermission(final SparseIntArray appIds,
- final SparseIntArray extraAppIds) {
- for (int i = 0; i < extraAppIds.size(); i++) {
- final int appId = extraAppIds.keyAt(i);
- final int permission = extraAppIds.valueAt(i);
- appIds.put(appId, appIds.get(appId) | permission);
+ /**
+ * Calculates permissions for appIds.
+ * Maps each appId to the union of all traffic permissions that the appId has in all users.
+ *
+ * @return The appIds traffic permissions.
+ */
+ private synchronized SparseIntArray makeAppIdsTrafficPermForAllUsers() {
+ final SparseIntArray appIds = new SparseIntArray();
+ // Check appIds permissions from each user.
+ for (UserHandle user : mUsersTrafficPermissions.keySet()) {
+ final SparseIntArray userAppIds = mUsersTrafficPermissions.get(user);
+ for (int i = 0; i < userAppIds.size(); i++) {
+ final int appId = userAppIds.keyAt(i);
+ final int permission = userAppIds.valueAt(i);
+ appIds.put(appId, appIds.get(appId) | permission);
+ }
}
- sendAppIdsTrafficPermission(appIds);
+ return appIds;
}
private SparseIntArray getSystemTrafficPerm() {
@@ -363,6 +427,10 @@
// mUidsAllowedOnRestrictedNetworks.
updateUidsAllowedOnRestrictedNetworks(mDeps.getUidsAllowedOnRestrictedNetworks(mContext));
+ // Read system traffic permissions when a user removed and put them to USER_ALL because they
+ // are not specific to any particular user.
+ mUsersTrafficPermissions.put(UserHandle.ALL, getSystemTrafficPerm());
+
final List<UserHandle> usrs = mUserManager.getUserHandles(true /* excludeDying */);
// Update netd permissions for all users.
for (UserHandle user : usrs) {
@@ -397,7 +465,14 @@
if (appInfo == null) return false;
// Check whether package's uid is in allowed on restricted networks uid list. If so, this
// uid can have netd system permission.
- return mUidsAllowedOnRestrictedNetworks.contains(appInfo.uid);
+ return isUidAllowedOnRestrictedNetworks(appInfo.uid);
+ }
+
+ /**
+ * Returns whether the given uid is in allowed on restricted networks list.
+ */
+ public synchronized boolean isUidAllowedOnRestrictedNetworks(final int uid) {
+ return mUidsAllowedOnRestrictedNetworks.contains(uid);
}
@VisibleForTesting
@@ -487,9 +562,16 @@
final SparseIntArray uids = makeUidsNetworkPerm(apps);
updateUidsNetworkPermission(uids);
- // App ids traffic permission
- final SparseIntArray appIds = makeAppIdsTrafficPerm(apps);
- updateAppIdsTrafficPermission(appIds, getSystemTrafficPerm());
+ // Add new user appIds permissions.
+ final SparseIntArray addedUserAppIds = makeAppIdsTrafficPerm(apps);
+ mUsersTrafficPermissions.put(user, addedUserAppIds);
+ // Generate appIds from all users and send result to netd.
+ final SparseIntArray appIds = makeAppIdsTrafficPermForAllUsers();
+ sendAppIdsTrafficPermission(appIds);
+
+ // Log user added
+ mPermissionUpdateLogs.log("New user(" + user.getIdentifier() + ") added: nPerm uids="
+ + uids + ", tPerm appIds=" + addedUserAppIds);
}
/**
@@ -502,6 +584,7 @@
public synchronized void onUserRemoved(@NonNull UserHandle user) {
mUsers.remove(user);
+ // Remove uids network permissions that belongs to the user.
final SparseIntArray removedUids = new SparseIntArray();
final SparseIntArray allUids = mUidToNetworkPerm.clone();
for (int i = 0; i < allUids.size(); i++) {
@@ -512,6 +595,31 @@
}
}
sendUidsNetworkPermission(removedUids, false /* add */);
+
+ // Remove appIds traffic permission that belongs to the user
+ final SparseIntArray removedUserAppIds = mUsersTrafficPermissions.remove(user);
+ // Generate appIds from the remaining users.
+ final SparseIntArray appIds = makeAppIdsTrafficPermForAllUsers();
+
+ if (removedUserAppIds == null) {
+ Log.wtf(TAG, "onUserRemoved: Receive unknown user=" + user);
+ return;
+ }
+
+ // Clear permission on those appIds belong to this user only, set the permission to
+ // PERMISSION_UNINSTALLED.
+ for (int i = 0; i < removedUserAppIds.size(); i++) {
+ final int appId = removedUserAppIds.keyAt(i);
+ // Need to clear permission if the removed appId is not found in the array.
+ if (appIds.indexOfKey(appId) < 0) {
+ appIds.put(appId, PERMISSION_UNINSTALLED);
+ }
+ }
+ sendAppIdsTrafficPermission(appIds);
+
+ // Log user removed
+ mPermissionUpdateLogs.log("User(" + user.getIdentifier() + ") removed: nPerm uids="
+ + removedUids + ", tPerm appIds=" + removedUserAppIds);
}
/**
@@ -562,16 +670,26 @@
}
private synchronized void updateVpnUid(int uid, boolean add) {
- for (Map.Entry<String, Set<UidRange>> vpn : mVpnUidRanges.entrySet()) {
+ // Apps that can use restricted networks can always bypass VPNs.
+ if (hasRestrictedNetworksPermission(uid)) {
+ return;
+ }
+ for (Map.Entry<String, Set<UidRange>> vpn : mVpnInterfaceUidRanges.entrySet()) {
if (UidRange.containsUid(vpn.getValue(), uid)) {
final Set<Integer> changedUids = new HashSet<>();
changedUids.add(uid);
- removeBypassingUids(changedUids, -1 /* vpnAppUid */);
updateVpnUidsInterfaceRules(vpn.getKey(), changedUids, add);
}
}
}
+ private synchronized void updateLockdownUid(int uid, boolean add) {
+ if (UidRange.containsUid(mVpnLockdownUidRanges.getSet(), uid)
+ && !hasRestrictedNetworksPermission(uid)) {
+ updateLockdownUidRule(uid, add);
+ }
+ }
+
/**
* This handles both network and traffic permission, because there is no overlap in actual
* values, where network permission is NETWORK or SYSTEM, and traffic permission is INTERNET
@@ -598,6 +716,39 @@
}
}
+ private synchronized void updateAppIdTrafficPermission(int uid) {
+ final int uidTrafficPerm = getTrafficPermissionForUid(uid);
+ final SparseIntArray userTrafficPerms =
+ mUsersTrafficPermissions.get(UserHandle.getUserHandleForUid(uid));
+ if (userTrafficPerms == null) {
+ Log.wtf(TAG, "Can't get user traffic permission from uid=" + uid);
+ return;
+ }
+ // Do not put PERMISSION_UNINSTALLED into the array. If no package left on the uid
+ // (PERMISSION_UNINSTALLED), remove the appId from the array. Otherwise, update the latest
+ // permission to the appId.
+ final int appId = UserHandle.getAppId(uid);
+ if (uidTrafficPerm == PERMISSION_UNINSTALLED) {
+ userTrafficPerms.delete(appId);
+ } else {
+ userTrafficPerms.put(appId, uidTrafficPerm);
+ }
+ }
+
+ private synchronized int getAppIdTrafficPermission(int appId) {
+ int permission = PERMISSION_NONE;
+ boolean installed = false;
+ for (UserHandle user : mUsersTrafficPermissions.keySet()) {
+ final SparseIntArray userApps = mUsersTrafficPermissions.get(user);
+ final int appIdx = userApps.indexOfKey(appId);
+ if (appIdx >= 0) {
+ permission |= userApps.valueAt(appIdx);
+ installed = true;
+ }
+ }
+ return installed ? permission : PERMISSION_UNINSTALLED;
+ }
+
/**
* Called when a package is added.
*
@@ -607,9 +758,12 @@
* @hide
*/
public synchronized void onPackageAdded(@NonNull final String packageName, final int uid) {
+ // Update uid permission.
+ updateAppIdTrafficPermission(uid);
+ // Get the appId permission from all users then send the latest permission to netd.
final int appId = UserHandle.getAppId(uid);
- final int trafficPerm = getTrafficPermissionForUid(uid);
- sendPackagePermissionsForAppId(appId, trafficPerm);
+ final int appIdTrafficPerm = getAppIdTrafficPermission(appId);
+ sendPackagePermissionsForAppId(appId, appIdTrafficPerm);
final int currentPermission = mUidToNetworkPerm.get(uid, PERMISSION_NONE);
final int permission = highestPermissionForUid(uid, currentPermission, packageName);
@@ -629,14 +783,17 @@
// If the newly-installed package falls within some VPN's uid range, update Netd with it.
// This needs to happen after the mUidToNetworkPerm update above, since
- // removeBypassingUids() in updateVpnUid() depends on mUidToNetworkPerm to check if the
- // package can bypass VPN.
+ // hasRestrictedNetworksPermission() in updateVpnUid() and updateLockdownUid() depends on
+ // mUidToNetworkPerm to check if the package can bypass VPN.
updateVpnUid(uid, true /* add */);
+ updateLockdownUid(uid, true /* add */);
mAllApps.add(appId);
+
+ // Log package added.
mPermissionUpdateLogs.log("Package add: name=" + packageName + ", uid=" + uid
+ ", nPerm=(" + permissionToString(permission) + "/"
+ permissionToString(currentPermission) + ")"
- + ", tPerm=" + permissionToString(trafficPerm));
+ + ", tPerm=" + permissionToString(appIdTrafficPerm));
}
private int highestUidNetworkPermission(int uid) {
@@ -664,15 +821,19 @@
* @hide
*/
public synchronized void onPackageRemoved(@NonNull final String packageName, final int uid) {
+ // Update uid permission.
+ updateAppIdTrafficPermission(uid);
+ // Get the appId permission from all users then send the latest permission to netd.
final int appId = UserHandle.getAppId(uid);
- final int trafficPerm = getTrafficPermissionForUid(uid);
- sendPackagePermissionsForAppId(appId, trafficPerm);
+ final int appIdTrafficPerm = getAppIdTrafficPermission(appId);
+ sendPackagePermissionsForAppId(appId, appIdTrafficPerm);
// If the newly-removed package falls within some VPN's uid range, update Netd with it.
// This needs to happen before the mUidToNetworkPerm update below, since
- // removeBypassingUids() in updateVpnUid() depends on mUidToNetworkPerm to check if the
- // package can bypass VPN.
+ // hasRestrictedNetworksPermission() in updateVpnUid() and updateLockdownUid() depends on
+ // mUidToNetworkPerm to check if the package can bypass VPN.
updateVpnUid(uid, false /* add */);
+ updateLockdownUid(uid, false /* add */);
// If the package has been removed from all users on the device, clear it form mAllApps.
if (mPackageManager.getNameForUid(uid) == null) {
mAllApps.remove(appId);
@@ -680,10 +841,13 @@
final int currentPermission = mUidToNetworkPerm.get(uid, PERMISSION_NONE);
final int permission = highestUidNetworkPermission(uid);
+
+ // Log package removed.
mPermissionUpdateLogs.log("Package remove: name=" + packageName + ", uid=" + uid
+ ", nPerm=(" + permissionToString(permission) + "/"
+ permissionToString(currentPermission) + ")"
- + ", tPerm=" + permissionToString(trafficPerm));
+ + ", tPerm=" + permissionToString(appIdTrafficPerm));
+
if (permission != currentPermission) {
final SparseIntArray apps = new SparseIntArray();
int sdkSandboxUid = -1;
@@ -751,48 +915,100 @@
/**
* Called when a new set of UID ranges are added to an active VPN network
*
- * @param iface The active VPN network's interface name
+ * @param iface The active VPN network's interface name. Null iface indicates that the app is
+ * allowed to receive packets on all interfaces.
* @param rangesToAdd The new UID ranges to be added to the network
* @param vpnAppUid The uid of the VPN app
*/
- public synchronized void onVpnUidRangesAdded(@NonNull String iface, Set<UidRange> rangesToAdd,
+ public synchronized void onVpnUidRangesAdded(@Nullable String iface, Set<UidRange> rangesToAdd,
int vpnAppUid) {
// Calculate the list of new app uids under the VPN due to the new UID ranges and update
// Netd about them. Because mAllApps only contains appIds instead of uids, the result might
// be an overestimation if an app is not installed on the user on which the VPN is running,
- // but that's safe.
+ // but that's safe: if an app is not installed, it cannot receive any packets, so dropping
+ // packets to that UID is fine.
final Set<Integer> changedUids = intersectUids(rangesToAdd, mAllApps);
removeBypassingUids(changedUids, vpnAppUid);
updateVpnUidsInterfaceRules(iface, changedUids, true /* add */);
- if (mVpnUidRanges.containsKey(iface)) {
- mVpnUidRanges.get(iface).addAll(rangesToAdd);
+ if (mVpnInterfaceUidRanges.containsKey(iface)) {
+ mVpnInterfaceUidRanges.get(iface).addAll(rangesToAdd);
} else {
- mVpnUidRanges.put(iface, new HashSet<UidRange>(rangesToAdd));
+ mVpnInterfaceUidRanges.put(iface, new HashSet<UidRange>(rangesToAdd));
}
}
/**
* Called when a set of UID ranges are removed from an active VPN network
*
- * @param iface The VPN network's interface name
+ * @param iface The VPN network's interface name. Null iface indicates that the app is allowed
+ * to receive packets on all interfaces.
* @param rangesToRemove Existing UID ranges to be removed from the VPN network
* @param vpnAppUid The uid of the VPN app
*/
- public synchronized void onVpnUidRangesRemoved(@NonNull String iface,
+ public synchronized void onVpnUidRangesRemoved(@Nullable String iface,
Set<UidRange> rangesToRemove, int vpnAppUid) {
// Calculate the list of app uids that are no longer under the VPN due to the removed UID
// ranges and update Netd about them.
final Set<Integer> changedUids = intersectUids(rangesToRemove, mAllApps);
removeBypassingUids(changedUids, vpnAppUid);
updateVpnUidsInterfaceRules(iface, changedUids, false /* add */);
- Set<UidRange> existingRanges = mVpnUidRanges.getOrDefault(iface, null);
+ Set<UidRange> existingRanges = mVpnInterfaceUidRanges.getOrDefault(iface, null);
if (existingRanges == null) {
loge("Attempt to remove unknown vpn uid Range iface = " + iface);
return;
}
existingRanges.removeAll(rangesToRemove);
if (existingRanges.size() == 0) {
- mVpnUidRanges.remove(iface);
+ mVpnInterfaceUidRanges.remove(iface);
+ }
+ }
+
+ /**
+ * Called when UID ranges under VPN Lockdown are updated
+ *
+ * @param add {@code true} if the uids are to be added to the Lockdown, {@code false} if they
+ * are to be removed from the Lockdown.
+ * @param ranges The updated UID ranges under VPN Lockdown. This function does not treat the VPN
+ * app's UID in any special way. The caller is responsible for excluding the VPN
+ * app UID from the passed-in ranges.
+ * Ranges can have duplications and/or contain the range that is already subject
+ * to lockdown. However, ranges can not have overlaps with other ranges including
+ * ranges that are currently subject to lockdown.
+ */
+ public synchronized void updateVpnLockdownUidRanges(boolean add, UidRange[] ranges) {
+ final Set<UidRange> affectedUidRanges = new HashSet<>();
+
+ for (final UidRange range : ranges) {
+ if (add) {
+ // Rule will be added if mVpnLockdownUidRanges does not have this uid range entry
+ // currently.
+ if (mVpnLockdownUidRanges.add(range) == 0) {
+ affectedUidRanges.add(range);
+ }
+ } else {
+ // Rule will be removed if the number of the range in the set is 1 before the
+ // removal.
+ if (mVpnLockdownUidRanges.remove(range) == 1) {
+ affectedUidRanges.add(range);
+ }
+ }
+ }
+
+ // mAllApps only contains appIds instead of uids. So the generated uid list might contain
+ // apps that are installed only on some users but not others. But that's safe: if an app is
+ // not installed, it cannot receive any packets, so dropping packets to that UID is fine.
+ final Set<Integer> affectedUids = intersectUids(affectedUidRanges, mAllApps);
+
+ // We skip adding rule to privileged apps and allow them to bypass incoming packet
+ // filtering. The behaviour is consistent with how lockdown works for outgoing packets, but
+ // the implementation is different: while ConnectivityService#setRequireVpnForUids does not
+ // exclude privileged apps from the prohibit routing rules used to implement outgoing packet
+ // filtering, privileged apps can still bypass outgoing packet filtering because the
+ // prohibit rules observe the protected from VPN bit.
+ for (final int uid: affectedUids) {
+ if (!hasRestrictedNetworksPermission(uid)) {
+ updateLockdownUidRule(uid, add);
+ }
}
}
@@ -831,7 +1047,7 @@
*/
private void removeBypassingUids(Set<Integer> uids, int vpnAppUid) {
uids.remove(vpnAppUid);
- uids.removeIf(uid -> mUidToNetworkPerm.get(uid, PERMISSION_NONE) == PERMISSION_SYSTEM);
+ uids.removeIf(this::hasRestrictedNetworksPermission);
}
/**
@@ -840,6 +1056,7 @@
*
* This is to instruct netd to set up appropriate filtering rules for these uids, such that they
* can only receive ingress packets from the VPN's tunnel interface (and loopback).
+ * Null iface set up a wildcard rule that allow app to receive packets on all interfaces.
*
* @param iface the interface name of the active VPN connection
* @param add {@code true} if the uids are to be added to the interface, {@code false} if they
@@ -860,6 +1077,18 @@
}
}
+ private void updateLockdownUidRule(int uid, boolean add) {
+ try {
+ if (add) {
+ mBpfNetMaps.setUidRule(FIREWALL_CHAIN_LOCKDOWN_VPN, uid, FIREWALL_RULE_DENY);
+ } else {
+ mBpfNetMaps.setUidRule(FIREWALL_CHAIN_LOCKDOWN_VPN, uid, FIREWALL_RULE_ALLOW);
+ }
+ } catch (ServiceSpecificException e) {
+ loge("Failed to " + (add ? "add" : "remove") + " Lockdown rule: " + e);
+ }
+ }
+
/**
* Send the updated permission information to netd. Called upon package install/uninstall.
*
@@ -889,10 +1118,6 @@
*/
@VisibleForTesting
void sendAppIdsTrafficPermission(SparseIntArray netdPermissionsAppIds) {
- if (mNetd == null) {
- Log.e(TAG, "Failed to get the netd service");
- return;
- }
final ArrayList<Integer> allPermissionAppIds = new ArrayList<>();
final ArrayList<Integer> internetPermissionAppIds = new ArrayList<>();
final ArrayList<Integer> updateStatsPermissionAppIds = new ArrayList<>();
@@ -951,8 +1176,14 @@
/** Should only be used by unit tests */
@VisibleForTesting
- public Set<UidRange> getVpnUidRanges(String iface) {
- return mVpnUidRanges.get(iface);
+ public Set<UidRange> getVpnInterfaceUidRanges(String iface) {
+ return mVpnInterfaceUidRanges.get(iface);
+ }
+
+ /** Should only be used by unit tests */
+ @VisibleForTesting
+ public Set<UidRange> getVpnLockdownUidRanges() {
+ return mVpnLockdownUidRanges.getSet();
}
private synchronized void onSettingChanged() {
@@ -1017,7 +1248,7 @@
public void dump(IndentingPrintWriter pw) {
pw.println("Interface filtering rules:");
pw.increaseIndent();
- for (Map.Entry<String, Set<UidRange>> vpn : mVpnUidRanges.entrySet()) {
+ for (Map.Entry<String, Set<UidRange>> vpn : mVpnInterfaceUidRanges.entrySet()) {
pw.println("Interface: " + vpn.getKey());
pw.println("UIDs: " + vpn.getValue().toString());
pw.println();
@@ -1025,6 +1256,14 @@
pw.decreaseIndent();
pw.println();
+ pw.println("Lockdown filtering rules:");
+ pw.increaseIndent();
+ for (final UidRange range : mVpnLockdownUidRanges.getSet()) {
+ pw.println("UIDs: " + range.toString());
+ }
+ pw.decreaseIndent();
+
+ pw.println();
pw.println("Update logs:");
pw.increaseIndent();
mPermissionUpdateLogs.reverseDump(pw);
diff --git a/service/src/com/android/server/connectivity/ProfileNetworkPreferenceList.java b/service/src/com/android/server/connectivity/ProfileNetworkPreferenceList.java
index 71f342d..5bafef9 100644
--- a/service/src/com/android/server/connectivity/ProfileNetworkPreferenceList.java
+++ b/service/src/com/android/server/connectivity/ProfileNetworkPreferenceList.java
@@ -70,23 +70,33 @@
/**
* Returns a new object consisting of this object plus the passed preference.
*
- * If a preference already exists for the same user, it will be replaced by the passed
- * preference. Passing a Preference object containing a null capabilities object is equivalent
- * to (and indeed, implemented as) removing the preference for this user.
+ * It is not expected that unwanted preference already exists for the same user.
+ * All preferences for the user that were previously configured should be cleared before
+ * adding a new preference.
+ * Passing a Preference object containing a null capabilities object is equivalent
+ * to removing the preference for this user.
*/
public ProfileNetworkPreferenceList plus(@NonNull final Preference pref) {
- final ArrayList<Preference> newPrefs = new ArrayList<>();
- for (final Preference existingPref : preferences) {
- if (!existingPref.user.equals(pref.user)) {
- newPrefs.add(existingPref);
- }
- }
+ final ArrayList<Preference> newPrefs = new ArrayList<>(preferences);
if (null != pref.capabilities) {
newPrefs.add(pref);
}
return new ProfileNetworkPreferenceList(newPrefs);
}
+ /**
+ * Remove all preferences corresponding to a user.
+ */
+ public ProfileNetworkPreferenceList withoutUser(UserHandle user) {
+ final ArrayList<Preference> newPrefs = new ArrayList<>();
+ for (final Preference existingPref : preferences) {
+ if (!existingPref.user.equals(user)) {
+ newPrefs.add(existingPref);
+ }
+ }
+ return new ProfileNetworkPreferenceList(newPrefs);
+ }
+
public boolean isEmpty() {
return preferences.isEmpty();
}
diff --git a/service/src/com/android/server/connectivity/QosCallbackAgentConnection.java b/service/src/com/android/server/connectivity/QosCallbackAgentConnection.java
index 534dbe7..e682026 100644
--- a/service/src/com/android/server/connectivity/QosCallbackAgentConnection.java
+++ b/service/src/com/android/server/connectivity/QosCallbackAgentConnection.java
@@ -30,6 +30,8 @@
import android.telephony.data.NrQosSessionAttributes;
import android.util.Log;
+import com.android.modules.utils.build.SdkLevel;
+
import java.util.Objects;
/**
@@ -149,6 +151,7 @@
void sendEventEpsQosSessionAvailable(final QosSession session,
final EpsBearerQosSessionAttributes attributes) {
+ if (!validateOrSendErrorAndUnregister()) return;
try {
if (DBG) log("sendEventEpsQosSessionAvailable: sending...");
mCallback.onQosEpsBearerSessionAvailable(session, attributes);
@@ -159,6 +162,7 @@
void sendEventNrQosSessionAvailable(final QosSession session,
final NrQosSessionAttributes attributes) {
+ if (!validateOrSendErrorAndUnregister()) return;
try {
if (DBG) log("sendEventNrQosSessionAvailable: sending...");
mCallback.onNrQosSessionAvailable(session, attributes);
@@ -168,6 +172,7 @@
}
void sendEventQosSessionLost(@NonNull final QosSession session) {
+ if (!validateOrSendErrorAndUnregister()) return;
try {
if (DBG) log("sendEventQosSessionLost: sending...");
mCallback.onQosSessionLost(session);
@@ -185,6 +190,21 @@
}
}
+ private boolean validateOrSendErrorAndUnregister() {
+ final int exceptionType = mFilter.validate();
+ if (exceptionType != EX_TYPE_FILTER_NONE) {
+ log("validation fail before sending QosCallback.");
+ // Error callback is returned from Android T to prevent any disruption of application
+ // running on Android S.
+ if (SdkLevel.isAtLeastT()) {
+ sendEventQosCallbackError(exceptionType);
+ mQosCallbackTracker.unregisterCallback(mCallback);
+ }
+ return false;
+ }
+ return true;
+ }
+
private static void log(@NonNull final String msg) {
Log.d(TAG, msg);
}
diff --git a/service/src/com/android/server/connectivity/TcpKeepaliveController.java b/service/src/com/android/server/connectivity/TcpKeepaliveController.java
index acfbb3c..a9cb2fa 100644
--- a/service/src/com/android/server/connectivity/TcpKeepaliveController.java
+++ b/service/src/com/android/server/connectivity/TcpKeepaliveController.java
@@ -124,7 +124,12 @@
final TcpKeepalivePacketDataParcelable tcpDetails = switchToRepairMode(fd);
// TODO: consider building a TcpKeepalivePacketData directly from switchToRepairMode
return fromStableParcelable(tcpDetails);
- } catch (InvalidPacketException | InvalidSocketException e) {
+ // Use separate catch blocks: a combined catch would get wrongly optimized by R8
+ // (b/226127213).
+ } catch (InvalidSocketException e) {
+ switchOutOfRepairMode(fd);
+ throw e;
+ } catch (InvalidPacketException e) {
switchOutOfRepairMode(fd);
throw e;
}
diff --git a/service/src/com/android/server/net/DelayedDiskWrite.java b/service/src/com/android/server/net/DelayedDiskWrite.java
new file mode 100644
index 0000000..41cb419
--- /dev/null
+++ b/service/src/com/android/server/net/DelayedDiskWrite.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.BufferedOutputStream;
+import java.io.DataOutputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * This class provides APIs to do a delayed data write to a given {@link OutputStream}.
+ */
+public class DelayedDiskWrite {
+ private static final String TAG = "DelayedDiskWrite";
+
+ private final Dependencies mDeps;
+
+ private HandlerThread mDiskWriteHandlerThread;
+ private Handler mDiskWriteHandler;
+ /* Tracks multiple writes on the same thread */
+ private int mWriteSequence = 0;
+
+ public DelayedDiskWrite() {
+ this(new Dependencies());
+ }
+
+ @VisibleForTesting
+ DelayedDiskWrite(Dependencies deps) {
+ mDeps = deps;
+ }
+
+ /**
+ * Dependencies class of DelayedDiskWrite, used for injection in tests.
+ */
+ @VisibleForTesting
+ static class Dependencies {
+ /**
+ * Create a HandlerThread to use in DelayedDiskWrite.
+ */
+ public HandlerThread makeHandlerThread() {
+ return new HandlerThread("DelayedDiskWriteThread");
+ }
+
+ /**
+ * Quit the HandlerThread looper.
+ */
+ public void quitHandlerThread(HandlerThread handlerThread) {
+ handlerThread.getLooper().quit();
+ }
+ }
+
+ /**
+ * Used to do a delayed data write to a given {@link OutputStream}.
+ */
+ public interface Writer {
+ /**
+ * write data to a given {@link OutputStream}.
+ */
+ void onWriteCalled(DataOutputStream out) throws IOException;
+ }
+
+ /**
+ * Do a delayed data write to a given output stream opened from filePath.
+ */
+ public void write(final String filePath, final Writer w) {
+ write(filePath, w, true);
+ }
+
+ /**
+ * Do a delayed data write to a given output stream opened from filePath.
+ */
+ public void write(final String filePath, final Writer w, final boolean open) {
+ if (TextUtils.isEmpty(filePath)) {
+ throw new IllegalArgumentException("empty file path");
+ }
+
+ /* Do a delayed write to disk on a separate handler thread */
+ synchronized (this) {
+ if (++mWriteSequence == 1) {
+ mDiskWriteHandlerThread = mDeps.makeHandlerThread();
+ mDiskWriteHandlerThread.start();
+ mDiskWriteHandler = new Handler(mDiskWriteHandlerThread.getLooper());
+ }
+ }
+
+ mDiskWriteHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ doWrite(filePath, w, open);
+ }
+ });
+ }
+
+ private void doWrite(String filePath, Writer w, boolean open) {
+ DataOutputStream out = null;
+ try {
+ if (open) {
+ out = new DataOutputStream(new BufferedOutputStream(
+ new FileOutputStream(filePath)));
+ }
+ w.onWriteCalled(out);
+ } catch (IOException e) {
+ loge("Error writing data file " + filePath);
+ } finally {
+ if (out != null) {
+ try {
+ out.close();
+ } catch (Exception e) { }
+ }
+
+ // Quit if no more writes sent
+ synchronized (this) {
+ if (--mWriteSequence == 0) {
+ mDeps.quitHandlerThread(mDiskWriteHandlerThread);
+ mDiskWriteHandlerThread = null;
+ mDiskWriteHandler = null;
+ }
+ }
+ }
+ }
+
+ private void loge(String s) {
+ Log.e(TAG, s);
+ }
+}
+
diff --git a/tests/common/Android.bp b/tests/common/Android.bp
index b23074d..58731e0 100644
--- a/tests/common/Android.bp
+++ b/tests/common/Android.bp
@@ -36,6 +36,7 @@
"modules-utils-build",
"net-tests-utils",
"net-utils-framework-common",
+ "platform-compat-test-rules",
"platform-test-annotations",
],
libs: [
@@ -43,10 +44,23 @@
],
}
-// Connectivity coverage tests combines Tethering and Connectivity tests, each with their
-// respective jarjar rules applied.
-// Some tests may be duplicated (in particular static lib tests), as they need to be run under both
-// jarjared packages to cover both usages.
+// Combine Connectivity, NetworkStack and Tethering jarjar rules for coverage target.
+// The jarjar files are simply concatenated in the order specified in srcs.
+// jarjar stops at the first matching rule, so order of concatenation affects the output.
+genrule {
+ name: "ConnectivityCoverageJarJarRules",
+ srcs: [
+ "tethering-jni-jarjar-rules.txt",
+ ":connectivity-jarjar-rules",
+ ":TetheringTestsJarJarRules",
+ ":NetworkStackJarJarRules",
+ ],
+ out: ["jarjar-rules-connectivity-coverage.txt"],
+ // Concat files with a line break in the middle
+ cmd: "for src in $(in); do cat $${src}; echo; done > $(out)",
+ visibility: ["//visibility:private"],
+}
+
android_library {
name: "ConnectivityCoverageTestsLib",
min_sdk_version: "30",
@@ -54,8 +68,11 @@
"FrameworksNetTestsLib",
"NetdStaticLibTestsLib",
"NetworkStaticLibTestsLib",
+ "NetworkStackTestsLib",
+ "TetheringTestsLatestSdkLib",
+ "TetheringIntegrationTestsLatestSdkLib",
],
- jarjar_rules: ":connectivity-jarjar-rules",
+ jarjar_rules: ":ConnectivityCoverageJarJarRules",
manifest: "AndroidManifest_coverage.xml",
visibility: ["//visibility:private"],
}
@@ -80,7 +97,6 @@
"mockito-target-extended-minus-junit4",
"modules-utils-native-coverage-listener",
"ConnectivityCoverageTestsLib",
- "TetheringCoverageTestsLib",
],
jni_libs: [
// For mockito extended
@@ -124,6 +140,30 @@
],
}
+// defaults for tests that need to build against framework-connectivity's @hide APIs, but also
+// using fully @hide classes that are jarjared (because they have no API member). Similar to
+// framework-connectivity-test-defaults above but uses pre-jarjar class names.
+// Only usable from targets that have visibility on framework-connectivity-pre-jarjar, and apply
+// connectivity jarjar rules so that references to jarjared classes still match: this is limited to
+// connectivity internal tests only.
+java_defaults {
+ name: "framework-connectivity-internal-test-defaults",
+ sdk_version: "core_platform", // tests can use @CorePlatformApi's
+ libs: [
+ // order matters: classes in framework-connectivity are resolved before framework,
+ // meaning @hide APIs in framework-connectivity are resolved before @SystemApi
+ // stubs in framework
+ "framework-connectivity-pre-jarjar",
+ "framework-connectivity-t-pre-jarjar",
+ "framework-tethering.impl",
+ "framework",
+
+ // if sdk_version="" this gets automatically included, but here we need to add manually.
+ "framework-res",
+ ],
+ defaults_visibility: ["//packages/modules/Connectivity/tests:__subpackages__"],
+}
+
// Defaults for tests that want to run in mainline-presubmit.
// Not widely used because many of our tests have AndroidTest.xml files and
// use the mainline-param config-descriptor metadata in AndroidTest.xml.
diff --git a/tests/common/AndroidTest_Coverage.xml b/tests/common/AndroidTest_Coverage.xml
index 7c8e710..48d26b8 100644
--- a/tests/common/AndroidTest_Coverage.xml
+++ b/tests/common/AndroidTest_Coverage.xml
@@ -14,10 +14,13 @@
-->
<configuration description="Runs coverage tests for Connectivity">
<target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
- <option name="test-file-name" value="ConnectivityCoverageTests.apk" />
+ <option name="test-file-name" value="ConnectivityCoverageTests.apk" />
+ <option name="install-arg" value="-t" />
</target_preparer>
<option name="test-tag" value="ConnectivityCoverageTests" />
+ <!-- Tethering/Connectivity is a SDK 30+ module -->
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
<option name="config-descriptor:metadata" key="mainline-param" value="CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex" />
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.connectivity.tests.coverage" />
diff --git a/tests/common/java/android/net/EthernetNetworkManagementExceptionTest.java b/tests/common/java/android/net/EthernetNetworkManagementExceptionTest.java
new file mode 100644
index 0000000..84b6e54
--- /dev/null
+++ b/tests/common/java/android/net/EthernetNetworkManagementExceptionTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
+import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
+
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
+@RunWith(DevSdkIgnoreRunner.class)
+@SmallTest
+public class EthernetNetworkManagementExceptionTest {
+ private static final String ERROR_MESSAGE = "Test error message";
+
+ @Test
+ public void testEthernetNetworkManagementExceptionParcelable() {
+ final EthernetNetworkManagementException e =
+ new EthernetNetworkManagementException(ERROR_MESSAGE);
+
+ assertParcelingIsLossless(e);
+ }
+
+ @Test
+ public void testEthernetNetworkManagementExceptionHasExpectedErrorMessage() {
+ final EthernetNetworkManagementException e =
+ new EthernetNetworkManagementException(ERROR_MESSAGE);
+
+ assertEquals(ERROR_MESSAGE, e.getMessage());
+ }
+}
diff --git a/tests/common/java/android/net/LinkPropertiesTest.java b/tests/common/java/android/net/LinkPropertiesTest.java
index 4d85a57..581ee22 100644
--- a/tests/common/java/android/net/LinkPropertiesTest.java
+++ b/tests/common/java/android/net/LinkPropertiesTest.java
@@ -20,6 +20,7 @@
import static android.net.RouteInfo.RTN_UNICAST;
import static android.net.RouteInfo.RTN_UNREACHABLE;
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
import static com.android.testutils.ParcelUtils.parcelingRoundTrip;
@@ -30,6 +31,7 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import android.compat.testing.PlatformCompatChangeRule;
import android.net.LinkProperties.ProvisioningChange;
import android.os.Build;
import android.system.OsConstants;
@@ -44,6 +46,10 @@
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.filters.CtsNetTestCasesMaxTargetSdk31;
+
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
import org.junit.Rule;
import org.junit.Test;
@@ -65,6 +71,9 @@
@Rule
public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
+ @Rule
+ public final PlatformCompatChangeRule compatChangeRule = new PlatformCompatChangeRule();
+
private static final InetAddress ADDRV4 = address("75.208.6.1");
private static final InetAddress ADDRV6 = address("2001:0db8:85a3:0000:0000:8a2e:0370:7334");
private static final InetAddress DNS1 = address("75.208.7.1");
@@ -1253,7 +1262,20 @@
assertFalse(lp.hasIpv4UnreachableDefaultRoute());
}
+ @Test @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ @EnableCompatChanges({LinkProperties.EXCLUDED_ROUTES})
+ public void testHasExcludeRoute() {
+ LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName("tun0");
+ lp.addRoute(new RouteInfo(new IpPrefix(ADDRV4, 24), RTN_UNICAST));
+ lp.addRoute(new RouteInfo(new IpPrefix(ADDRV6, 0), RTN_UNICAST));
+ assertFalse(lp.hasExcludeRoute());
+ lp.addRoute(new RouteInfo(new IpPrefix(ADDRV6, 32), RTN_THROW));
+ assertTrue(lp.hasExcludeRoute());
+ }
+
@Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ @EnableCompatChanges({LinkProperties.EXCLUDED_ROUTES})
public void testRouteAddWithSameKey() throws Exception {
LinkProperties lp = new LinkProperties();
lp.setInterfaceName("wlan0");
@@ -1268,4 +1290,37 @@
lp.addRoute(new RouteInfo(v4, address("192.0.2.1"), "wlan0", RTN_THROW, 1460));
assertEquals(2, lp.getRoutes().size());
}
+
+ @Test @IgnoreUpTo(SC_V2)
+ @EnableCompatChanges({LinkProperties.EXCLUDED_ROUTES})
+ public void testExcludedRoutesEnabled() {
+ final LinkProperties lp = new LinkProperties();
+ assertEquals(0, lp.getRoutes().size());
+
+ lp.addRoute(new RouteInfo(new IpPrefix(ADDRV4, 0), RTN_UNREACHABLE));
+ assertEquals(1, lp.getRoutes().size());
+
+ lp.addRoute(new RouteInfo(new IpPrefix(ADDRV6, 0), RTN_THROW));
+ assertEquals(2, lp.getRoutes().size());
+
+ lp.addRoute(new RouteInfo(GATEWAY1));
+ assertEquals(3, lp.getRoutes().size());
+ }
+
+ @Test @IgnoreUpTo(SC_V2)
+ @CtsNetTestCasesMaxTargetSdk31(reason = "Compat change cannot be overridden on T or above")
+ @DisableCompatChanges({LinkProperties.EXCLUDED_ROUTES})
+ public void testExcludedRoutesDisabled() {
+ final LinkProperties lp = new LinkProperties();
+ assertEquals(0, lp.getRoutes().size());
+
+ lp.addRoute(new RouteInfo(new IpPrefix(ADDRV4, 0), RTN_UNREACHABLE));
+ assertEquals(0, lp.getRoutes().size());
+
+ lp.addRoute(new RouteInfo(new IpPrefix(ADDRV6, 5), RTN_THROW));
+ assertEquals(0, lp.getRoutes().size());
+
+ lp.addRoute(new RouteInfo(new IpPrefix(ADDRV6, 2), RTN_UNICAST));
+ assertEquals(1, lp.getRoutes().size());
+ }
}
diff --git a/tests/common/java/android/net/NetworkCapabilitiesTest.java b/tests/common/java/android/net/NetworkCapabilitiesTest.java
index 9ae5fab..c30e1d3 100644
--- a/tests/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/common/java/android/net/NetworkCapabilitiesTest.java
@@ -82,12 +82,11 @@
import android.util.ArraySet;
import android.util.Range;
-import androidx.test.runner.AndroidJUnit4;
-
import com.android.testutils.CompatUtil;
import com.android.testutils.ConnectivityModuleTest;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.Rule;
import org.junit.Test;
@@ -99,8 +98,12 @@
import java.util.List;
import java.util.Set;
-@RunWith(AndroidJUnit4.class)
@SmallTest
+@RunWith(DevSdkIgnoreRunner.class)
+// NetworkCapabilities is only updatable on S+, and this test covers behavior which implementation
+// is self-contained within NetworkCapabilities.java, so it does not need to be run on, or
+// compatible with, earlier releases.
+@IgnoreUpTo(Build.VERSION_CODES.R)
@ConnectivityModuleTest
public class NetworkCapabilitiesTest {
private static final String TEST_SSID = "TEST_SSID";
@@ -489,7 +492,7 @@
assertTrue(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+ @Test
public void testOemPrivate() {
NetworkCapabilities nc = new NetworkCapabilities();
// By default OEM_PRIVATE is neither in the required or forbidden lists and the network is
@@ -516,7 +519,7 @@
assertFalse(nr.satisfiedByNetworkCapabilities(new NetworkCapabilities()));
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+ @Test
public void testForbiddenCapabilities() {
NetworkCapabilities network = new NetworkCapabilities();
@@ -630,7 +633,7 @@
return new Range<Integer>(from, to);
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ @Test
public void testSetAdministratorUids() {
NetworkCapabilities nc =
new NetworkCapabilities().setAdministratorUids(new int[] {2, 1, 3});
@@ -638,7 +641,7 @@
assertArrayEquals(new int[] {1, 2, 3}, nc.getAdministratorUids());
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ @Test
public void testSetAdministratorUidsWithDuplicates() {
try {
new NetworkCapabilities().setAdministratorUids(new int[] {1, 1});
@@ -750,7 +753,7 @@
() -> nc2.addTransportType(TRANSPORT_WIFI));
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.R) // New behavior in updatable NetworkCapabilities (S+)
+ @Test
public void testSetNetworkSpecifierOnTestMultiTransportNc() {
final NetworkSpecifier specifier = CompatUtil.makeEthernetNetworkSpecifier("eth0");
NetworkCapabilities nc = new NetworkCapabilities.Builder()
@@ -859,7 +862,7 @@
assertEquals(TRANSPORT_TEST, transportTypes[3]);
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ @Test
public void testTelephonyNetworkSpecifier() {
final TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier(1);
final NetworkCapabilities nc1 = new NetworkCapabilities.Builder()
@@ -970,7 +973,7 @@
assertEquals(specifier, nc.getNetworkSpecifier());
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ @Test
public void testAdministratorUidsAndOwnerUid() {
// Test default owner uid.
// If the owner uid is not set, the default value should be Process.INVALID_UID.
@@ -1014,7 +1017,7 @@
return nc;
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+ @Test
public void testSubIds() throws Exception {
final NetworkCapabilities ncWithoutId = capsWithSubIds();
final NetworkCapabilities ncWithId = capsWithSubIds(TEST_SUBID1);
@@ -1036,7 +1039,7 @@
assertTrue(requestWithoutId.canBeSatisfiedBy(ncWithId));
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+ @Test
public void testEqualsSubIds() throws Exception {
assertEquals(capsWithSubIds(), capsWithSubIds());
assertNotEquals(capsWithSubIds(), capsWithSubIds(TEST_SUBID1));
@@ -1185,7 +1188,7 @@
}
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ @Test
public void testBuilder() {
final int ownerUid = 1001;
final int signalStrength = -80;
@@ -1255,7 +1258,7 @@
}
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+ @Test
public void testBuilderWithoutDefaultCap() {
final NetworkCapabilities nc =
NetworkCapabilities.Builder.withoutDefaultCapabilities().build();
@@ -1266,12 +1269,12 @@
assertEquals(0, nc.getCapabilities().length);
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+ @Test
public void testRestrictCapabilitiesForTestNetworkByNotOwnerWithNonRestrictedNc() {
testRestrictCapabilitiesForTestNetworkWithNonRestrictedNc(false /* isOwner */);
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+ @Test
public void testRestrictCapabilitiesForTestNetworkByOwnerWithNonRestrictedNc() {
testRestrictCapabilitiesForTestNetworkWithNonRestrictedNc(true /* isOwner */);
}
@@ -1316,12 +1319,12 @@
assertEquals(expectedNcBuilder.build(), nonRestrictedNc);
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+ @Test
public void testRestrictCapabilitiesForTestNetworkByNotOwnerWithRestrictedNc() {
testRestrictCapabilitiesForTestNetworkWithRestrictedNc(false /* isOwner */);
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+ @Test
public void testRestrictCapabilitiesForTestNetworkByOwnerWithRestrictedNc() {
testRestrictCapabilitiesForTestNetworkWithRestrictedNc(true /* isOwner */);
}
diff --git a/tests/common/java/android/net/netstats/NetworkStatsCollectionTest.kt b/tests/common/java/android/net/netstats/NetworkStatsCollectionTest.kt
new file mode 100644
index 0000000..368a519
--- /dev/null
+++ b/tests/common/java/android/net/netstats/NetworkStatsCollectionTest.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.netstats
+
+import android.net.NetworkIdentity
+import android.net.NetworkStatsCollection
+import android.net.NetworkStatsHistory
+import androidx.test.filters.SmallTest
+import com.android.testutils.ConnectivityModuleTest
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.SC_V2
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import kotlin.test.assertEquals
+import kotlin.test.fail
+
+@ConnectivityModuleTest
+@RunWith(JUnit4::class)
+@SmallTest
+class NetworkStatsCollectionTest {
+ @Rule
+ @JvmField
+ val ignoreRule = DevSdkIgnoreRule(ignoreClassUpTo = SC_V2)
+
+ @Test
+ fun testBuilder() {
+ val ident = setOf<NetworkIdentity>()
+ val key1 = NetworkStatsCollection.Key(ident, /* uid */ 0, /* set */ 0, /* tag */ 0)
+ val key2 = NetworkStatsCollection.Key(ident, /* uid */ 1, /* set */ 0, /* tag */ 0)
+ val bucketDuration = 10L
+ val entry1 = NetworkStatsHistory.Entry(10, 10, 40, 4, 50, 5, 60)
+ val entry2 = NetworkStatsHistory.Entry(30, 10, 3, 41, 7, 1, 0)
+ val history1 = NetworkStatsHistory.Builder(10, 5)
+ .addEntry(entry1)
+ .addEntry(entry2)
+ .build()
+ val history2 = NetworkStatsHistory(10, 5)
+ val actualCollection = NetworkStatsCollection.Builder(bucketDuration)
+ .addEntry(key1, history1)
+ .addEntry(key2, history2)
+ .build()
+
+ // The builder will omit any entry with empty history. Thus, only history1
+ // is expected in the result collection.
+ val actualEntries = actualCollection.entries
+ assertEquals(1, actualEntries.size)
+ val actualHistory = actualEntries[key1] ?: fail("There should be an entry for $key1")
+ assertEquals(history1.entries, actualHistory.entries)
+ }
+}
diff --git a/tests/common/java/android/net/netstats/NetworkStatsHistoryTest.kt b/tests/common/java/android/net/netstats/NetworkStatsHistoryTest.kt
new file mode 100644
index 0000000..f8e041a
--- /dev/null
+++ b/tests/common/java/android/net/netstats/NetworkStatsHistoryTest.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.netstats
+
+import android.net.NetworkStatsHistory
+import android.text.format.DateUtils
+import androidx.test.filters.SmallTest
+import com.android.testutils.ConnectivityModuleTest
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.SC_V2
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+
+@ConnectivityModuleTest
+@RunWith(JUnit4::class)
+@SmallTest
+class NetworkStatsHistoryTest {
+ @Rule
+ @JvmField
+ val ignoreRule = DevSdkIgnoreRule(ignoreClassUpTo = SC_V2)
+
+ @Test
+ fun testBuilder() {
+ val entry1 = NetworkStatsHistory.Entry(10, 30, 40, 4, 50, 5, 60)
+ val entry2 = NetworkStatsHistory.Entry(30, 15, 3, 41, 7, 1, 0)
+ val entry3 = NetworkStatsHistory.Entry(7, 301, 11, 14, 31, 2, 80)
+ val statsEmpty = NetworkStatsHistory
+ .Builder(DateUtils.HOUR_IN_MILLIS, /* initialCapacity */ 10).build()
+ assertEquals(0, statsEmpty.entries.size)
+ assertEquals(DateUtils.HOUR_IN_MILLIS, statsEmpty.bucketDuration)
+ val statsSingle = NetworkStatsHistory
+ .Builder(DateUtils.HOUR_IN_MILLIS, /* initialCapacity */ 8)
+ .addEntry(entry1)
+ .build()
+ statsSingle.assertEntriesEqual(entry1)
+ assertEquals(DateUtils.HOUR_IN_MILLIS, statsSingle.bucketDuration)
+
+ // Verify the builder throws if the timestamp of added entry is not greater than
+ // that of any previously-added entry.
+ assertFailsWith(IllegalArgumentException::class) {
+ NetworkStatsHistory
+ .Builder(DateUtils.SECOND_IN_MILLIS, /* initialCapacity */ 0)
+ .addEntry(entry1).addEntry(entry2).addEntry(entry3)
+ .build()
+ }
+
+ val statsMultiple = NetworkStatsHistory
+ .Builder(DateUtils.SECOND_IN_MILLIS, /* initialCapacity */ 0)
+ .addEntry(entry3).addEntry(entry1).addEntry(entry2)
+ .build()
+ assertEquals(DateUtils.SECOND_IN_MILLIS, statsMultiple.bucketDuration)
+ statsMultiple.assertEntriesEqual(entry3, entry1, entry2)
+ }
+
+ fun NetworkStatsHistory.assertEntriesEqual(vararg entries: NetworkStatsHistory.Entry) {
+ assertEquals(entries.size, this.entries.size)
+ entries.forEachIndexed { i, element ->
+ assertEquals(element, this.entries[i])
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/common/java/android/net/netstats/NetworkTemplateTest.kt b/tests/common/java/android/net/netstats/NetworkTemplateTest.kt
new file mode 100644
index 0000000..192694b
--- /dev/null
+++ b/tests/common/java/android/net/netstats/NetworkTemplateTest.kt
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.netstats
+
+import android.net.NetworkStats.DEFAULT_NETWORK_ALL
+import android.net.NetworkStats.METERED_ALL
+import android.net.NetworkStats.METERED_YES
+import android.net.NetworkStats.ROAMING_YES
+import android.net.NetworkStats.ROAMING_ALL
+import android.net.NetworkTemplate
+import android.net.NetworkTemplate.MATCH_BLUETOOTH
+import android.net.NetworkTemplate.MATCH_CARRIER
+import android.net.NetworkTemplate.MATCH_ETHERNET
+import android.net.NetworkTemplate.MATCH_MOBILE
+import android.net.NetworkTemplate.MATCH_MOBILE_WILDCARD
+import android.net.NetworkTemplate.MATCH_PROXY
+import android.net.NetworkTemplate.MATCH_WIFI
+import android.net.NetworkTemplate.MATCH_WIFI_WILDCARD
+import android.net.NetworkTemplate.NETWORK_TYPE_ALL
+import android.net.NetworkTemplate.OEM_MANAGED_ALL
+import android.telephony.TelephonyManager
+import com.android.net.module.util.NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_ALL
+import com.android.net.module.util.NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT
+import com.android.testutils.ConnectivityModuleTest
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.SC_V2
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+
+private const val TEST_IMSI1 = "imsi"
+private const val TEST_WIFI_KEY1 = "wifiKey1"
+private const val TEST_WIFI_KEY2 = "wifiKey2"
+
+@RunWith(JUnit4::class)
+@ConnectivityModuleTest
+class NetworkTemplateTest {
+ @Rule
+ @JvmField
+ val ignoreRule = DevSdkIgnoreRule(ignoreClassUpTo = SC_V2)
+
+ @Test
+ fun testBuilderMatchRules() {
+ // Verify unknown match rules cannot construct templates.
+ listOf(Integer.MIN_VALUE, -1, Integer.MAX_VALUE).forEach {
+ assertFailsWith<IllegalArgumentException> {
+ NetworkTemplate.Builder(it).build()
+ }
+ }
+
+ // Verify hidden match rules cannot construct templates.
+ listOf(MATCH_WIFI_WILDCARD, MATCH_MOBILE_WILDCARD, MATCH_PROXY).forEach {
+ assertFailsWith<IllegalArgumentException> {
+ NetworkTemplate.Builder(it).build()
+ }
+ }
+
+ // Verify template which matches metered cellular and carrier networks with
+ // the given IMSI. See buildTemplateMobileAll and buildTemplateCarrierMetered.
+ listOf(MATCH_MOBILE, MATCH_CARRIER).forEach { matchRule ->
+ NetworkTemplate.Builder(matchRule).setSubscriberIds(setOf(TEST_IMSI1))
+ .setMeteredness(METERED_YES).build().let {
+ val expectedTemplate = NetworkTemplate(matchRule, TEST_IMSI1,
+ arrayOf(TEST_IMSI1), arrayOf<String>(), METERED_YES,
+ ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
+ OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_EXACT)
+ assertEquals(expectedTemplate, it)
+ }
+ }
+
+ // Verify template which matches roaming cellular and carrier networks with
+ // the given IMSI.
+ listOf(MATCH_MOBILE, MATCH_CARRIER).forEach { matchRule ->
+ NetworkTemplate.Builder(matchRule).setSubscriberIds(setOf(TEST_IMSI1))
+ .setRoaming(ROAMING_YES).setMeteredness(METERED_YES).build().let {
+ val expectedTemplate = NetworkTemplate(matchRule, TEST_IMSI1,
+ arrayOf(TEST_IMSI1), arrayOf<String>(), METERED_YES,
+ ROAMING_YES, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
+ OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_EXACT)
+ assertEquals(expectedTemplate, it)
+ }
+ }
+
+ // Verify carrier template cannot be created without IMSI.
+ assertFailsWith<IllegalArgumentException> {
+ NetworkTemplate.Builder(MATCH_CARRIER).build()
+ }
+
+ // Verify template which matches metered cellular networks,
+ // regardless of IMSI. See buildTemplateMobileWildcard.
+ NetworkTemplate.Builder(MATCH_MOBILE).setMeteredness(METERED_YES).build().let {
+ val expectedTemplate = NetworkTemplate(MATCH_MOBILE_WILDCARD, null /*subscriberId*/,
+ null /*subscriberIds*/, arrayOf<String>(),
+ METERED_YES, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
+ OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_ALL)
+ assertEquals(expectedTemplate, it)
+ }
+
+ // Verify template which matches metered cellular networks and ratType.
+ // See NetworkTemplate#buildTemplateMobileWithRatType.
+ NetworkTemplate.Builder(MATCH_MOBILE).setSubscriberIds(setOf(TEST_IMSI1))
+ .setMeteredness(METERED_YES).setRatType(TelephonyManager.NETWORK_TYPE_UMTS)
+ .build().let {
+ val expectedTemplate = NetworkTemplate(MATCH_MOBILE, TEST_IMSI1,
+ arrayOf(TEST_IMSI1), arrayOf<String>(), METERED_YES,
+ ROAMING_ALL, DEFAULT_NETWORK_ALL, TelephonyManager.NETWORK_TYPE_UMTS,
+ OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_EXACT)
+ assertEquals(expectedTemplate, it)
+ }
+
+ // Verify template which matches all wifi networks,
+ // regardless of Wifi Network Key. See buildTemplateWifiWildcard and buildTemplateWifi.
+ NetworkTemplate.Builder(MATCH_WIFI).build().let {
+ val expectedTemplate = NetworkTemplate(MATCH_WIFI_WILDCARD, null /*subscriberId*/,
+ null /*subscriberIds*/, arrayOf<String>(),
+ METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
+ OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_ALL)
+ assertEquals(expectedTemplate, it)
+ }
+
+ // Verify template which matches wifi networks with the given Wifi Network Key.
+ // See buildTemplateWifi(wifiNetworkKey).
+ NetworkTemplate.Builder(MATCH_WIFI).setWifiNetworkKeys(setOf(TEST_WIFI_KEY1)).build().let {
+ val expectedTemplate = NetworkTemplate(MATCH_WIFI, null /*subscriberId*/,
+ null /*subscriberIds*/, arrayOf(TEST_WIFI_KEY1),
+ METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
+ OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_ALL)
+ assertEquals(expectedTemplate, it)
+ }
+
+ // Verify template which matches all wifi networks with the
+ // given Wifi Network Key, and IMSI. See buildTemplateWifi(wifiNetworkKey, subscriberId).
+ NetworkTemplate.Builder(MATCH_WIFI).setSubscriberIds(setOf(TEST_IMSI1))
+ .setWifiNetworkKeys(setOf(TEST_WIFI_KEY1)).build().let {
+ val expectedTemplate = NetworkTemplate(MATCH_WIFI, TEST_IMSI1,
+ arrayOf(TEST_IMSI1), arrayOf(TEST_WIFI_KEY1),
+ METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
+ OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_EXACT)
+ assertEquals(expectedTemplate, it)
+ }
+
+ // Verify template which matches ethernet and bluetooth networks.
+ // See buildTemplateEthernet and buildTemplateBluetooth.
+ listOf(MATCH_ETHERNET, MATCH_BLUETOOTH).forEach { matchRule ->
+ NetworkTemplate.Builder(matchRule).build().let {
+ val expectedTemplate = NetworkTemplate(matchRule, null /*subscriberId*/,
+ null /*subscriberIds*/, arrayOf<String>(),
+ METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
+ OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_ALL)
+ assertEquals(expectedTemplate, it)
+ }
+ }
+ }
+
+ @Test
+ fun testBuilderWifiNetworkKeys() {
+ // Verify template builder which generates same template with the given different
+ // sequence keys.
+ NetworkTemplate.Builder(MATCH_WIFI).setWifiNetworkKeys(
+ setOf(TEST_WIFI_KEY1, TEST_WIFI_KEY2)).build().let {
+ val expectedTemplate = NetworkTemplate.Builder(MATCH_WIFI).setWifiNetworkKeys(
+ setOf(TEST_WIFI_KEY2, TEST_WIFI_KEY1)).build()
+ assertEquals(expectedTemplate, it)
+ }
+
+ // Verify template which matches non-wifi networks with the given key is invalid.
+ listOf(MATCH_MOBILE, MATCH_CARRIER, MATCH_ETHERNET, MATCH_BLUETOOTH, -1,
+ Integer.MAX_VALUE).forEach { matchRule ->
+ assertFailsWith<IllegalArgumentException> {
+ NetworkTemplate.Builder(matchRule).setWifiNetworkKeys(setOf(TEST_WIFI_KEY1)).build()
+ }
+ }
+
+ // Verify template which matches wifi networks with the given null key is invalid.
+ assertFailsWith<IllegalArgumentException> {
+ NetworkTemplate.Builder(MATCH_WIFI).setWifiNetworkKeys(setOf(null)).build()
+ }
+
+ // Verify template which matches wifi wildcard with the given empty key set.
+ NetworkTemplate.Builder(MATCH_WIFI).setWifiNetworkKeys(setOf<String>()).build().let {
+ val expectedTemplate = NetworkTemplate(MATCH_WIFI_WILDCARD, null /*subscriberId*/,
+ arrayOf<String>() /*subscriberIds*/, arrayOf<String>(),
+ METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
+ OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_ALL)
+ assertEquals(expectedTemplate, it)
+ }
+ }
+}
diff --git a/tests/common/tethering-jni-jarjar-rules.txt b/tests/common/tethering-jni-jarjar-rules.txt
new file mode 100644
index 0000000..593ba14
--- /dev/null
+++ b/tests/common/tethering-jni-jarjar-rules.txt
@@ -0,0 +1,10 @@
+# Match the tethering jarjar rules for utils backed by
+# libcom_android_networkstack_tethering_util_jni, so that this JNI library can be used as-is in the
+# test. The alternative would be to build a test-specific JNI library
+# (libcom_android_connectivity_tests_coverage_jni ?) that registers classes following whatever
+# jarjar rules the test is using, but this is a bit less realistic (using a different JNI library),
+# and complicates the test build. It would be necessary if TetheringUtils had a different package
+# name in test code though, as the JNI library name is deducted from the TetheringUtils package.
+rule com.android.net.module.util.BpfMap* com.android.networkstack.tethering.util.BpfMap@1
+rule com.android.net.module.util.BpfUtils* com.android.networkstack.tethering.util.BpfUtils@1
+rule com.android.net.module.util.TcUtils* com.android.networkstack.tethering.util.TcUtils@1
diff --git a/tests/cts/OWNERS b/tests/cts/OWNERS
index 8dfa455..875b4a2 100644
--- a/tests/cts/OWNERS
+++ b/tests/cts/OWNERS
@@ -1,3 +1,3 @@
-# Bug component: 31808
+# Bug template url: http://b/new?component=31808
set noparent
file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking_xts
diff --git a/tests/cts/hostside/Android.bp b/tests/cts/hostside/Android.bp
index b684068..ac84e57 100644
--- a/tests/cts/hostside/Android.bp
+++ b/tests/cts/hostside/Android.bp
@@ -26,6 +26,7 @@
"tradefed",
],
static_libs: [
+ "CompatChangeGatingTestBase",
"modules-utils-build-testing",
],
// Tag this module as a cts test artifact
@@ -34,4 +35,12 @@
"general-tests",
"sts"
],
+ data: [
+ ":CtsHostsideNetworkTestsApp",
+ ":CtsHostsideNetworkTestsApp2",
+ ":CtsHostsideNetworkTestsApp3",
+ ":CtsHostsideNetworkTestsApp3PreT",
+ ":CtsHostsideNetworkTestsAppNext",
+ ],
+ per_testcase_directory: true,
}
diff --git a/tests/cts/hostside/TEST_MAPPING b/tests/cts/hostside/TEST_MAPPING
index fcec483..ab6de82 100644
--- a/tests/cts/hostside/TEST_MAPPING
+++ b/tests/cts/hostside/TEST_MAPPING
@@ -4,9 +4,6 @@
"name": "CtsHostsideNetworkTests",
"options": [
{
- "include-filter": "com.android.cts.net.HostsideRestrictBackgroundNetworkTests"
- },
- {
"exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
diff --git a/tests/cts/hostside/aidl/com/android/cts/net/hostside/IMyService.aidl b/tests/cts/hostside/aidl/com/android/cts/net/hostside/IMyService.aidl
index 28437c2..e7b2815 100644
--- a/tests/cts/hostside/aidl/com/android/cts/net/hostside/IMyService.aidl
+++ b/tests/cts/hostside/aidl/com/android/cts/net/hostside/IMyService.aidl
@@ -28,5 +28,5 @@
void sendNotification(int notificationId, String notificationType);
void registerNetworkCallback(in NetworkRequest request, in INetworkCallback cb);
void unregisterNetworkCallback();
- void scheduleJob(in JobInfo jobInfo);
+ int scheduleJob(in JobInfo jobInfo);
}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index a840242..93e9dcd 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -16,6 +16,7 @@
package com.android.cts.net.hostside;
+import static android.app.job.JobScheduler.RESULT_SUCCESS;
import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
import static android.os.BatteryManager.BATTERY_PLUGGED_AC;
import static android.os.BatteryManager.BATTERY_PLUGGED_USB;
@@ -149,9 +150,9 @@
private static final String APP_NOT_FOREGROUND_ERROR = "app_not_fg";
- protected static final long TEMP_POWERSAVE_WHITELIST_DURATION_MS = 5_000; // 5 sec
+ protected static final long TEMP_POWERSAVE_WHITELIST_DURATION_MS = 20_000; // 20 sec
- private static final long BROADCAST_TIMEOUT_MS = 15_000;
+ private static final long BROADCAST_TIMEOUT_MS = 5_000;
protected Context mContext;
protected Instrumentation mInstrumentation;
@@ -216,7 +217,10 @@
Log.d(TAG, "Expecting count " + expectedCount + " but actual is " + count + " after "
+ attempts + " attempts; sleeping "
+ SLEEP_TIME_SEC + " seconds before trying again");
- SystemClock.sleep(SLEEP_TIME_SEC * SECOND_IN_MS);
+ // No sleep after the last turn
+ if (attempts <= maxAttempts) {
+ SystemClock.sleep(SLEEP_TIME_SEC * SECOND_IN_MS);
+ }
} while (attempts <= maxAttempts);
assertEquals("Number of expected broadcasts for " + receiverName + " not reached after "
+ maxAttempts * SLEEP_TIME_SEC + " seconds", expectedCount, count);
@@ -327,7 +331,10 @@
}
Log.d(TAG, "App not on background state (" + state + ") on attempt #" + i
+ "; sleeping 1s before trying again");
- SystemClock.sleep(SECOND_IN_MS);
+ // No sleep after the last turn
+ if (i < maxTries) {
+ SystemClock.sleep(SECOND_IN_MS);
+ }
}
fail("App2 (" + mUid + ") is not on background state after "
+ maxTries + " attempts: " + state);
@@ -346,7 +353,10 @@
Log.d(TAG, "App not on foreground state on attempt #" + i
+ "; sleeping 1s before trying again");
turnScreenOn();
- SystemClock.sleep(SECOND_IN_MS);
+ // No sleep after the last turn
+ if (i < maxTries) {
+ SystemClock.sleep(SECOND_IN_MS);
+ }
}
fail("App2 (" + mUid + ") is not on foreground state after "
+ maxTries + " attempts: " + state);
@@ -364,7 +374,10 @@
}
Log.d(TAG, "App not on foreground service state on attempt #" + i
+ "; sleeping 1s before trying again");
- SystemClock.sleep(SECOND_IN_MS);
+ // No sleep after the last turn
+ if (i < maxTries) {
+ SystemClock.sleep(SECOND_IN_MS);
+ }
}
fail("App2 (" + mUid + ") is not on foreground service state after "
+ maxTries + " attempts: " + state);
@@ -505,7 +518,10 @@
Log.v(TAG, "Command '" + command + "' returned '" + result + " instead of '"
+ checker.getExpected() + "' on attempt #" + i
+ "; sleeping " + napTimeSeconds + "s before trying again");
- SystemClock.sleep(napTimeSeconds * SECOND_IN_MS);
+ // No sleep after the last turn
+ if (i < maxTries) {
+ SystemClock.sleep(napTimeSeconds * SECOND_IN_MS);
+ }
}
fail("Command '" + command + "' did not return '" + checker.getExpected() + "' after "
+ maxTries
@@ -577,7 +593,10 @@
}
Log.v(TAG, list + " check for uid " + uid + " doesn't match yet (expected "
+ expected + ", got " + actual + "); sleeping 1s before polling again");
- SystemClock.sleep(SECOND_IN_MS);
+ // No sleep after the last turn
+ if (i < maxTries) {
+ SystemClock.sleep(SECOND_IN_MS);
+ }
}
fail(list + " check for uid " + uid + " failed: expected " + expected + ", got " + actual
+ ". Full list: " + uids);
@@ -737,7 +756,8 @@
protected void assertAppIdle(boolean enabled) throws Exception {
try {
- assertDelayedShellCommand("am get-inactive " + TEST_APP2_PKG, 15, 2, "Idle=" + enabled);
+ assertDelayedShellCommand("am get-inactive " + TEST_APP2_PKG,
+ 30 /* maxTries */, 1 /* napTimeSeconds */, "Idle=" + enabled);
} catch (Throwable e) {
throw e;
}
@@ -764,7 +784,10 @@
return;
}
Log.v(TAG, "app2 receiver is not ready yet; sleeping 1s before polling again");
- SystemClock.sleep(SECOND_IN_MS);
+ // No sleep after the last turn
+ if (i < maxTries) {
+ SystemClock.sleep(SECOND_IN_MS);
+ }
}
fail("app2 receiver is not ready in " + mUid);
}
@@ -813,8 +836,6 @@
return;
} else if (type == TYPE_COMPONENT_ACTIVTIY) {
turnScreenOn();
- // Wait for screen-on state to propagate through the system.
- SystemClock.sleep(2000);
final CountDownLatch latch = new CountDownLatch(1);
final Intent launchIntent = getIntentForComponent(type);
final Bundle extras = new Bundle();
@@ -855,7 +876,8 @@
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.setTransientExtras(extras)
.build();
- mServiceClient.scheduleJob(jobInfo);
+ assertEquals("Error scheduling " + jobInfo,
+ RESULT_SUCCESS, mServiceClient.scheduleJob(jobInfo));
forceRunJob(TEST_APP2_PKG, TEST_JOB_ID);
if (latch.await(JOB_NETWORK_STATE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
final int resultCode = result.get(0).first;
@@ -896,7 +918,7 @@
final Intent intent = new Intent();
if (type == TYPE_COMPONENT_ACTIVTIY) {
intent.setComponent(new ComponentName(TEST_APP2_PKG, TEST_APP2_ACTIVITY_CLASS))
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
} else if (type == TYPE_COMPONENT_FOREGROUND_SERVICE) {
intent.setComponent(new ComponentName(TEST_APP2_PKG, TEST_APP2_SERVICE_CLASS))
.setFlags(1);
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/ConnOnActivityStartTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/ConnOnActivityStartTest.java
new file mode 100644
index 0000000..098f295
--- /dev/null
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/ConnOnActivityStartTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.net.hostside;
+
+
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getUiDevice;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setRestrictBackground;
+import static com.android.cts.net.hostside.Property.APP_STANDBY_MODE;
+import static com.android.cts.net.hostside.Property.BATTERY_SAVER_MODE;
+import static com.android.cts.net.hostside.Property.DATA_SAVER_MODE;
+import static com.android.cts.net.hostside.Property.DOZE_MODE;
+import static com.android.cts.net.hostside.Property.METERED_NETWORK;
+import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
+
+import android.util.Log;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+@RequiredProperties({NON_METERED_NETWORK})
+public class ConnOnActivityStartTest extends AbstractRestrictBackgroundNetworkTestCase {
+ private static final int TEST_ITERATION_COUNT = 5;
+
+ @Before
+ public final void setUp() throws Exception {
+ super.setUp();
+ resetDeviceState();
+ }
+
+ @After
+ public final void tearDown() throws Exception {
+ super.tearDown();
+ resetDeviceState();
+ }
+
+ private void resetDeviceState() throws Exception {
+ resetBatteryState();
+ setBatterySaverMode(false);
+ setRestrictBackground(false);
+ setAppIdle(false);
+ setDozeMode(false);
+ }
+
+
+ @Test
+ @RequiredProperties({BATTERY_SAVER_MODE})
+ public void testStartActivity_batterySaver() throws Exception {
+ setBatterySaverMode(true);
+ assertLaunchedActivityHasNetworkAccess("testStartActivity_batterySaver");
+ }
+
+ @Test
+ @RequiredProperties({DATA_SAVER_MODE, METERED_NETWORK})
+ public void testStartActivity_dataSaver() throws Exception {
+ setRestrictBackground(true);
+ assertLaunchedActivityHasNetworkAccess("testStartActivity_dataSaver");
+ }
+
+ @Test
+ @RequiredProperties({DOZE_MODE})
+ public void testStartActivity_doze() throws Exception {
+ setDozeMode(true);
+ assertLaunchedActivityHasNetworkAccess("testStartActivity_doze");
+ }
+
+ @Test
+ @RequiredProperties({APP_STANDBY_MODE})
+ public void testStartActivity_appStandby() throws Exception {
+ turnBatteryOn();
+ setAppIdle(true);
+ assertLaunchedActivityHasNetworkAccess("testStartActivity_appStandby");
+ }
+
+ private void assertLaunchedActivityHasNetworkAccess(String testName) throws Exception {
+ for (int i = 0; i < TEST_ITERATION_COUNT; ++i) {
+ Log.i(TAG, testName + " start #" + i);
+ launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
+ getUiDevice().pressHome();
+ assertBackgroundState();
+ Log.i(TAG, testName + " end #" + i);
+ }
+ }
+}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java
index 8b70f9b..0610774 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java
@@ -107,7 +107,7 @@
mService.unregisterNetworkCallback();
}
- public void scheduleJob(JobInfo jobInfo) throws RemoteException {
- mService.scheduleJob(jobInfo);
+ public int scheduleJob(JobInfo jobInfo) throws RemoteException {
+ return mService.scheduleJob(jobInfo);
}
}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
index 89a9bd6..c53276b 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
@@ -25,7 +25,7 @@
import static android.net.wifi.WifiConfiguration.METERED_OVERRIDE_METERED;
import static android.net.wifi.WifiConfiguration.METERED_OVERRIDE_NONE;
-import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow;
import static com.android.cts.net.hostside.AbstractRestrictBackgroundNetworkTestCase.TAG;
import static org.junit.Assert.assertEquals;
@@ -38,6 +38,7 @@
import android.app.Instrumentation;
import android.app.UiAutomation;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.location.LocationManager;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
@@ -56,6 +57,7 @@
import android.util.Log;
import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.uiautomator.UiDevice;
import com.android.compatibility.common.util.AppStandbyUtils;
import com.android.compatibility.common.util.BatteryUtils;
@@ -99,6 +101,10 @@
return mBatterySaverSupported;
}
+ private static boolean isWear() {
+ return getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
+ }
+
/**
* As per CDD requirements, if the device doesn't support data saver mode then
* ConnectivityManager.getRestrictBackgroundStatus() will always return
@@ -107,6 +113,9 @@
* RESTRICT_BACKGROUND_STATUS_DISABLED or not.
*/
public static boolean isDataSaverSupported() {
+ if (isWear()) {
+ return false;
+ }
if (mDataSaverSupported == null) {
assertMyRestrictBackgroundStatus(RESTRICT_BACKGROUND_STATUS_DISABLED);
try {
@@ -382,7 +391,7 @@
}
public static String executeShellCommand(String command) {
- final String result = runShellCommand(command).trim();
+ final String result = runShellCommandOrThrow(command).trim();
Log.d(TAG, "Output of '" + command + "': '" + result + "'");
return result;
}
@@ -430,6 +439,10 @@
return InstrumentationRegistry.getInstrumentation();
}
+ public static UiDevice getUiDevice() {
+ return UiDevice.getInstance(getInstrumentation());
+ }
+
// When power saver mode or restrict background enabled or adding any white/black list into
// those modes, NetworkPolicy may need to take some time to update the rules of uids. So having
// this function and using PollingCheck to try to make sure the uid has updated and reduce the
diff --git a/tests/cts/hostside/app2/Android.bp b/tests/cts/hostside/app2/Android.bp
index 01c8cd2..edfaf9f 100644
--- a/tests/cts/hostside/app2/Android.bp
+++ b/tests/cts/hostside/app2/Android.bp
@@ -23,6 +23,7 @@
defaults: ["cts_support_defaults"],
sdk_version: "test_current",
static_libs: [
+ "androidx.annotation_annotation",
"CtsHostsideNetworkTestsAidl",
"NetworkStackApiStableShims",
],
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java
index 9fdb9c9..82f13ae 100644
--- a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java
+++ b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java
@@ -29,6 +29,9 @@
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Log;
+import android.view.WindowManager;
+
+import androidx.annotation.GuardedBy;
import com.android.cts.net.hostside.INetworkStateObserver;
@@ -37,27 +40,24 @@
*/
public class MyActivity extends Activity {
+ @GuardedBy("this")
private BroadcastReceiver finishCommandReceiver = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "MyActivity.onCreate()");
- Common.notifyNetworkStateObserver(this, getIntent(), TYPE_COMPONENT_ACTIVTY);
- finishCommandReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- Log.d(TAG, "Finishing MyActivity");
- MyActivity.this.finish();
- }
- };
- registerReceiver(finishCommandReceiver, new IntentFilter(ACTION_FINISH_ACTIVITY));
+
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
@Override
public void finish() {
- if (finishCommandReceiver != null) {
- unregisterReceiver(finishCommandReceiver);
+ synchronized (this) {
+ if (finishCommandReceiver != null) {
+ unregisterReceiver(finishCommandReceiver);
+ finishCommandReceiver = null;
+ }
}
super.finish();
}
@@ -69,6 +69,31 @@
}
@Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ Log.d(TAG, "MyActivity.onNewIntent()");
+ setIntent(intent);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ Log.d(TAG, "MyActivity.onResume(): " + getIntent());
+ Common.notifyNetworkStateObserver(this, getIntent(), TYPE_COMPONENT_ACTIVTY);
+ synchronized (this) {
+ finishCommandReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.d(TAG, "Finishing MyActivity");
+ MyActivity.this.finish();
+ }
+ };
+ registerReceiver(finishCommandReceiver, new IntentFilter(ACTION_FINISH_ACTIVITY),
+ Context.RECEIVER_EXPORTED);
+ }
+ }
+
+ @Override
protected void onDestroy() {
Log.d(TAG, "MyActivity.onDestroy()");
super.onDestroy();
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java
index f2a7b3f..3ed5391 100644
--- a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java
+++ b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java
@@ -165,10 +165,10 @@
}
@Override
- public void scheduleJob(JobInfo jobInfo) {
+ public int scheduleJob(JobInfo jobInfo) {
final JobScheduler jobScheduler = getApplicationContext()
.getSystemService(JobScheduler.class);
- jobScheduler.schedule(jobInfo);
+ return jobScheduler.schedule(jobInfo);
}
};
diff --git a/tests/cts/hostside/app3/Android.bp b/tests/cts/hostside/app3/Android.bp
new file mode 100644
index 0000000..141cf03
--- /dev/null
+++ b/tests/cts/hostside/app3/Android.bp
@@ -0,0 +1,54 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_defaults {
+ name: "CtsHostsideNetworkTestsApp3Defaults",
+ srcs: ["src/**/*.java"],
+ libs: [
+ "junit",
+ ],
+ static_libs: [
+ "ctstestrunner-axt",
+ "truth-prebuilt",
+ ],
+
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+}
+
+android_test_helper_app {
+ name: "CtsHostsideNetworkTestsApp3",
+ defaults: [
+ "cts_support_defaults",
+ "CtsHostsideNetworkTestsApp3Defaults",
+ ],
+}
+
+android_test_helper_app {
+ name: "CtsHostsideNetworkTestsApp3PreT",
+ target_sdk_version: "31",
+ defaults: [
+ "cts_support_defaults",
+ "CtsHostsideNetworkTestsApp3Defaults",
+ ],
+}
diff --git a/tests/cts/hostside/app3/AndroidManifest.xml b/tests/cts/hostside/app3/AndroidManifest.xml
new file mode 100644
index 0000000..eabcacb
--- /dev/null
+++ b/tests/cts/hostside/app3/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.net.hostside.app3">
+
+ <application android:debuggable="true">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.cts.net.hostside.app3" />
+
+</manifest>
diff --git a/tests/cts/hostside/app3/src/com/android/cts/net/hostside/app3/ExcludedRoutesGatingTest.java b/tests/cts/hostside/app3/src/com/android/cts/net/hostside/app3/ExcludedRoutesGatingTest.java
new file mode 100644
index 0000000..a1a8209
--- /dev/null
+++ b/tests/cts/hostside/app3/src/com/android/cts/net/hostside/app3/ExcludedRoutesGatingTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.net.hostside.app3;
+
+import static org.junit.Assert.assertEquals;
+
+import android.Manifest;
+import android.net.IpPrefix;
+import android.net.LinkProperties;
+import android.net.RouteInfo;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests to verify {@link LinkProperties#getRoutes} behavior, depending on
+ * {@LinkProperties#EXCLUDED_ROUTES} change state.
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExcludedRoutesGatingTest {
+ @Before
+ public void setUp() {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .adoptShellPermissionIdentity(Manifest.permission.LOG_COMPAT_CHANGE,
+ Manifest.permission.READ_COMPAT_CHANGE_CONFIG);
+ }
+
+ @After
+ public void tearDown() {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .dropShellPermissionIdentity();
+ }
+
+ @Test
+ public void testExcludedRoutesChangeEnabled() {
+ final LinkProperties lp = makeLinkPropertiesWithExcludedRoutes();
+
+ // Excluded routes change is enabled: non-RTN_UNICAST routes are visible.
+ assertEquals(2, lp.getRoutes().size());
+ assertEquals(2, lp.getAllRoutes().size());
+ }
+
+ @Test
+ public void testExcludedRoutesChangeDisabled() {
+ final LinkProperties lp = makeLinkPropertiesWithExcludedRoutes();
+
+ // Excluded routes change is disabled: non-RTN_UNICAST routes are filtered out.
+ assertEquals(0, lp.getRoutes().size());
+ assertEquals(0, lp.getAllRoutes().size());
+ }
+
+ private LinkProperties makeLinkPropertiesWithExcludedRoutes() {
+ final LinkProperties lp = new LinkProperties();
+
+ lp.addRoute(new RouteInfo(new IpPrefix("10.0.0.0/8"), null, null, RouteInfo.RTN_THROW));
+ lp.addRoute(new RouteInfo(new IpPrefix("2001:db8::/64"), null, null,
+ RouteInfo.RTN_UNREACHABLE));
+
+ return lp;
+ }
+}
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideConnOnActivityStartTest.java b/tests/cts/hostside/src/com/android/cts/net/HostsideConnOnActivityStartTest.java
new file mode 100644
index 0000000..3387fd7
--- /dev/null
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideConnOnActivityStartTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.net;
+
+public class HostsideConnOnActivityStartTest extends HostsideNetworkTestCase {
+ private static final String TEST_CLASS = TEST_PKG + ".ConnOnActivityStartTest";
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ uninstallPackage(TEST_APP2_PKG, false);
+ installPackage(TEST_APP2_APK);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+
+ uninstallPackage(TEST_APP2_PKG, true);
+ }
+
+ public void testStartActivity_batterySaver() throws Exception {
+ runDeviceTests(TEST_PKG, TEST_CLASS, "testStartActivity_batterySaver");
+ }
+
+ public void testStartActivity_dataSaver() throws Exception {
+ runDeviceTests(TEST_PKG, TEST_CLASS, "testStartActivity_dataSaver");
+ }
+
+ public void testStartActivity_doze() throws Exception {
+ runDeviceTests(TEST_PKG, TEST_CLASS, "testStartActivity_doze");
+ }
+
+ public void testStartActivity_appStandby() throws Exception {
+ runDeviceTests(TEST_PKG, TEST_CLASS, "testStartActivity_appStandby");
+ }
+}
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideLinkPropertiesGatingTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideLinkPropertiesGatingTests.java
new file mode 100644
index 0000000..b65fb6b
--- /dev/null
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideLinkPropertiesGatingTests.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.net;
+
+import android.compat.cts.CompatChangeGatingTestCase;
+
+import java.util.Set;
+
+/**
+ * Tests for the {@link android.net.LinkProperties#EXCLUDED_ROUTES} compatibility change.
+ */
+public class HostsideLinkPropertiesGatingTests extends CompatChangeGatingTestCase {
+ private static final String TEST_APK = "CtsHostsideNetworkTestsApp3.apk";
+ private static final String TEST_APK_PRE_T = "CtsHostsideNetworkTestsApp3PreT.apk";
+ private static final String TEST_PKG = "com.android.cts.net.hostside.app3";
+ private static final String TEST_CLASS = ".ExcludedRoutesGatingTest";
+
+ private static final long EXCLUDED_ROUTES_CHANGE_ID = 186082280;
+
+ protected void tearDown() throws Exception {
+ uninstallPackage(TEST_PKG, true);
+ }
+
+ public void testExcludedRoutesChangeEnabled() throws Exception {
+ installPackage(TEST_APK, true);
+ runDeviceCompatTest("testExcludedRoutesChangeEnabled");
+ }
+
+ public void testExcludedRoutesChangeDisabledPreT() throws Exception {
+ installPackage(TEST_APK_PRE_T, true);
+ runDeviceCompatTest("testExcludedRoutesChangeDisabled");
+ }
+
+ public void testExcludedRoutesChangeDisabledByOverride() throws Exception {
+ installPackage(TEST_APK, true);
+ runDeviceCompatTestWithChangeDisabled("testExcludedRoutesChangeDisabled");
+ }
+
+ public void testExcludedRoutesChangeEnabledByOverridePreT() throws Exception {
+ installPackage(TEST_APK_PRE_T, true);
+ runDeviceCompatTestWithChangeEnabled("testExcludedRoutesChangeEnabled");
+ }
+
+ private void runDeviceCompatTest(String methodName) throws Exception {
+ runDeviceCompatTest(TEST_PKG, TEST_CLASS, methodName, Set.of(), Set.of());
+ }
+
+ private void runDeviceCompatTestWithChangeEnabled(String methodName) throws Exception {
+ runDeviceCompatTest(TEST_PKG, TEST_CLASS, methodName, Set.of(EXCLUDED_ROUTES_CHANGE_ID),
+ Set.of());
+ }
+
+ private void runDeviceCompatTestWithChangeDisabled(String methodName) throws Exception {
+ runDeviceCompatTest(TEST_PKG, TEST_CLASS, methodName, Set.of(),
+ Set.of(EXCLUDED_ROUTES_CHANGE_ID));
+ }
+}
diff --git a/tests/cts/net/Android.bp b/tests/cts/net/Android.bp
index e979a3b..a6ed762 100644
--- a/tests/cts/net/Android.bp
+++ b/tests/cts/net/Android.bp
@@ -62,6 +62,7 @@
// sdk_version: "current",
platform_apis: true,
required: ["ConnectivityChecker"],
+ test_config_template: "AndroidTestTemplate.xml",
}
// Networking CTS tests for development and release. These tests always target the platform SDK
@@ -79,7 +80,16 @@
"cts",
"general-tests",
],
- test_config_template: "AndroidTestTemplate.xml",
+}
+
+java_defaults {
+ name: "CtsNetTestCasesApiStableDefaults",
+ // TODO: CTS should not depend on the entirety of the networkstack code.
+ static_libs: [
+ "NetworkStackApiStableLib",
+ ],
+ jni_uses_sdk_apis: true,
+ min_sdk_version: "29",
}
// Networking CTS tests that target the latest released SDK. These tests can be installed on release
@@ -87,14 +97,11 @@
// on release devices.
android_test {
name: "CtsNetTestCasesLatestSdk",
- defaults: ["CtsNetTestCasesDefaults"],
- // TODO: CTS should not depend on the entirety of the networkstack code.
- static_libs: [
- "NetworkStackApiStableLib",
+ defaults: [
+ "CtsNetTestCasesDefaults",
+ "CtsNetTestCasesApiStableDefaults",
],
- jni_uses_sdk_apis: true,
- min_sdk_version: "29",
- target_sdk_version: "30",
+ target_sdk_version: "33",
test_suites: [
"general-tests",
"mts-dnsresolver",
@@ -102,5 +109,21 @@
"mts-tethering",
"mts-wifi",
],
- test_config_template: "AndroidTestTemplate.xml",
}
+
+android_test {
+ name: "CtsNetTestCasesMaxTargetSdk31", // Must match CtsNetTestCasesMaxTargetSdk31 annotation.
+ defaults: [
+ "CtsNetTestCasesDefaults",
+ "CtsNetTestCasesApiStableDefaults",
+ ],
+ target_sdk_version: "31",
+ package_name: "android.net.cts.maxtargetsdk31", // CTS package names must be unique.
+ instrumentation_target_package: "android.net.cts.maxtargetsdk31",
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-networking",
+ ],
+}
+
diff --git a/tests/cts/net/AndroidManifest.xml b/tests/cts/net/AndroidManifest.xml
index 3b47100..6b5bb93 100644
--- a/tests/cts/net/AndroidManifest.xml
+++ b/tests/cts/net/AndroidManifest.xml
@@ -44,7 +44,8 @@
android.permission.MANAGE_TEST_NETWORKS
-->
- <application android:usesCleartextTraffic="true">
+ <application android:debuggable="true"
+ android:usesCleartextTraffic="true">
<uses-library android:name="android.test.runner" />
<uses-library android:name="org.apache.http.legacy" android:required="false" />
</application>
diff --git a/tests/cts/net/AndroidTestTemplate.xml b/tests/cts/net/AndroidTestTemplate.xml
index 48a1c79..d2fb04a 100644
--- a/tests/cts/net/AndroidTestTemplate.xml
+++ b/tests/cts/net/AndroidTestTemplate.xml
@@ -33,9 +33,36 @@
<target_preparer class="com.android.testutils.DisableConfigSyncTargetPreparer">
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
- <option name="package" value="android.net.cts" />
+ <option name="package" value="{PACKAGE}" />
<option name="runtime-hint" value="9m4s" />
<option name="hidden-api-checks" value="false" />
<option name="isolated-storage" value="false" />
+ <!-- Test filter that allows test APKs to select which tests they want to run by annotating
+ those tests with an annotation matching the name of the APK.
+
+ This allows us to maintain one AndroidTestTemplate.xml for all CtsNetTestCases*.apk,
+ and have CtsNetTestCases and CtsNetTestCasesLatestSdk run all tests, but have
+ CtsNetTestCasesMaxTargetSdk31 run only tests that require target SDK 31.
+
+ This relies on the fact that if the class specified in include-annotation exists, then
+ the runner will only run the tests annotated with that annotation, but if it does not,
+ the runner will run all the tests. -->
+ <option name="include-annotation" value="com.android.testutils.filters.{MODULE}" />
</test>
+ <!-- When this test is run in a Mainline context (e.g. with `mts-tradefed`), only enable it if
+ one of the Mainline modules below is present on the device used for testing. -->
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+ <!-- Tethering Module (internal version). -->
+ <option name="mainline-module-package-name" value="com.google.android.tethering" />
+ <!-- Tethering Module (AOSP version). -->
+ <option name="mainline-module-package-name" value="com.android.tethering" />
+ <!-- NetworkStack Module (internal version). Should always be installed with CaptivePortalLogin. -->
+ <option name="mainline-module-package-name" value="com.google.android.networkstack" />
+ <!-- NetworkStack Module (AOSP version). Should always be installed with CaptivePortalLogin. -->
+ <option name="mainline-module-package-name" value="com.android.networkstack" />
+ <!-- Resolver Module (internal version). -->
+ <option name="mainline-module-package-name" value="com.google.android.resolv" />
+ <!-- Resolver Module (AOSP version). -->
+ <option name="mainline-module-package-name" value="com.android.resolv" />
+ </object>
</configuration>
diff --git a/tests/cts/net/OWNERS b/tests/cts/net/OWNERS
deleted file mode 100644
index df5569e..0000000
--- a/tests/cts/net/OWNERS
+++ /dev/null
@@ -1,5 +0,0 @@
-# Bug component: 31808
-# Inherits parent owners
-per-file src/android/net/cts/NetworkWatchlistTest.java=alanstokes@google.com
-
-# Bug component: 685852 = per-file *IpSec*
\ No newline at end of file
diff --git a/tests/cts/net/api23Test/Android.bp b/tests/cts/net/api23Test/Android.bp
index 5b37294..9b81a56 100644
--- a/tests/cts/net/api23Test/Android.bp
+++ b/tests/cts/net/api23Test/Android.bp
@@ -51,5 +51,8 @@
"cts",
"general-tests",
],
-
+ data: [
+ ":CtsNetTestAppForApi23",
+ ],
+ per_testcase_directory: true,
}
diff --git a/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt b/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt
index a378aa7..0344604 100644
--- a/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt
+++ b/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt
@@ -77,7 +77,7 @@
// Re-connecting to the AP, obtaining an IP address, revalidating can take a long time
private const val WIFI_CONNECT_TIMEOUT_MS = 40_000L
-private const val TEST_TIMEOUT_MS = 10_000L
+private const val TEST_TIMEOUT_MS = 20_000L
private const val TAG = "CaptivePortalTest"
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index d40bc9f..d97ebcb 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -37,6 +37,10 @@
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.net.ConnectivityManager.EXTRA_NETWORK;
import static android.net.ConnectivityManager.EXTRA_NETWORK_REQUEST;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_1;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_2;
+import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
+import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE;
import static android.net.ConnectivityManager.TYPE_BLUETOOTH;
import static android.net.ConnectivityManager.TYPE_ETHERNET;
@@ -52,6 +56,7 @@
import static android.net.ConnectivityManager.TYPE_PROXY;
import static android.net.ConnectivityManager.TYPE_VPN;
import static android.net.ConnectivityManager.TYPE_WIFI_P2P;
+import static android.net.ConnectivitySettingsManager.setUidsAllowedOnRestrictedNetworks;
import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
@@ -151,6 +156,7 @@
import android.os.Looper;
import android.os.MessageQueue;
import android.os.Process;
+import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
@@ -163,7 +169,6 @@
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
-import android.util.Pair;
import android.util.Range;
import androidx.test.InstrumentationRegistry;
@@ -181,6 +186,7 @@
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DevSdkIgnoreRuleKt;
+import com.android.testutils.DeviceInfoUtils;
import com.android.testutils.DumpTestUtils;
import com.android.testutils.RecorderCallback.CallbackEntry;
import com.android.testutils.TestHttpServer;
@@ -202,6 +208,8 @@
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
import java.net.HttpURLConnection;
import java.net.Inet4Address;
import java.net.Inet6Address;
@@ -216,6 +224,7 @@
import java.util.Collection;
import java.util.List;
import java.util.Objects;
+import java.util.Random;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
@@ -253,6 +262,7 @@
private static final int NETWORK_CALLBACK_TIMEOUT_MS = 30_000;
private static final int LISTEN_ACTIVITY_TIMEOUT_MS = 5_000;
private static final int NO_CALLBACK_TIMEOUT_MS = 100;
+ private static final int SOCKET_TIMEOUT_MS = 100;
private static final int NUM_TRIES_MULTIPATH_PREF_CHECK = 20;
private static final long INTERVAL_MULTIPATH_PREF_CHECK_MS = 500;
// device could have only one interface: data, wifi.
@@ -590,6 +600,7 @@
}
@DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
+ @AppModeFull(reason = "Cannot get installed packages in instant app mode")
@Test
public void testGetRedactedLinkPropertiesForPackage() throws Exception {
final String groundedPkg = findPackageByPermissions(
@@ -660,10 +671,12 @@
// CaptivePortalApiUrl & CaptivePortalData will be preserved if the given uid holds the
// NETWORK_SETTINGS permission.
- assertEquals(capportUrl,
+ assertNotNull(lp.getCaptivePortalApiUrl());
+ assertNotNull(lp.getCaptivePortalData());
+ assertEquals(lp.getCaptivePortalApiUrl(),
mCm.getRedactedLinkPropertiesForPackage(lp, privilegedUid, privilegedPkg)
.getCaptivePortalApiUrl());
- assertEquals(capportData,
+ assertEquals(lp.getCaptivePortalData(),
mCm.getRedactedLinkPropertiesForPackage(lp, privilegedUid, privilegedPkg)
.getCaptivePortalData());
});
@@ -675,6 +688,7 @@
}
@DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
+ @AppModeFull(reason = "Cannot get installed packages in instant app mode")
@Test
public void testGetRedactedNetworkCapabilitiesForPackage() throws Exception {
final String groundedPkg = findPackageByPermissions(
@@ -885,9 +899,21 @@
//
// Note that this test this will still fail in instant mode if a device supports Ethernet
// via other hardware means. We are not currently aware of any such device.
- return (mContext.getSystemService(Context.ETHERNET_SERVICE) != null) ||
- mPackageManager.hasSystemFeature(FEATURE_ETHERNET) ||
- mPackageManager.hasSystemFeature(FEATURE_USB_HOST);
+ return hasEthernetService()
+ || mPackageManager.hasSystemFeature(FEATURE_ETHERNET)
+ || mPackageManager.hasSystemFeature(FEATURE_USB_HOST);
+ }
+
+ private boolean hasEthernetService() {
+ // On Q creating EthernetManager from a thread that does not have a looper (like the test
+ // thread) crashes because it tried to use Looper.myLooper() through the default Handler
+ // constructor to run onAvailabilityChanged callbacks. Use ServiceManager to check whether
+ // the service exists instead.
+ // TODO: remove once Q is no longer supported in MTS, as ServiceManager is hidden API
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
+ return ServiceManager.getService(Context.ETHERNET_SERVICE) != null;
+ }
+ return mContext.getSystemService(Context.ETHERNET_SERVICE) != null;
}
private boolean shouldBeSupported(int networkType) {
@@ -1588,51 +1614,7 @@
private static boolean isTcpKeepaliveSupportedByKernel() {
final String kVersionString = VintfRuntimeInfo.getKernelVersion();
- return compareMajorMinorVersion(kVersionString, "4.8") >= 0;
- }
-
- private static Pair<Integer, Integer> getVersionFromString(String version) {
- // Only gets major and minor number of the version string.
- final Pattern versionPattern = Pattern.compile("^(\\d+)(\\.(\\d+))?.*");
- final Matcher m = versionPattern.matcher(version);
- if (m.matches()) {
- final int major = Integer.parseInt(m.group(1));
- final int minor = TextUtils.isEmpty(m.group(3)) ? 0 : Integer.parseInt(m.group(3));
- return new Pair<>(major, minor);
- } else {
- return new Pair<>(0, 0);
- }
- }
-
- // TODO: Move to util class.
- private static int compareMajorMinorVersion(final String s1, final String s2) {
- final Pair<Integer, Integer> v1 = getVersionFromString(s1);
- final Pair<Integer, Integer> v2 = getVersionFromString(s2);
-
- if (v1.first == v2.first) {
- return Integer.compare(v1.second, v2.second);
- } else {
- return Integer.compare(v1.first, v2.first);
- }
- }
-
- /**
- * Verifies that version string compare logic returns expected result for various cases.
- * Note that only major and minor number are compared.
- */
- @Test
- public void testMajorMinorVersionCompare() {
- assertEquals(0, compareMajorMinorVersion("4.8.1", "4.8"));
- assertEquals(1, compareMajorMinorVersion("4.9", "4.8.1"));
- assertEquals(1, compareMajorMinorVersion("5.0", "4.8"));
- assertEquals(1, compareMajorMinorVersion("5", "4.8"));
- assertEquals(0, compareMajorMinorVersion("5", "5.0"));
- assertEquals(1, compareMajorMinorVersion("5-beta1", "4.8"));
- assertEquals(0, compareMajorMinorVersion("4.8.0.0", "4.8"));
- assertEquals(0, compareMajorMinorVersion("4.8-RC1", "4.8"));
- assertEquals(0, compareMajorMinorVersion("4.8", "4.8"));
- assertEquals(-1, compareMajorMinorVersion("3.10", "4.8.0"));
- assertEquals(-1, compareMajorMinorVersion("4.7.10.10", "4.8"));
+ return DeviceInfoUtils.compareMajorMinorVersion(kVersionString, "4.8") >= 0;
}
/**
@@ -3195,7 +3177,7 @@
@AppModeFull(reason = "WRITE_SECURE_SETTINGS permission can't be granted to instant apps")
@Test
public void testUidsAllowedOnRestrictedNetworks() throws Exception {
- assumeTrue(TestUtils.shouldTestSApis());
+ assumeTestSApis();
// TODO (b/175199465): figure out a reasonable permission check for
// setUidsAllowedOnRestrictedNetworks that allows tests but not system-external callers.
@@ -3208,10 +3190,10 @@
// because it has been just installed to device. In case the uid is existed in setting
// mistakenly, try to remove the uid and set correct uids to setting.
originalUidsAllowedOnRestrictedNetworks.remove(uid);
- runWithShellPermissionIdentity(() ->
- ConnectivitySettingsManager.setUidsAllowedOnRestrictedNetworks(
- mContext, originalUidsAllowedOnRestrictedNetworks), NETWORK_SETTINGS);
+ runWithShellPermissionIdentity(() -> setUidsAllowedOnRestrictedNetworks(
+ mContext, originalUidsAllowedOnRestrictedNetworks), NETWORK_SETTINGS);
+ // File a restricted network request with permission first to hold the connection.
final TestableNetworkCallback testNetworkCb = new TestableNetworkCallback();
final NetworkRequest testRequest = new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_TEST)
@@ -3223,6 +3205,19 @@
runWithShellPermissionIdentity(() -> requestNetwork(testRequest, testNetworkCb),
CONNECTIVITY_USE_RESTRICTED_NETWORKS);
+ // File another restricted network request without permission.
+ final TestableNetworkCallback restrictedNetworkCb = new TestableNetworkCallback();
+ final NetworkRequest restrictedRequest = new NetworkRequest.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_TEST)
+ .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+ .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+ .setNetworkSpecifier(CompatUtil.makeTestNetworkSpecifier(
+ TEST_RESTRICTED_NW_IFACE_NAME))
+ .build();
+ // Uid is not in allowed list and no permissions. Expect that SecurityException will throw.
+ assertThrows(SecurityException.class,
+ () -> mCm.requestNetwork(restrictedRequest, restrictedNetworkCb));
+
final NetworkAgent agent = createRestrictedNetworkAgent(mContext);
final Network network = agent.getNetwork();
@@ -3242,19 +3237,28 @@
final Set<Integer> newUidsAllowedOnRestrictedNetworks =
new ArraySet<>(originalUidsAllowedOnRestrictedNetworks);
newUidsAllowedOnRestrictedNetworks.add(uid);
- runWithShellPermissionIdentity(() ->
- ConnectivitySettingsManager.setUidsAllowedOnRestrictedNetworks(
- mContext, newUidsAllowedOnRestrictedNetworks), NETWORK_SETTINGS);
+ runWithShellPermissionIdentity(() -> setUidsAllowedOnRestrictedNetworks(
+ mContext, newUidsAllowedOnRestrictedNetworks), NETWORK_SETTINGS);
// Wait a while for sending allowed uids on the restricted network to netd.
- // TODD: Have a significant signal to know the uids has been send to netd.
+ // TODD: Have a significant signal to know the uids has been sent to netd.
assertBindSocketToNetworkSuccess(network);
+
+ if (TestUtils.shouldTestTApis()) {
+ // Uid is in allowed list. Try file network request again.
+ requestNetwork(restrictedRequest, restrictedNetworkCb);
+ // Verify that the network is restricted.
+ restrictedNetworkCb.eventuallyExpect(CallbackEntry.NETWORK_CAPS_UPDATED,
+ NETWORK_CALLBACK_TIMEOUT_MS,
+ entry -> network.equals(entry.getNetwork())
+ && (!((CallbackEntry.CapabilitiesChanged) entry).getCaps()
+ .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)));
+ }
} finally {
agent.unregister();
// Restore setting.
- runWithShellPermissionIdentity(() ->
- ConnectivitySettingsManager.setUidsAllowedOnRestrictedNetworks(
- mContext, originalUidsAllowedOnRestrictedNetworks), NETWORK_SETTINGS);
+ runWithShellPermissionIdentity(() -> setUidsAllowedOnRestrictedNetworks(
+ mContext, originalUidsAllowedOnRestrictedNetworks), NETWORK_SETTINGS);
}
}
@@ -3278,6 +3282,117 @@
assertTrue(dumpOutput, dumpOutput.contains("BPF map content"));
}
+ private void checkFirewallBlocking(final DatagramSocket srcSock, final DatagramSocket dstSock,
+ final boolean expectBlock) throws Exception {
+ final Random random = new Random();
+ final byte[] sendData = new byte[100];
+ random.nextBytes(sendData);
+
+ final DatagramPacket pkt = new DatagramPacket(sendData, sendData.length,
+ InetAddresses.parseNumericAddress("::1"), dstSock.getLocalPort());
+ try {
+ srcSock.send(pkt);
+ } catch (IOException e) {
+ if (expectBlock) {
+ return;
+ }
+ fail("Expect not to be blocked by firewall but sending packet was blocked");
+ }
+
+ if (expectBlock) {
+ fail("Expect to be blocked by firewall but sending packet was not blocked");
+ }
+
+ dstSock.receive(pkt);
+ assertArrayEquals(sendData, pkt.getData());
+ }
+
+ private static final boolean EXPECT_PASS = false;
+ private static final boolean EXPECT_BLOCK = true;
+
+ private void doTestFirewallBlockingDenyRule(final int chain) {
+ runWithShellPermissionIdentity(() -> {
+ try (DatagramSocket srcSock = new DatagramSocket();
+ DatagramSocket dstSock = new DatagramSocket()) {
+ dstSock.setSoTimeout(SOCKET_TIMEOUT_MS);
+
+ // No global config, No uid config
+ checkFirewallBlocking(srcSock, dstSock, EXPECT_PASS);
+
+ // Has global config, No uid config
+ mCm.setFirewallChainEnabled(chain, true /* enable */);
+ checkFirewallBlocking(srcSock, dstSock, EXPECT_PASS);
+
+ // Has global config, Has uid config
+ mCm.setUidFirewallRule(chain, Process.myUid(), FIREWALL_RULE_DENY);
+ checkFirewallBlocking(srcSock, dstSock, EXPECT_BLOCK);
+
+ // No global config, Has uid config
+ mCm.setFirewallChainEnabled(chain, false /* enable */);
+ checkFirewallBlocking(srcSock, dstSock, EXPECT_PASS);
+
+ // No global config, No uid config
+ mCm.setUidFirewallRule(chain, Process.myUid(), FIREWALL_RULE_ALLOW);
+ checkFirewallBlocking(srcSock, dstSock, EXPECT_PASS);
+ } finally {
+ mCm.setFirewallChainEnabled(chain, false /* enable */);
+ mCm.setUidFirewallRule(chain, Process.myUid(), FIREWALL_RULE_ALLOW);
+ }
+ }, NETWORK_SETTINGS);
+ }
+
+ private void doTestFirewallBlockingAllowRule(final int chain) {
+ runWithShellPermissionIdentity(() -> {
+ try (DatagramSocket srcSock = new DatagramSocket();
+ DatagramSocket dstSock = new DatagramSocket()) {
+ dstSock.setSoTimeout(SOCKET_TIMEOUT_MS);
+
+ // No global config, No uid config
+ checkFirewallBlocking(srcSock, dstSock, EXPECT_PASS);
+
+ // Has global config, No uid config
+ mCm.setFirewallChainEnabled(chain, true /* enable */);
+ checkFirewallBlocking(srcSock, dstSock, EXPECT_BLOCK);
+
+ // Has global config, Has uid config
+ mCm.setUidFirewallRule(chain, Process.myUid(), FIREWALL_RULE_ALLOW);
+ checkFirewallBlocking(srcSock, dstSock, EXPECT_PASS);
+
+ // No global config, Has uid config
+ mCm.setFirewallChainEnabled(chain, false /* enable */);
+ checkFirewallBlocking(srcSock, dstSock, EXPECT_PASS);
+
+ // No global config, No uid config
+ mCm.setUidFirewallRule(chain, Process.myUid(), FIREWALL_RULE_DENY);
+ checkFirewallBlocking(srcSock, dstSock, EXPECT_PASS);
+ } finally {
+ mCm.setFirewallChainEnabled(chain, false /* enable */);
+ mCm.setUidFirewallRule(chain, Process.myUid(), FIREWALL_RULE_DENY);
+ }
+ }, NETWORK_SETTINGS);
+ }
+
+ @Test @IgnoreUpTo(SC_V2)
+ public void testFirewallBlocking() {
+ // Following tests affect the actual state of networking on the device after the test.
+ // This might cause unexpected behaviour of the device. So, we skip them for now.
+ // We will enable following tests after adding the logic of firewall state restoring.
+ // doTestFirewallBlockingAllowRule(FIREWALL_CHAIN_DOZABLE);
+ // doTestFirewallBlockingAllowRule(FIREWALL_CHAIN_POWERSAVE);
+ // doTestFirewallBlockingAllowRule(FIREWALL_CHAIN_RESTRICTED);
+ // doTestFirewallBlockingAllowRule(FIREWALL_CHAIN_LOW_POWER_STANDBY);
+
+ // doTestFirewallBlockingDenyRule(FIREWALL_CHAIN_STANDBY);
+ doTestFirewallBlockingDenyRule(FIREWALL_CHAIN_OEM_DENY_1);
+ doTestFirewallBlockingDenyRule(FIREWALL_CHAIN_OEM_DENY_2);
+ }
+
+ private void assumeTestSApis() {
+ // Cannot use @IgnoreUpTo(Build.VERSION_CODES.R) because this test also requires API 31
+ // shims, and @IgnoreUpTo does not check that.
+ assumeTrue(TestUtils.shouldTestSApis());
+ }
+
private void unregisterRegisteredCallbacks() {
for (NetworkCallback callback: mRegisteredCallbacks) {
mCm.unregisterNetworkCallback(callback);
diff --git a/tests/cts/net/src/android/net/cts/DnsResolverTest.java b/tests/cts/net/src/android/net/cts/DnsResolverTest.java
index c6fc38f..0c53411 100644
--- a/tests/cts/net/src/android/net/cts/DnsResolverTest.java
+++ b/tests/cts/net/src/android/net/cts/DnsResolverTest.java
@@ -49,6 +49,7 @@
import android.os.Handler;
import android.os.Looper;
import android.platform.test.annotations.AppModeFull;
+import android.provider.Settings;
import android.system.ErrnoException;
import android.util.Log;
@@ -727,6 +728,18 @@
@Test
public void testPrivateDnsBypass() throws InterruptedException {
+ final String dataStallSetting = Settings.Global.getString(mCR,
+ Settings.Global.DATA_STALL_RECOVERY_ON_BAD_NETWORK);
+ Settings.Global.putInt(mCR, Settings.Global.DATA_STALL_RECOVERY_ON_BAD_NETWORK, 0);
+ try {
+ doTestPrivateDnsBypass();
+ } finally {
+ Settings.Global.putString(mCR, Settings.Global.DATA_STALL_RECOVERY_ON_BAD_NETWORK,
+ dataStallSetting);
+ }
+ }
+
+ private void doTestPrivateDnsBypass() throws InterruptedException {
final Network[] testNetworks = getTestableNetworks();
// Set an invalid private DNS server
diff --git a/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt b/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
index ea98289..bbac09b 100644
--- a/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
+++ b/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
@@ -22,15 +22,14 @@
import android.Manifest.permission.MANAGE_TEST_NETWORKS
import android.content.Context
import android.net.ConnectivityManager
-import android.net.cts.util.CtsNetUtils
import android.net.DscpPolicy
-import android.net.DscpPolicy.STATUS_DELETED
-import android.net.DscpPolicy.STATUS_SUCCESS
import android.net.InetAddresses
import android.net.IpPrefix
import android.net.LinkAddress
import android.net.LinkProperties
import android.net.NetworkAgent
+import android.net.NetworkAgent.DSCP_POLICY_STATUS_DELETED
+import android.net.NetworkAgent.DSCP_POLICY_STATUS_SUCCESS
import android.net.NetworkAgentConfig
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
@@ -45,15 +44,11 @@
import android.net.TestNetworkInterface
import android.net.TestNetworkManager
import android.net.RouteInfo
-import android.net.util.SocketUtils
-import android.os.Build
import android.os.HandlerThread
-import android.os.Looper
import android.platform.test.annotations.AppModeFull
import android.system.Os
-import android.system.OsConstants
import android.system.OsConstants.AF_INET
-import android.system.OsConstants.IPPROTO_IP
+import android.system.OsConstants.AF_INET6
import android.system.OsConstants.IPPROTO_UDP
import android.system.OsConstants.SOCK_DGRAM
import android.system.OsConstants.SOCK_NONBLOCK
@@ -61,10 +56,8 @@
import android.util.Range
import androidx.test.InstrumentationRegistry
import androidx.test.runner.AndroidJUnit4
-import com.android.modules.utils.build.SdkLevel
import com.android.testutils.CompatUtil
import com.android.testutils.DevSdkIgnoreRule
-import com.android.testutils.OffsetFilter
import com.android.testutils.assertParcelingIsLossless
import com.android.testutils.runAsShell
import com.android.testutils.SC_V2
@@ -74,30 +67,27 @@
import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnDscpPolicyStatusUpdated
import com.android.testutils.TestableNetworkCallback
import org.junit.After
-import org.junit.AfterClass
import org.junit.Assume.assumeTrue
import org.junit.Before
-import org.junit.BeforeClass
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import java.net.Inet4Address
import java.net.Inet6Address
import java.net.InetAddress
-import java.net.InetSocketAddress
-import java.net.ServerSocket
import java.nio.ByteBuffer
import java.nio.ByteOrder
-import java.util.UUID
import java.util.regex.Pattern
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
-import kotlin.concurrent.thread
import kotlin.test.fail
private const val MAX_PACKET_LENGTH = 1500
+private const val IP4_PREFIX_LEN = 32
+private const val IP6_PREFIX_LEN = 128
+
private val instrumentation: Instrumentation
get() = InstrumentationRegistry.getInstrumentation()
@@ -114,6 +104,9 @@
private val LOCAL_IPV4_ADDRESS = InetAddresses.parseNumericAddress("192.0.2.1")
private val TEST_TARGET_IPV4_ADDR =
InetAddresses.parseNumericAddress("8.8.8.8") as Inet4Address
+ private val LOCAL_IPV6_ADDRESS = InetAddresses.parseNumericAddress("2001:db8::1")
+ private val TEST_TARGET_IPV6_ADDR =
+ InetAddresses.parseNumericAddress("2001:4860:4860::8888") as Inet6Address
private val realContext = InstrumentationRegistry.getContext()
private val cm = realContext.getSystemService(ConnectivityManager::class.java)
@@ -149,7 +142,9 @@
runAsShell(MANAGE_TEST_NETWORKS) {
val tnm = realContext.getSystemService(TestNetworkManager::class.java)
- iface = tnm.createTunInterface( Array(1){ LinkAddress(LOCAL_IPV4_ADDRESS, 32) } )
+ iface = tnm.createTunInterface(arrayOf(
+ LinkAddress(LOCAL_IPV4_ADDRESS, IP4_PREFIX_LEN),
+ LinkAddress(LOCAL_IPV6_ADDRESS, IP6_PREFIX_LEN)))
assertNotNull(iface)
}
@@ -163,11 +158,16 @@
@After
fun tearDown() {
+ if (!kernelIsAtLeast(5, 4)) {
+ return;
+ }
agentsToCleanUp.forEach { it.unregister() }
callbacksToCleanUp.forEach { cm.unregisterNetworkCallback(it) }
// reader.stop() cleans up tun fd
reader.handler.post { reader.stop() }
+ if (iface.fileDescriptor.fileDescriptor != null)
+ Os.close(iface.fileDescriptor.fileDescriptor)
handlerThread.quitSafely()
}
@@ -191,7 +191,7 @@
private fun createConnectedNetworkAgent(
context: Context = realContext,
- specifier: String? = iface.getInterfaceName(),
+ specifier: String? = iface.getInterfaceName()
): Pair<TestableNetworkAgent, TestableNetworkCallback> {
val callback = TestableNetworkCallback()
// Ensure this NetworkAgent is never unneeded by filing a request with its specifier.
@@ -210,9 +210,11 @@
}
}
val lp = LinkProperties().apply {
- addLinkAddress(LinkAddress(LOCAL_IPV4_ADDRESS, 32))
+ addLinkAddress(LinkAddress(LOCAL_IPV4_ADDRESS, IP4_PREFIX_LEN))
+ addLinkAddress(LinkAddress(LOCAL_IPV6_ADDRESS, IP6_PREFIX_LEN))
addRoute(RouteInfo(IpPrefix("0.0.0.0/0"), null, null))
- setInterfaceName(iface.getInterfaceName())
+ addRoute(RouteInfo(InetAddress.getByName("fe80::1234")))
+ setInterfaceName(specifier)
}
val config = NetworkAgentConfig.Builder().build()
val agent = TestableNetworkAgent(context, handlerThread.looper, nc, lp, config)
@@ -232,47 +234,114 @@
eachByte -> "%02x".format(eachByte)
}
- fun checkDscpValue(
- agent : TestableNetworkAgent,
- callback : TestableNetworkCallback,
- dscpValue : Int = 0,
- dstPort : Int = 0,
+ fun sendPacket(
+ agent: TestableNetworkAgent,
+ sendV6: Boolean,
+ dstPort: Int = 0,
) {
val testString = "test string"
val testPacket = ByteBuffer.wrap(testString.toByteArray(Charsets.UTF_8))
var packetFound = false
- val socket = Os.socket(AF_INET, SOCK_DGRAM or SOCK_NONBLOCK, IPPROTO_UDP)
+ val socket = Os.socket(if (sendV6) AF_INET6 else AF_INET, SOCK_DGRAM or SOCK_NONBLOCK,
+ IPPROTO_UDP)
agent.network.bindSocket(socket)
val originalPacket = testPacket.readAsArray()
- Os.sendto(socket, originalPacket, 0 /* bytesOffset */, originalPacket.size,
- 0 /* flags */, TEST_TARGET_IPV4_ADDR, dstPort)
-
+ Os.sendto(socket, originalPacket, 0 /* bytesOffset */, originalPacket.size, 0 /* flags */,
+ if(sendV6) TEST_TARGET_IPV6_ADDR else TEST_TARGET_IPV4_ADDR, dstPort)
Os.close(socket)
+ }
+
+ fun parseV4PacketDscp(buffer : ByteBuffer) : Int {
+ val ip_ver = buffer.get()
+ val tos = buffer.get()
+ val length = buffer.getShort()
+ val id = buffer.getShort()
+ val offset = buffer.getShort()
+ val ttl = buffer.get()
+ val ipType = buffer.get()
+ val checksum = buffer.getShort()
+ return tos.toInt().shr(2)
+ }
+
+ fun parseV6PacketDscp(buffer : ByteBuffer) : Int {
+ val ip_ver = buffer.get()
+ val tc = buffer.get()
+ val fl = buffer.getShort()
+ val length = buffer.getShort()
+ val proto = buffer.get()
+ val hop = buffer.get()
+ // DSCP is bottom 4 bits of ip_ver and top 2 of tc.
+ val ip_ver_bottom = ip_ver.toInt().and(0xf)
+ val tc_dscp = tc.toInt().shr(6)
+ return ip_ver_bottom.toInt().shl(2) + tc_dscp
+ }
+
+ fun parsePacketIp(
+ buffer : ByteBuffer,
+ sendV6 : Boolean,
+ ) : Boolean {
+ val ipAddr = if (sendV6) ByteArray(16) else ByteArray(4)
+ buffer.get(ipAddr)
+ val srcIp = if (sendV6) Inet6Address.getByAddress(ipAddr)
+ else Inet4Address.getByAddress(ipAddr)
+ buffer.get(ipAddr)
+ val dstIp = if (sendV6) Inet6Address.getByAddress(ipAddr)
+ else Inet4Address.getByAddress(ipAddr)
+
+ Log.e(TAG, "IP Src:" + srcIp + " dst: " + dstIp)
+
+ if ((sendV6 && srcIp == LOCAL_IPV6_ADDRESS && dstIp == TEST_TARGET_IPV6_ADDR) ||
+ (!sendV6 && srcIp == LOCAL_IPV4_ADDRESS && dstIp == TEST_TARGET_IPV4_ADDR)) {
+ Log.e(TAG, "IP return true");
+ return true
+ }
+ Log.e(TAG, "IP return false");
+ return false
+ }
+
+ fun parsePacketPort(
+ buffer : ByteBuffer,
+ srcPort : Int,
+ dstPort : Int
+ ) : Boolean {
+ if (srcPort == 0 && dstPort == 0) return true
+
+ val packetSrcPort = buffer.getShort().toInt()
+ val packetDstPort = buffer.getShort().toInt()
+
+ Log.e(TAG, "Port Src:" + packetSrcPort + " dst: " + packetDstPort)
+
+ if ((srcPort == 0 || (srcPort != 0 && srcPort == packetSrcPort)) &&
+ (dstPort == 0 || (dstPort != 0 && dstPort == packetDstPort))) {
+ Log.e(TAG, "Port return true");
+ return true
+ }
+ Log.e(TAG, "Port return false");
+ return false
+ }
+
+ fun validatePacket(
+ agent : TestableNetworkAgent,
+ sendV6 : Boolean = false,
+ dscpValue : Int = 0,
+ dstPort : Int = 0,
+ ) {
+ var packetFound = false;
+ sendPacket(agent, sendV6, dstPort)
+ // TODO: grab source port from socket in sendPacket
+
+ Log.e(TAG, "find DSCP value:" + dscpValue)
generateSequence { reader.poll(PACKET_TIMEOUT_MS) }.forEach { packet ->
val buffer = ByteBuffer.wrap(packet, 0, packet.size).order(ByteOrder.BIG_ENDIAN)
- val ip_ver = buffer.get()
- val tos = buffer.get()
- val length = buffer.getShort()
- val id = buffer.getShort()
- val offset = buffer.getShort()
- val ttl = buffer.get()
- val ipType = buffer.get()
- val checksum = buffer.getShort()
+ val dscp = if (sendV6) parseV6PacketDscp(buffer) else parseV4PacketDscp(buffer)
+ Log.e(TAG, "DSCP value:" + dscp)
- val ipAddr = ByteArray(4)
- buffer.get(ipAddr)
- val srcIp = Inet4Address.getByAddress(ipAddr);
- buffer.get(ipAddr)
- val dstIp = Inet4Address.getByAddress(ipAddr);
- val packetSrcPort = buffer.getShort().toInt()
- val packetDstPort = buffer.getShort().toInt()
-
- // TODO: Add source port comparison.
- if (srcIp == LOCAL_IPV4_ADDRESS && dstIp == TEST_TARGET_IPV4_ADDR &&
- packetDstPort == dstPort) {
- assertEquals(dscpValue, (tos.toInt().shr(2)))
+ // TODO: Add source port comparison. Use 0 for now.
+ if (parsePacketIp(buffer, sendV6) && parsePacketPort(buffer, 0, dstPort)) {
+ Log.e(TAG, "DSCP value found")
+ assertEquals(dscpValue, dscp)
packetFound = true
}
}
@@ -280,52 +349,90 @@
}
fun doRemovePolicyTest(
- agent : TestableNetworkAgent,
- callback : TestableNetworkCallback,
- policyId : Int
+ agent: TestableNetworkAgent,
+ callback: TestableNetworkCallback,
+ policyId: Int
) {
val portNumber = 1111 * policyId
agent.sendRemoveDscpPolicy(policyId)
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
assertEquals(policyId, it.policyId)
- assertEquals(STATUS_DELETED, it.status)
- checkDscpValue(agent, callback, dstPort = portNumber)
+ assertEquals(DSCP_POLICY_STATUS_DELETED, it.status)
}
}
@Test
- fun testDscpPolicyAddPolicies(): Unit = createConnectedNetworkAgent().let { (agent, callback) ->
+ fun testDscpPolicyAddPolicies(): Unit = createConnectedNetworkAgent().let {
+ (agent, callback) ->
val policy = DscpPolicy.Builder(1, 1)
.setDestinationPortRange(Range(4444, 4444)).build()
agent.sendAddDscpPolicy(policy)
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
assertEquals(1, it.policyId)
- assertEquals(STATUS_SUCCESS, it.status)
+ assertEquals(DSCP_POLICY_STATUS_SUCCESS, it.status)
}
-
- checkDscpValue(agent, callback, dscpValue = 1, dstPort = 4444)
+ validatePacket(agent, dscpValue = 1, dstPort = 4444)
agent.sendRemoveDscpPolicy(1)
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
assertEquals(1, it.policyId)
- assertEquals(STATUS_DELETED, it.status)
+ assertEquals(DSCP_POLICY_STATUS_DELETED, it.status)
}
val policy2 = DscpPolicy.Builder(1, 4)
- .setDestinationPortRange(Range(5555, 5555)).setSourceAddress(LOCAL_IPV4_ADDRESS)
- .setDestinationAddress(TEST_TARGET_IPV4_ADDR).setProtocol(IPPROTO_UDP).build()
+ .setDestinationPortRange(Range(5555, 5555))
+ .setDestinationAddress(TEST_TARGET_IPV4_ADDR)
+ .setSourceAddress(LOCAL_IPV4_ADDRESS)
+ .setProtocol(IPPROTO_UDP).build()
agent.sendAddDscpPolicy(policy2)
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
assertEquals(1, it.policyId)
- assertEquals(STATUS_SUCCESS, it.status)
+ assertEquals(DSCP_POLICY_STATUS_SUCCESS, it.status)
}
- checkDscpValue(agent, callback, dscpValue = 4, dstPort = 5555)
+ validatePacket(agent, dscpValue = 4, dstPort = 5555)
agent.sendRemoveDscpPolicy(1)
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
assertEquals(1, it.policyId)
- assertEquals(STATUS_DELETED, it.status)
+ assertEquals(DSCP_POLICY_STATUS_DELETED, it.status)
+ }
+ }
+
+ @Test
+ fun testDscpPolicyAddV6Policies(): Unit = createConnectedNetworkAgent().let {
+ (agent, callback) ->
+ val policy = DscpPolicy.Builder(1, 1)
+ .setDestinationPortRange(Range(4444, 4444)).build()
+ agent.sendAddDscpPolicy(policy)
+ agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
+ assertEquals(1, it.policyId)
+ assertEquals(DSCP_POLICY_STATUS_SUCCESS, it.status)
+ }
+ validatePacket(agent, true, dscpValue = 1, dstPort = 4444)
+
+ agent.sendRemoveDscpPolicy(1)
+ agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
+ assertEquals(1, it.policyId)
+ assertEquals(DSCP_POLICY_STATUS_DELETED, it.status)
+ }
+
+ val policy2 = DscpPolicy.Builder(1, 4)
+ .setDestinationPortRange(Range(5555, 5555))
+ .setDestinationAddress(TEST_TARGET_IPV6_ADDR)
+ .setSourceAddress(LOCAL_IPV6_ADDRESS)
+ .setProtocol(IPPROTO_UDP).build()
+ agent.sendAddDscpPolicy(policy2)
+ agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
+ assertEquals(1, it.policyId)
+ assertEquals(DSCP_POLICY_STATUS_SUCCESS, it.status)
+ }
+ validatePacket(agent, true, dscpValue = 4, dstPort = 5555)
+
+ agent.sendRemoveDscpPolicy(1)
+ agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
+ assertEquals(1, it.policyId)
+ assertEquals(DSCP_POLICY_STATUS_DELETED, it.status)
}
}
@@ -337,41 +444,44 @@
agent.sendAddDscpPolicy(policy)
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
assertEquals(1, it.policyId)
- assertEquals(STATUS_SUCCESS, it.status)
- checkDscpValue(agent, callback, dscpValue = 1, dstPort = 1111)
+ assertEquals(DSCP_POLICY_STATUS_SUCCESS, it.status)
+ validatePacket(agent, dscpValue = 1, dstPort = 1111)
}
val policy2 = DscpPolicy.Builder(2, 1).setDestinationPortRange(Range(2222, 2222)).build()
agent.sendAddDscpPolicy(policy2)
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
assertEquals(2, it.policyId)
- assertEquals(STATUS_SUCCESS, it.status)
- checkDscpValue(agent, callback, dscpValue = 1, dstPort = 2222)
+ assertEquals(DSCP_POLICY_STATUS_SUCCESS, it.status)
+ validatePacket(agent, dscpValue = 1, dstPort = 2222)
}
val policy3 = DscpPolicy.Builder(3, 1).setDestinationPortRange(Range(3333, 3333)).build()
agent.sendAddDscpPolicy(policy3)
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
assertEquals(3, it.policyId)
- assertEquals(STATUS_SUCCESS, it.status)
- checkDscpValue(agent, callback, dscpValue = 1, dstPort = 3333)
+ assertEquals(DSCP_POLICY_STATUS_SUCCESS, it.status)
+ validatePacket(agent, dscpValue = 1, dstPort = 3333)
}
/* Remove Policies and check CE is no longer set */
doRemovePolicyTest(agent, callback, 1)
+ validatePacket(agent, dscpValue = 0, dstPort = 1111)
doRemovePolicyTest(agent, callback, 2)
+ validatePacket(agent, dscpValue = 0, dstPort = 2222)
doRemovePolicyTest(agent, callback, 3)
+ validatePacket(agent, dscpValue = 0, dstPort = 3333)
}
@Test
fun testRemoveDscpPolicy_RemoveImmediatelyAfterAdd(): Unit =
- createConnectedNetworkAgent().let{ (agent, callback) ->
+ createConnectedNetworkAgent().let { (agent, callback) ->
val policy = DscpPolicy.Builder(1, 1).setDestinationPortRange(Range(1111, 1111)).build()
agent.sendAddDscpPolicy(policy)
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
assertEquals(1, it.policyId)
- assertEquals(STATUS_SUCCESS, it.status)
- checkDscpValue(agent, callback, dscpValue = 1, dstPort = 1111)
+ assertEquals(DSCP_POLICY_STATUS_SUCCESS, it.status)
+ validatePacket(agent, dscpValue = 1, dstPort = 1111)
}
doRemovePolicyTest(agent, callback, 1)
@@ -379,8 +489,8 @@
agent.sendAddDscpPolicy(policy2)
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
assertEquals(2, it.policyId)
- assertEquals(STATUS_SUCCESS, it.status)
- checkDscpValue(agent, callback, dscpValue = 1, dstPort = 2222)
+ assertEquals(DSCP_POLICY_STATUS_SUCCESS, it.status)
+ validatePacket(agent, dscpValue = 1, dstPort = 2222)
}
doRemovePolicyTest(agent, callback, 2)
@@ -388,8 +498,8 @@
agent.sendAddDscpPolicy(policy3)
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
assertEquals(3, it.policyId)
- assertEquals(STATUS_SUCCESS, it.status)
- checkDscpValue(agent, callback, dscpValue = 1, dstPort = 3333)
+ assertEquals(DSCP_POLICY_STATUS_SUCCESS, it.status)
+ validatePacket(agent, dscpValue = 1, dstPort = 3333)
}
doRemovePolicyTest(agent, callback, 3)
}
@@ -402,24 +512,24 @@
agent.sendAddDscpPolicy(policy)
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
assertEquals(1, it.policyId)
- assertEquals(STATUS_SUCCESS, it.status)
- checkDscpValue(agent, callback, dscpValue = 1, dstPort = 1111)
+ assertEquals(DSCP_POLICY_STATUS_SUCCESS, it.status)
+ validatePacket(agent, dscpValue = 1, dstPort = 1111)
}
val policy2 = DscpPolicy.Builder(2, 1).setDestinationPortRange(Range(2222, 2222)).build()
agent.sendAddDscpPolicy(policy2)
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
assertEquals(2, it.policyId)
- assertEquals(STATUS_SUCCESS, it.status)
- checkDscpValue(agent, callback, dscpValue = 1, dstPort = 2222)
+ assertEquals(DSCP_POLICY_STATUS_SUCCESS, it.status)
+ validatePacket(agent, dscpValue = 1, dstPort = 2222)
}
val policy3 = DscpPolicy.Builder(3, 1).setDestinationPortRange(Range(3333, 3333)).build()
agent.sendAddDscpPolicy(policy3)
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
assertEquals(3, it.policyId)
- assertEquals(STATUS_SUCCESS, it.status)
- checkDscpValue(agent, callback, dscpValue = 1, dstPort = 3333)
+ assertEquals(DSCP_POLICY_STATUS_SUCCESS, it.status)
+ validatePacket(agent, dscpValue = 1, dstPort = 3333)
}
/* Remove Policies and check CE is no longer set */
@@ -433,18 +543,19 @@
(agent, callback) ->
agent.sendRemoveDscpPolicy(3)
// Is there something to add in TestableNetworkCallback to NOT expect a callback?
- // Or should we send STATUS_DELETED in any case or a different STATUS?
+ // Or should we send DSCP_POLICY_STATUS_DELETED in any case or a different STATUS?
}
@Test
- fun testRemoveAllDscpPolicies(): Unit = createConnectedNetworkAgent().let { (agent, callback) ->
+ fun testRemoveAllDscpPolicies(): Unit = createConnectedNetworkAgent().let {
+ (agent, callback) ->
val policy = DscpPolicy.Builder(1, 1)
.setDestinationPortRange(Range(1111, 1111)).build()
agent.sendAddDscpPolicy(policy)
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
assertEquals(1, it.policyId)
- assertEquals(STATUS_SUCCESS, it.status)
- checkDscpValue(agent, callback, dscpValue = 1, dstPort = 1111)
+ assertEquals(DSCP_POLICY_STATUS_SUCCESS, it.status)
+ validatePacket(agent, dscpValue = 1, dstPort = 1111)
}
val policy2 = DscpPolicy.Builder(2, 1)
@@ -452,8 +563,8 @@
agent.sendAddDscpPolicy(policy2)
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
assertEquals(2, it.policyId)
- assertEquals(STATUS_SUCCESS, it.status)
- checkDscpValue(agent, callback, dscpValue = 1, dstPort = 2222)
+ assertEquals(DSCP_POLICY_STATUS_SUCCESS, it.status)
+ validatePacket(agent, dscpValue = 1, dstPort = 2222)
}
val policy3 = DscpPolicy.Builder(3, 1)
@@ -461,25 +572,25 @@
agent.sendAddDscpPolicy(policy3)
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
assertEquals(3, it.policyId)
- assertEquals(STATUS_SUCCESS, it.status)
- checkDscpValue(agent, callback, dscpValue = 1, dstPort = 3333)
+ assertEquals(DSCP_POLICY_STATUS_SUCCESS, it.status)
+ validatePacket(agent, dscpValue = 1, dstPort = 3333)
}
agent.sendRemoveAllDscpPolicies()
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
assertEquals(1, it.policyId)
- assertEquals(STATUS_DELETED, it.status)
- checkDscpValue(agent, callback, dstPort = 1111)
+ assertEquals(DSCP_POLICY_STATUS_DELETED, it.status)
+ validatePacket(agent, false, dstPort = 1111)
}
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
assertEquals(2, it.policyId)
- assertEquals(STATUS_DELETED, it.status)
- checkDscpValue(agent, callback, dstPort = 2222)
+ assertEquals(DSCP_POLICY_STATUS_DELETED, it.status)
+ validatePacket(agent, false, dstPort = 2222)
}
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
assertEquals(3, it.policyId)
- assertEquals(STATUS_DELETED, it.status)
- checkDscpValue(agent, callback, dstPort = 3333)
+ assertEquals(DSCP_POLICY_STATUS_DELETED, it.status)
+ validatePacket(agent, false, dstPort = 3333)
}
}
@@ -490,44 +601,57 @@
agent.sendAddDscpPolicy(policy)
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
assertEquals(1, it.policyId)
- assertEquals(STATUS_SUCCESS, it.status)
- checkDscpValue(agent, callback, dscpValue = 1, dstPort = 4444)
+ assertEquals(DSCP_POLICY_STATUS_SUCCESS, it.status)
+ validatePacket(agent, dscpValue = 1, dstPort = 4444)
}
- // TODO: send packet on socket and confirm that changing the DSCP policy
- // updates the mark to the new value.
-
val policy2 = DscpPolicy.Builder(1, 1).setDestinationPortRange(Range(5555, 5555)).build()
agent.sendAddDscpPolicy(policy2)
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
assertEquals(1, it.policyId)
- assertEquals(STATUS_SUCCESS, it.status)
+ assertEquals(DSCP_POLICY_STATUS_SUCCESS, it.status)
// Sending packet with old policy should fail
- checkDscpValue(agent, callback, dstPort = 4444)
- checkDscpValue(agent, callback, dscpValue = 1, dstPort = 5555)
+ validatePacket(agent, dscpValue = 0, dstPort = 4444)
+ validatePacket(agent, dscpValue = 1, dstPort = 5555)
}
agent.sendRemoveDscpPolicy(1)
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
assertEquals(1, it.policyId)
- assertEquals(STATUS_DELETED, it.status)
+ assertEquals(DSCP_POLICY_STATUS_DELETED, it.status)
}
}
@Test
fun testParcelingDscpPolicyIsLossless(): Unit = createConnectedNetworkAgent().let {
(agent, callback) ->
+ val policyId = 1
+ val dscpValue = 1
+ val range = Range(4444, 4444)
+ val srcPort = 555
+
// Check that policy with partial parameters is lossless.
- val policy = DscpPolicy.Builder(1, 1).setDestinationPortRange(Range(4444, 4444)).build()
- assertParcelingIsLossless(policy);
+ val policy = DscpPolicy.Builder(policyId, dscpValue).setDestinationPortRange(range).build()
+ assertEquals(policyId, policy.policyId)
+ assertEquals(dscpValue, policy.dscpValue)
+ assertEquals(range, policy.destinationPortRange)
+ assertParcelingIsLossless(policy)
// Check that policy with all parameters is lossless.
- val policy2 = DscpPolicy.Builder(1, 1).setDestinationPortRange(Range(4444, 4444))
+ val policy2 = DscpPolicy.Builder(policyId, dscpValue).setDestinationPortRange(range)
.setSourceAddress(LOCAL_IPV4_ADDRESS)
.setDestinationAddress(TEST_TARGET_IPV4_ADDR)
+ .setSourcePort(srcPort)
.setProtocol(IPPROTO_UDP).build()
- assertParcelingIsLossless(policy2);
+ assertEquals(policyId, policy2.policyId)
+ assertEquals(dscpValue, policy2.dscpValue)
+ assertEquals(range, policy2.destinationPortRange)
+ assertEquals(TEST_TARGET_IPV4_ADDR, policy2.destinationAddress)
+ assertEquals(LOCAL_IPV4_ADDRESS, policy2.sourceAddress)
+ assertEquals(srcPort, policy2.sourcePort)
+ assertEquals(IPPROTO_UDP, policy2.protocol)
+ assertParcelingIsLossless(policy2)
}
}
diff --git a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
index 0a32f09..db24b44 100644
--- a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
@@ -15,57 +15,129 @@
*/
package android.net.cts
+import android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS
import android.Manifest.permission.MANAGE_TEST_NETWORKS
import android.Manifest.permission.NETWORK_SETTINGS
+import android.content.Context
+import android.net.ConnectivityManager
+import android.net.EthernetManager
+import android.net.EthernetManager.InterfaceStateListener
+import android.net.EthernetManager.ROLE_CLIENT
+import android.net.EthernetManager.ROLE_NONE
+import android.net.EthernetManager.ROLE_SERVER
+import android.net.EthernetManager.STATE_ABSENT
+import android.net.EthernetManager.STATE_LINK_DOWN
+import android.net.EthernetManager.STATE_LINK_UP
+import android.net.EthernetManager.TetheredInterfaceCallback
+import android.net.EthernetManager.TetheredInterfaceRequest
+import android.net.EthernetNetworkSpecifier
+import android.net.InetAddresses
import android.net.IpConfiguration
+import android.net.MacAddress
+import android.net.Network
+import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED
+import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
+import android.net.NetworkCapabilities.TRANSPORT_TEST
+import android.net.NetworkRequest
import android.net.TestNetworkInterface
import android.net.TestNetworkManager
-import android.platform.test.annotations.AppModeFull
-import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.runner.AndroidJUnit4
-import com.android.net.module.util.ArrayTrackRecord
-import com.android.net.module.util.TrackRecord
-import com.android.testutils.DevSdkIgnoreRule
-import com.android.testutils.SC_V2
-import com.android.testutils.runAsShell
-import org.junit.After
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import kotlin.test.assertNull
-import kotlin.test.fail
import android.net.cts.EthernetManagerTest.EthernetStateListener.CallbackEntry.InterfaceStateChanged
+import android.os.Build
import android.os.Handler
import android.os.HandlerExecutor
import android.os.Looper
-import com.android.networkstack.apishim.common.EthernetManagerShim.InterfaceStateListener
-import com.android.networkstack.apishim.common.EthernetManagerShim.STATE_ABSENT
-import com.android.networkstack.apishim.common.EthernetManagerShim.STATE_LINK_DOWN
-import com.android.networkstack.apishim.common.EthernetManagerShim.STATE_LINK_UP
-import com.android.networkstack.apishim.common.EthernetManagerShim.ROLE_CLIENT
-import com.android.networkstack.apishim.common.EthernetManagerShim.ROLE_NONE
-import com.android.networkstack.apishim.EthernetManagerShimImpl
-import java.util.concurrent.Executor
+import android.os.SystemProperties
+import android.platform.test.annotations.AppModeFull
+import android.util.ArraySet
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.net.module.util.ArrayTrackRecord
+import com.android.net.module.util.TrackRecord
+import com.android.testutils.anyNetwork
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.RecorderCallback.CallbackEntry.Available
+import com.android.testutils.RecorderCallback.CallbackEntry.Lost
+import com.android.testutils.RouterAdvertisementResponder
+import com.android.testutils.TapPacketReader
+import com.android.testutils.TestableNetworkCallback
+import com.android.testutils.runAsShell
+import com.android.testutils.waitForIdle
+import org.junit.After
+import org.junit.Assume.assumeFalse
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.net.Inet6Address
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.TimeUnit
import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+import kotlin.test.fail
-private const val TIMEOUT_MS = 1000L
+// TODO: try to lower this timeout in the future. Currently, ethernet tests are still flaky because
+// the interface is not ready fast enough (mostly due to the up / up / down / up issue).
+private const val TIMEOUT_MS = 2000L
private const val NO_CALLBACK_TIMEOUT_MS = 200L
private val DEFAULT_IP_CONFIGURATION = IpConfiguration(IpConfiguration.IpAssignment.DHCP,
IpConfiguration.ProxySettings.NONE, null, null)
+private val ETH_REQUEST: NetworkRequest = NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_TEST)
+ .addTransportType(TRANSPORT_ETHERNET)
+ .removeCapability(NET_CAPABILITY_TRUSTED)
+ .build()
@AppModeFull(reason = "Instant apps can't access EthernetManager")
-@RunWith(AndroidJUnit4::class)
+// EthernetManager is not updatable before T, so tests do not need to be backwards compatible.
+@RunWith(DevSdkIgnoreRunner::class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
class EthernetManagerTest {
- // EthernetManager is not updatable before T, so tests do not need to be backwards compatible
- @get:Rule
- val ignoreRule = DevSdkIgnoreRule(ignoreClassUpTo = SC_V2)
private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
- private val em by lazy { EthernetManagerShimImpl.newInstance(context) }
+ private val em by lazy { context.getSystemService(EthernetManager::class.java) }
+ private val cm by lazy { context.getSystemService(ConnectivityManager::class.java) }
- private val createdIfaces = ArrayList<TestNetworkInterface>()
- private val addedListeners = ArrayList<InterfaceStateListener>()
+ private val ifaceListener = EthernetStateListener()
+ private val createdIfaces = ArrayList<EthernetTestInterface>()
+ private val addedListeners = ArrayList<EthernetStateListener>()
+ private val networkRequests = ArrayList<TestableNetworkCallback>()
+
+ private var tetheredInterfaceRequest: TetheredInterfaceRequest? = null
+
+ private class EthernetTestInterface(
+ context: Context,
+ private val handler: Handler
+ ) {
+ private val tapInterface: TestNetworkInterface
+ private val packetReader: TapPacketReader
+ private val raResponder: RouterAdvertisementResponder
+ val interfaceName get() = tapInterface.interfaceName
+
+ init {
+ tapInterface = runAsShell(MANAGE_TEST_NETWORKS) {
+ val tnm = context.getSystemService(TestNetworkManager::class.java)
+ tnm.createTapInterface(false /* bringUp */)
+ }
+ val mtu = 1500
+ packetReader = TapPacketReader(handler, tapInterface.fileDescriptor.fileDescriptor, mtu)
+ raResponder = RouterAdvertisementResponder(packetReader)
+ raResponder.addRouterEntry(MacAddress.fromString("01:23:45:67:89:ab"),
+ InetAddresses.parseNumericAddress("fe80::abcd") as Inet6Address)
+
+ packetReader.startAsyncForTest()
+ raResponder.start()
+ }
+
+ fun destroy() {
+ raResponder.stop()
+ handler.post({ packetReader.stop() })
+ handler.waitForIdle(TIMEOUT_MS)
+ }
+ }
private open class EthernetStateListener private constructor(
private val history: ArrayTrackRecord<CallbackEntry>
@@ -99,86 +171,364 @@
return event as T
}
- fun expectCallback(iface: TestNetworkInterface, state: Int, role: Int) {
- expectCallback(InterfaceStateChanged(iface.interfaceName, state, role,
- if (state != STATE_ABSENT) DEFAULT_IP_CONFIGURATION else null))
+ fun expectCallback(iface: EthernetTestInterface, state: Int, role: Int) {
+ expectCallback(createChangeEvent(iface.interfaceName, state, role))
}
+ fun createChangeEvent(iface: String, state: Int, role: Int) =
+ InterfaceStateChanged(iface, state, role,
+ if (state != STATE_ABSENT) DEFAULT_IP_CONFIGURATION else null)
+
fun pollForNextCallback(): CallbackEntry {
return events.poll(TIMEOUT_MS) ?: fail("Did not receive callback after ${TIMEOUT_MS}ms")
}
+ fun eventuallyExpect(expected: CallbackEntry) = events.poll(TIMEOUT_MS) { it == expected }
+
+ fun eventuallyExpect(interfaceName: String, state: Int, role: Int) {
+ assertNotNull(eventuallyExpect(createChangeEvent(interfaceName, state, role)))
+ }
+
+ fun eventuallyExpect(iface: EthernetTestInterface, state: Int, role: Int) {
+ eventuallyExpect(iface.interfaceName, state, role)
+ }
+
fun assertNoCallback() {
val cb = events.poll(NO_CALLBACK_TIMEOUT_MS)
assertNull(cb, "Expected no callback but got $cb")
}
}
- @Test
- public fun testCallbacks() {
- val executor = HandlerExecutor(Handler(Looper.getMainLooper()))
+ private class TetheredInterfaceListener : TetheredInterfaceCallback {
+ private val available = CompletableFuture<String>()
- // If an interface exists when the callback is registered, it is reported on registration.
- val iface = runAsShell(MANAGE_TEST_NETWORKS) {
- createInterface()
+ override fun onAvailable(iface: String) {
+ available.complete(iface)
}
- val listener = EthernetStateListener()
- addInterfaceStateListener(executor, listener)
- listener.expectCallback(iface, STATE_LINK_UP, ROLE_CLIENT)
- // If an interface appears, existing callbacks see it.
- // TODO: fix the up/up/down/up callbacks and only send down/up.
- val iface2 = runAsShell(MANAGE_TEST_NETWORKS) {
- createInterface()
+ override fun onUnavailable() {
+ available.completeExceptionally(IllegalStateException("onUnavailable was called"))
}
- listener.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
- listener.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
- listener.expectCallback(iface2, STATE_LINK_DOWN, ROLE_CLIENT)
- listener.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
- // Removing interfaces first sends link down, then STATE_ABSENT/ROLE_NONE.
- removeInterface(iface)
- listener.expectCallback(iface, STATE_LINK_DOWN, ROLE_CLIENT)
- listener.expectCallback(iface, STATE_ABSENT, ROLE_NONE)
+ fun expectOnAvailable(): String {
+ return available.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+ }
- removeInterface(iface2)
- listener.expectCallback(iface2, STATE_LINK_DOWN, ROLE_CLIENT)
- listener.expectCallback(iface2, STATE_ABSENT, ROLE_NONE)
- listener.assertNoCallback()
+ fun expectOnUnavailable() {
+ // Assert that the future fails with the IllegalStateException from the
+ // completeExceptionally() call inside onUnavailable.
+ assertFailsWith(IllegalStateException::class) {
+ try {
+ available.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+ } catch (e: ExecutionException) {
+ throw e.cause!!
+ }
+ }
+ }
}
@Before
fun setUp() {
- runAsShell(MANAGE_TEST_NETWORKS, NETWORK_SETTINGS) {
- em.setIncludeTestInterfaces(true)
- }
+ setIncludeTestInterfaces(true)
+ addInterfaceStateListener(ifaceListener)
}
@After
fun tearDown() {
- runAsShell(MANAGE_TEST_NETWORKS, NETWORK_SETTINGS) {
- em.setIncludeTestInterfaces(false)
- for (iface in createdIfaces) {
- if (iface.fileDescriptor.fileDescriptor.valid()) iface.fileDescriptor.close()
- }
- for (listener in addedListeners) {
- em.removeInterfaceStateListener(listener)
- }
+ setIncludeTestInterfaces(false)
+ for (iface in createdIfaces) {
+ iface.destroy()
+ ifaceListener.eventuallyExpect(iface, STATE_ABSENT, ROLE_NONE)
}
+ for (listener in addedListeners) {
+ em.removeInterfaceStateListener(listener)
+ }
+ networkRequests.forEach { cm.unregisterNetworkCallback(it) }
+ releaseTetheredInterface()
}
- private fun addInterfaceStateListener(executor: Executor, listener: InterfaceStateListener) {
- em.addInterfaceStateListener(executor, listener)
+ private fun addInterfaceStateListener(listener: EthernetStateListener) {
+ runAsShell(CONNECTIVITY_USE_RESTRICTED_NETWORKS) {
+ em.addInterfaceStateListener(HandlerExecutor(Handler(Looper.getMainLooper())), listener)
+ }
addedListeners.add(listener)
}
- private fun createInterface(): TestNetworkInterface {
- val tnm = context.getSystemService(TestNetworkManager::class.java)
- return tnm.createTapInterface(false /* bringUp */).also { createdIfaces.add(it) }
+ private fun createInterface(): EthernetTestInterface {
+ val iface = EthernetTestInterface(
+ context,
+ Handler(Looper.getMainLooper())
+ ).also { createdIfaces.add(it) }
+ with(ifaceListener) {
+ // when an interface comes up, we should always see a down cb before an up cb.
+ eventuallyExpect(iface, STATE_LINK_DOWN, ROLE_CLIENT)
+ expectCallback(iface, STATE_LINK_UP, ROLE_CLIENT)
+ }
+ return iface
}
- private fun removeInterface(iface: TestNetworkInterface) {
- iface.fileDescriptor.close()
- createdIfaces.remove(iface)
+ private fun setIncludeTestInterfaces(value: Boolean) {
+ runAsShell(NETWORK_SETTINGS) {
+ em.setIncludeTestInterfaces(value)
+ }
}
-}
\ No newline at end of file
+
+ private fun removeInterface(iface: EthernetTestInterface) {
+ iface.destroy()
+ createdIfaces.remove(iface)
+ ifaceListener.eventuallyExpect(iface, STATE_ABSENT, ROLE_NONE)
+ }
+
+ private fun requestNetwork(request: NetworkRequest): TestableNetworkCallback {
+ return TestableNetworkCallback().also {
+ cm.requestNetwork(request, it)
+ networkRequests.add(it)
+ }
+ }
+
+ private fun releaseNetwork(cb: TestableNetworkCallback) {
+ cm.unregisterNetworkCallback(cb)
+ networkRequests.remove(cb)
+ }
+
+ private fun requestTetheredInterface() = TetheredInterfaceListener().also {
+ tetheredInterfaceRequest = runAsShell(NETWORK_SETTINGS) {
+ em.requestTetheredInterface(HandlerExecutor(Handler(Looper.getMainLooper())), it)
+ }
+ }
+
+ private fun releaseTetheredInterface() {
+ runAsShell(NETWORK_SETTINGS) {
+ tetheredInterfaceRequest?.release()
+ tetheredInterfaceRequest = null
+ }
+ }
+
+ private fun NetworkRequest.createCopyWithEthernetSpecifier(ifaceName: String) =
+ NetworkRequest.Builder(NetworkRequest(ETH_REQUEST))
+ .setNetworkSpecifier(EthernetNetworkSpecifier(ifaceName)).build()
+
+ // It can take multiple seconds for the network to become available.
+ private fun TestableNetworkCallback.expectAvailable() =
+ expectCallback<Available>(anyNetwork(), 5000/*ms timeout*/).network
+
+ // b/233534110: eventuallyExpect<Lost>() does not advance ReadHead, use
+ // eventuallyExpect(Lost::class) instead.
+ private fun TestableNetworkCallback.eventuallyExpectLost(n: Network? = null) =
+ eventuallyExpect(Lost::class, TIMEOUT_MS) { n?.equals(it.network) ?: true }
+
+ private fun TestableNetworkCallback.assertNotLost(n: Network? = null) =
+ assertNoCallbackThat() { it is Lost && (n?.equals(it.network) ?: true) }
+
+ @Test
+ fun testCallbacks() {
+ // If an interface exists when the callback is registered, it is reported on registration.
+ val iface = createInterface()
+ val listener1 = EthernetStateListener()
+ addInterfaceStateListener(listener1)
+ validateListenerOnRegistration(listener1)
+
+ // If an interface appears, existing callbacks see it.
+ // TODO: fix the up/up/down/up callbacks and only send down/up.
+ val iface2 = createInterface()
+ listener1.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
+ listener1.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
+ listener1.expectCallback(iface2, STATE_LINK_DOWN, ROLE_CLIENT)
+ listener1.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
+
+ // Register a new listener, it should see state of all existing interfaces immediately.
+ val listener2 = EthernetStateListener()
+ addInterfaceStateListener(listener2)
+ validateListenerOnRegistration(listener2)
+
+ // Removing interfaces first sends link down, then STATE_ABSENT/ROLE_NONE.
+ removeInterface(iface)
+ for (listener in listOf(listener1, listener2)) {
+ listener.expectCallback(iface, STATE_LINK_DOWN, ROLE_CLIENT)
+ listener.expectCallback(iface, STATE_ABSENT, ROLE_NONE)
+ }
+
+ removeInterface(iface2)
+ for (listener in listOf(listener1, listener2)) {
+ listener.expectCallback(iface2, STATE_LINK_DOWN, ROLE_CLIENT)
+ listener.expectCallback(iface2, STATE_ABSENT, ROLE_NONE)
+ listener.assertNoCallback()
+ }
+ }
+
+ // TODO: this function is now used in two places (EthernetManagerTest and
+ // EthernetTetheringTest), so it should be moved to testutils.
+ private fun isAdbOverNetwork(): Boolean {
+ // If adb TCP port opened, this test may running by adb over network.
+ return (SystemProperties.getInt("persist.adb.tcp.port", -1) > -1 ||
+ SystemProperties.getInt("service.adb.tcp.port", -1) > -1)
+ }
+
+ @Test
+ fun testCallbacks_forServerModeInterfaces() {
+ // do not run this test when adb might be connected over ethernet.
+ assumeFalse(isAdbOverNetwork())
+
+ val listener = EthernetStateListener()
+ addInterfaceStateListener(listener)
+
+ // it is possible that a physical interface is present, so it is not guaranteed that iface
+ // will be put into server mode. This should not matter for the test though. Calling
+ // createInterface() makes sure we have at least one interface available.
+ val iface = createInterface()
+ val cb = requestTetheredInterface()
+ val ifaceName = cb.expectOnAvailable()
+ listener.eventuallyExpect(ifaceName, STATE_LINK_UP, ROLE_SERVER)
+
+ releaseTetheredInterface()
+ listener.eventuallyExpect(ifaceName, STATE_LINK_UP, ROLE_CLIENT)
+ }
+
+ /**
+ * Validate all interfaces are returned for an EthernetStateListener upon registration.
+ */
+ private fun validateListenerOnRegistration(listener: EthernetStateListener) {
+ // Get all tracked interfaces to validate on listener registration. Ordering and interface
+ // state (up/down) can't be validated for interfaces not created as part of testing.
+ val ifaces = em.getInterfaceList()
+ val polledIfaces = ArraySet<String>()
+ for (i in ifaces) {
+ val event = (listener.pollForNextCallback() as InterfaceStateChanged)
+ val iface = event.iface
+ assertTrue(polledIfaces.add(iface), "Duplicate interface $iface returned")
+ assertTrue(ifaces.contains(iface), "Untracked interface $iface returned")
+ // If the event's iface was created in the test, additional criteria can be validated.
+ createdIfaces.find { it.interfaceName.equals(iface) }?.let {
+ assertEquals(event,
+ listener.createChangeEvent(it.interfaceName,
+ STATE_LINK_UP,
+ ROLE_CLIENT))
+ }
+ }
+ // Assert all callbacks are accounted for.
+ listener.assertNoCallback()
+ }
+
+ @Test
+ fun testGetInterfaceList() {
+ setIncludeTestInterfaces(true)
+
+ // Create two test interfaces and check the return list contains the interface names.
+ val iface1 = createInterface()
+ val iface2 = createInterface()
+ var ifaces = em.getInterfaceList()
+ assertTrue(ifaces.size > 0)
+ assertTrue(ifaces.contains(iface1.interfaceName))
+ assertTrue(ifaces.contains(iface2.interfaceName))
+
+ // Remove one existing test interface and check the return list doesn't contain the
+ // removed interface name.
+ removeInterface(iface1)
+ ifaces = em.getInterfaceList()
+ assertFalse(ifaces.contains(iface1.interfaceName))
+ assertTrue(ifaces.contains(iface2.interfaceName))
+
+ removeInterface(iface2)
+ }
+
+ @Test
+ fun testNetworkRequest_withSingleExistingInterface() {
+ setIncludeTestInterfaces(true)
+ createInterface()
+
+ // install a listener which will later be used to verify the Lost callback
+ val listenerCb = TestableNetworkCallback()
+ cm.registerNetworkCallback(ETH_REQUEST, listenerCb)
+ networkRequests.add(listenerCb)
+
+ val cb = requestNetwork(ETH_REQUEST)
+ val network = cb.expectAvailable()
+
+ cb.assertNotLost()
+ releaseNetwork(cb)
+ listenerCb.eventuallyExpectLost(network)
+ }
+
+ @Test
+ fun testNetworkRequest_beforeSingleInterfaceIsUp() {
+ setIncludeTestInterfaces(true)
+
+ val cb = requestNetwork(ETH_REQUEST)
+
+ // bring up interface after network has been requested
+ val iface = createInterface()
+ val network = cb.expectAvailable()
+
+ // remove interface before network request has been removed
+ cb.assertNotLost()
+ removeInterface(iface)
+ cb.eventuallyExpectLost()
+
+ releaseNetwork(cb)
+ }
+
+ @Test
+ fun testNetworkRequest_withMultipleInterfaces() {
+ setIncludeTestInterfaces(true)
+
+ val iface1 = createInterface()
+ val iface2 = createInterface()
+
+ val cb = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface2.interfaceName))
+
+ val network = cb.expectAvailable()
+ cb.expectCapabilitiesThat(network) {
+ it.networkSpecifier == EthernetNetworkSpecifier(iface2.interfaceName)
+ }
+
+ removeInterface(iface1)
+ cb.assertNotLost()
+ removeInterface(iface2)
+ cb.eventuallyExpectLost()
+
+ releaseNetwork(cb)
+ }
+
+ @Test
+ fun testNetworkRequest_withInterfaceBeingReplaced() {
+ setIncludeTestInterfaces(true)
+ val iface1 = createInterface()
+
+ val cb = requestNetwork(ETH_REQUEST)
+ val network = cb.expectAvailable()
+
+ // create another network and verify the request sticks to the current network
+ val iface2 = createInterface()
+ cb.assertNotLost()
+
+ // remove iface1 and verify the request brings up iface2
+ removeInterface(iface1)
+ cb.eventuallyExpectLost(network)
+ val network2 = cb.expectAvailable()
+
+ releaseNetwork(cb)
+ }
+
+ @Test
+ fun testNetworkRequest_withMultipleInterfacesAndRequests() {
+ setIncludeTestInterfaces(true)
+ val iface1 = createInterface()
+ val iface2 = createInterface()
+
+ val cb1 = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface1.interfaceName))
+ val cb2 = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface2.interfaceName))
+ val cb3 = requestNetwork(ETH_REQUEST)
+
+ cb1.expectAvailable()
+ cb2.expectAvailable()
+ cb3.expectAvailable()
+
+ cb1.assertNotLost()
+ cb2.assertNotLost()
+ cb3.assertNotLost()
+
+ releaseNetwork(cb1)
+ releaseNetwork(cb2)
+ releaseNetwork(cb3)
+ }
+}
diff --git a/tests/cts/net/src/android/net/cts/EthernetNetworkUpdateRequestTest.java b/tests/cts/net/src/android/net/cts/EthernetNetworkUpdateRequestTest.java
new file mode 100644
index 0000000..c8ee0c7
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/EthernetNetworkUpdateRequestTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.cts;
+
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertThrows;
+
+import android.annotation.NonNull;
+import android.net.EthernetNetworkUpdateRequest;
+import android.net.IpConfiguration;
+import android.net.NetworkCapabilities;
+import android.net.StaticIpConfiguration;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
+@RunWith(DevSdkIgnoreRunner.class)
+public class EthernetNetworkUpdateRequestTest {
+ private static final NetworkCapabilities DEFAULT_CAPS =
+ new NetworkCapabilities.Builder()
+ .removeCapability(NET_CAPABILITY_NOT_RESTRICTED).build();
+ private static final StaticIpConfiguration DEFAULT_STATIC_IP_CONFIG =
+ new StaticIpConfiguration.Builder().setDomains("test").build();
+ private static final IpConfiguration DEFAULT_IP_CONFIG =
+ new IpConfiguration.Builder()
+ .setStaticIpConfiguration(DEFAULT_STATIC_IP_CONFIG).build();
+
+ private EthernetNetworkUpdateRequest createRequest(@NonNull final NetworkCapabilities nc,
+ @NonNull final IpConfiguration ipConfig) {
+ return new EthernetNetworkUpdateRequest.Builder()
+ .setNetworkCapabilities(nc)
+ .setIpConfiguration(ipConfig)
+ .build();
+ }
+
+ @Test
+ public void testGetNetworkCapabilities() {
+ final EthernetNetworkUpdateRequest r = createRequest(DEFAULT_CAPS, DEFAULT_IP_CONFIG);
+ assertEquals(DEFAULT_CAPS, r.getNetworkCapabilities());
+ }
+
+ @Test
+ public void testGetIpConfiguration() {
+ final EthernetNetworkUpdateRequest r = createRequest(DEFAULT_CAPS, DEFAULT_IP_CONFIG);
+ assertEquals(DEFAULT_IP_CONFIG, r.getIpConfiguration());
+ }
+
+ @Test
+ public void testBuilderWithRequest() {
+ final EthernetNetworkUpdateRequest r = createRequest(DEFAULT_CAPS, DEFAULT_IP_CONFIG);
+ final EthernetNetworkUpdateRequest rFromExisting =
+ new EthernetNetworkUpdateRequest.Builder(r).build();
+
+ assertNotSame(r, rFromExisting);
+ assertEquals(r.getIpConfiguration(), rFromExisting.getIpConfiguration());
+ assertEquals(r.getNetworkCapabilities(), rFromExisting.getNetworkCapabilities());
+ }
+
+ @Test
+ public void testNullIpConfigurationAndNetworkCapabilitiesThrows() {
+ assertThrows("Should not be able to build with null ip config and network capabilities.",
+ IllegalStateException.class,
+ () -> createRequest(null, null));
+ }
+}
diff --git a/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java b/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
index 6e9f0cd..2b1d173 100644
--- a/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
+++ b/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
@@ -17,16 +17,21 @@
package android.net.cts;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.cts.util.CtsNetUtils.TestNetworkCallback;
import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
+import static com.android.testutils.TestableNetworkCallbackKt.anyNetwork;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
@@ -40,24 +45,34 @@
import android.net.Ikev2VpnProfile;
import android.net.IpSecAlgorithm;
import android.net.Network;
-import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.ProxyInfo;
import android.net.TestNetworkInterface;
import android.net.VpnManager;
import android.net.cts.util.CtsNetUtils;
+import android.net.cts.util.IkeSessionTestUtils;
+import android.net.ipsec.ike.IkeTunnelConnectionParams;
import android.os.Build;
import android.os.Process;
import android.platform.test.annotations.AppModeFull;
+import android.text.TextUtils;
import androidx.test.InstrumentationRegistry;
import com.android.internal.util.HexDump;
+import com.android.networkstack.apishim.ConstantsShim;
+import com.android.networkstack.apishim.VpnManagerShimImpl;
+import com.android.networkstack.apishim.common.VpnManagerShim;
+import com.android.networkstack.apishim.common.VpnProfileStateShim;
+import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DevSdkIgnoreRunner;
+import com.android.testutils.RecorderCallback.CallbackEntry;
+import com.android.testutils.TestableNetworkCallback;
import org.bouncycastle.x509.X509V1CertificateGenerator;
import org.junit.After;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -67,6 +82,7 @@
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
@@ -80,6 +96,9 @@
public class Ikev2VpnTest {
private static final String TAG = Ikev2VpnTest.class.getSimpleName();
+ @Rule
+ public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
+
// Test vectors for IKE negotiation in test mode.
private static final String SUCCESSFUL_IKE_INIT_RESP_V4 =
"46b8eca1e0d72a18b2b5d9006d47a0022120222000000000000002d0220000300000002c01010004030000"
@@ -167,9 +186,13 @@
private static final VpnManager sVpnMgr =
(VpnManager) sContext.getSystemService(Context.VPN_MANAGEMENT_SERVICE);
private static final CtsNetUtils mCtsNetUtils = new CtsNetUtils(sContext);
+ private static final long TIMEOUT_MS = 15_000;
+
+ private VpnManagerShim mVmShim = VpnManagerShimImpl.newInstance(sContext);
private final X509Certificate mServerRootCa;
private final CertificateAndKey mUserCertKey;
+ private final List<TestableNetworkCallback> mCallbacksToUnregister = new ArrayList<>();
public Ikev2VpnTest() throws Exception {
// Build certificates
@@ -179,6 +202,9 @@
@After
public void tearDown() {
+ for (TestableNetworkCallback callback : mCallbacksToUnregister) {
+ sCM.unregisterNetworkCallback(callback);
+ }
setAppop(AppOpsManager.OP_ACTIVATE_VPN, false);
setAppop(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, false);
}
@@ -198,39 +224,61 @@
}
private Ikev2VpnProfile buildIkev2VpnProfileCommon(
- Ikev2VpnProfile.Builder builder, boolean isRestrictedToTestNetworks) throws Exception {
+ @NonNull Ikev2VpnProfile.Builder builder, boolean isRestrictedToTestNetworks,
+ boolean requiresValidation) throws Exception {
+
+ builder.setBypassable(true)
+ .setAllowedAlgorithms(TEST_ALLOWED_ALGORITHMS)
+ .setProxy(TEST_PROXY_INFO)
+ .setMaxMtu(TEST_MTU)
+ .setMetered(false);
+ if (TestUtils.shouldTestTApis()) {
+ builder.setRequiresInternetValidation(requiresValidation);
+ }
if (isRestrictedToTestNetworks) {
builder.restrictToTestNetworks();
}
- return builder.setBypassable(true)
- .setAllowedAlgorithms(TEST_ALLOWED_ALGORITHMS)
- .setProxy(TEST_PROXY_INFO)
- .setMaxMtu(TEST_MTU)
- .setMetered(false)
- .build();
+ return builder.build();
}
- private Ikev2VpnProfile buildIkev2VpnProfilePsk(boolean isRestrictedToTestNetworks)
- throws Exception {
- return buildIkev2VpnProfilePsk(TEST_SERVER_ADDR_V6, isRestrictedToTestNetworks);
+ private Ikev2VpnProfile buildIkev2VpnProfileIkeTunConnParams(
+ final boolean isRestrictedToTestNetworks, final boolean requiresValidation,
+ final boolean testIpv6) throws Exception {
+ final IkeTunnelConnectionParams params =
+ new IkeTunnelConnectionParams(testIpv6
+ ? IkeSessionTestUtils.IKE_PARAMS_V6 : IkeSessionTestUtils.IKE_PARAMS_V4,
+ IkeSessionTestUtils.CHILD_PARAMS);
+
+ final Ikev2VpnProfile.Builder builder =
+ new Ikev2VpnProfile.Builder(params)
+ .setRequiresInternetValidation(requiresValidation)
+ .setProxy(TEST_PROXY_INFO)
+ .setMaxMtu(TEST_MTU)
+ .setMetered(false);
+
+ if (isRestrictedToTestNetworks) {
+ builder.restrictToTestNetworks();
+ }
+ return builder.build();
}
- private Ikev2VpnProfile buildIkev2VpnProfilePsk(
- String remote, boolean isRestrictedToTestNetworks) throws Exception {
+ private Ikev2VpnProfile buildIkev2VpnProfilePsk(@NonNull String remote,
+ boolean isRestrictedToTestNetworks, boolean requiresValidation) throws Exception {
final Ikev2VpnProfile.Builder builder =
new Ikev2VpnProfile.Builder(remote, TEST_IDENTITY).setAuthPsk(TEST_PSK);
-
- return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks);
+ return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks,
+ requiresValidation);
}
private Ikev2VpnProfile buildIkev2VpnProfileUsernamePassword(boolean isRestrictedToTestNetworks)
throws Exception {
+
final Ikev2VpnProfile.Builder builder =
new Ikev2VpnProfile.Builder(TEST_SERVER_ADDR_V6, TEST_IDENTITY)
.setAuthUsernamePassword(TEST_USER, TEST_PASSWORD, mServerRootCa);
-
- return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks);
+ return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks,
+ false /* requiresValidation */);
}
private Ikev2VpnProfile buildIkev2VpnProfileDigitalSignature(boolean isRestrictedToTestNetworks)
@@ -239,8 +287,8 @@
new Ikev2VpnProfile.Builder(TEST_SERVER_ADDR_V6, TEST_IDENTITY)
.setAuthDigitalSignature(
mUserCertKey.cert, mUserCertKey.key, mServerRootCa);
-
- return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks);
+ return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks,
+ false /* requiresValidation */);
}
private void checkBasicIkev2VpnProfile(@NonNull Ikev2VpnProfile profile) throws Exception {
@@ -254,12 +302,11 @@
assertFalse(profile.isRestrictedToTestNetworks());
}
- @Test
- public void testBuildIkev2VpnProfilePsk() throws Exception {
+ public void doTestBuildIkev2VpnProfilePsk(final boolean requiresValidation) throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
- final Ikev2VpnProfile profile =
- buildIkev2VpnProfilePsk(false /* isRestrictedToTestNetworks */);
+ final Ikev2VpnProfile profile = buildIkev2VpnProfilePsk(TEST_SERVER_ADDR_V6,
+ false /* isRestrictedToTestNetworks */, requiresValidation);
checkBasicIkev2VpnProfile(profile);
assertArrayEquals(TEST_PSK, profile.getPresharedKey());
@@ -270,6 +317,38 @@
assertNull(profile.getServerRootCaCert());
assertNull(profile.getRsaPrivateKey());
assertNull(profile.getUserCert());
+ if (isAtLeastT()) {
+ assertEquals(requiresValidation, profile.isInternetValidationRequired());
+ }
+ }
+
+ @IgnoreUpTo(SC_V2)
+ @Test
+ public void testBuildIkev2VpnProfileWithIkeTunnelConnectionParams() throws Exception {
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
+ assumeTrue(TestUtils.shouldTestTApis());
+
+ final IkeTunnelConnectionParams expectedParams = new IkeTunnelConnectionParams(
+ IkeSessionTestUtils.IKE_PARAMS_V6, IkeSessionTestUtils.CHILD_PARAMS);
+ final Ikev2VpnProfile.Builder ikeProfileBuilder =
+ new Ikev2VpnProfile.Builder(expectedParams);
+ // Verify the other Ike options could not be set with IkeTunnelConnectionParams.
+ final Class<IllegalArgumentException> expected = IllegalArgumentException.class;
+ assertThrows(expected, () -> ikeProfileBuilder.setAuthPsk(TEST_PSK));
+ assertThrows(expected, () ->
+ ikeProfileBuilder.setAuthUsernamePassword(TEST_USER, TEST_PASSWORD, mServerRootCa));
+ assertThrows(expected, () -> ikeProfileBuilder.setAuthDigitalSignature(
+ mUserCertKey.cert, mUserCertKey.key, mServerRootCa));
+
+ final Ikev2VpnProfile profile = ikeProfileBuilder.build();
+
+ assertEquals(expectedParams, profile.getIkeTunnelConnectionParams());
+ }
+
+ @Test
+ public void testBuildIkev2VpnProfilePsk() throws Exception {
+ doTestBuildIkev2VpnProfilePsk(true /* requiresValidation */);
+ doTestBuildIkev2VpnProfilePsk(false /* requiresValidation */);
}
@Test
@@ -316,8 +395,8 @@
setAppop(AppOpsManager.OP_ACTIVATE_VPN, hasActivateVpn);
setAppop(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, hasActivatePlatformVpn);
- final Ikev2VpnProfile profile =
- buildIkev2VpnProfilePsk(false /* isRestrictedToTestNetworks */);
+ final Ikev2VpnProfile profile = buildIkev2VpnProfilePsk(TEST_SERVER_ADDR_V6,
+ false /* isRestrictedToTestNetworks */, false /* requiresValidation */);
final Intent intent = sVpnMgr.provisionVpnProfile(profile);
assertEquals(expectIntent, intent != null);
}
@@ -360,8 +439,8 @@
setAppop(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, true);
- final Ikev2VpnProfile profile =
- buildIkev2VpnProfilePsk(false /* isRestrictedToTestNetworks */);
+ final Ikev2VpnProfile profile = buildIkev2VpnProfilePsk(TEST_SERVER_ADDR_V6,
+ false /* isRestrictedToTestNetworks */, false /* requiresValidation */);
assertNull(sVpnMgr.provisionVpnProfile(profile));
// Verify that deleting the profile works (even without the appop)
@@ -394,7 +473,9 @@
}
}
- private void checkStartStopVpnProfileBuildsNetworks(IkeTunUtils tunUtils, boolean testIpv6)
+ private void checkStartStopVpnProfileBuildsNetworks(@NonNull IkeTunUtils tunUtils,
+ boolean testIpv6, boolean requiresValidation, boolean testSessionKey,
+ boolean testIkeTunConnParams)
throws Exception {
String serverAddr = testIpv6 ? TEST_SERVER_ADDR_V6 : TEST_SERVER_ADDR_V4;
String initResp = testIpv6 ? SUCCESSFUL_IKE_INIT_RESP_V6 : SUCCESSFUL_IKE_INIT_RESP_V4;
@@ -404,11 +485,32 @@
// Requires MANAGE_TEST_NETWORKS to provision a test-mode profile.
mCtsNetUtils.setAppopPrivileged(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, true);
- final Ikev2VpnProfile profile =
- buildIkev2VpnProfilePsk(serverAddr, true /* isRestrictedToTestNetworks */);
+ final Ikev2VpnProfile profile = testIkeTunConnParams
+ ? buildIkev2VpnProfileIkeTunConnParams(true /* isRestrictedToTestNetworks */,
+ requiresValidation, testIpv6)
+ : buildIkev2VpnProfilePsk(serverAddr, true /* isRestrictedToTestNetworks */,
+ requiresValidation);
assertNull(sVpnMgr.provisionVpnProfile(profile));
- sVpnMgr.startProvisionedVpnProfile();
+ final TestableNetworkCallback cb = new TestableNetworkCallback(TIMEOUT_MS);
+ final NetworkRequest nr = new NetworkRequest.Builder()
+ .clearCapabilities().addTransportType(TRANSPORT_VPN).build();
+ registerNetworkCallback(nr, cb);
+
+ if (testSessionKey) {
+ // testSessionKey will never be true if running on <T
+ // startProvisionedVpnProfileSession() should return a non-null & non-empty random UUID.
+ final String sessionId = mVmShim.startProvisionedVpnProfileSession();
+ assertFalse(TextUtils.isEmpty(sessionId));
+ final VpnProfileStateShim profileState = mVmShim.getProvisionedVpnProfileState();
+ assertNotNull(profileState);
+ assertEquals(ConstantsShim.VPN_PROFILE_STATE_CONNECTING, profileState.getState());
+ assertEquals(sessionId, profileState.getSessionId());
+ assertFalse(profileState.isAlwaysOn());
+ assertFalse(profileState.isLockdownEnabled());
+ } else {
+ sVpnMgr.startProvisionedVpnProfile();
+ }
// Inject IKE negotiation
int expectedMsgId = 0;
@@ -418,35 +520,71 @@
HexDump.hexStringToByteArray(authResp));
// Verify the VPN network came up
- final NetworkRequest nr = new NetworkRequest.Builder()
- .clearCapabilities().addTransportType(TRANSPORT_VPN).build();
+ final Network vpnNetwork = cb.expectCallback(CallbackEntry.AVAILABLE, anyNetwork())
+ .getNetwork();
- final TestNetworkCallback cb = new TestNetworkCallback();
- sCM.requestNetwork(nr, cb);
- cb.waitForAvailable();
- final Network vpnNetwork = cb.currentNetwork;
- assertNotNull(vpnNetwork);
+ if (testSessionKey) {
+ final VpnProfileStateShim profileState = mVmShim.getProvisionedVpnProfileState();
+ assertNotNull(profileState);
+ assertEquals(ConstantsShim.VPN_PROFILE_STATE_CONNECTED, profileState.getState());
+ assertFalse(profileState.isAlwaysOn());
+ assertFalse(profileState.isLockdownEnabled());
+ }
- final NetworkCapabilities caps = sCM.getNetworkCapabilities(vpnNetwork);
- assertTrue(caps.hasTransport(TRANSPORT_VPN));
- assertTrue(caps.hasCapability(NET_CAPABILITY_INTERNET));
- assertEquals(Process.myUid(), caps.getOwnerUid());
+ cb.expectCapabilitiesThat(vpnNetwork, TIMEOUT_MS,
+ caps -> caps.hasTransport(TRANSPORT_VPN)
+ && caps.hasCapability(NET_CAPABILITY_INTERNET)
+ && !caps.hasCapability(NET_CAPABILITY_VALIDATED)
+ && Process.myUid() == caps.getOwnerUid());
+ cb.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, vpnNetwork);
+ cb.expectCallback(CallbackEntry.BLOCKED_STATUS, vpnNetwork);
+
+ // A VPN that requires validation is initially not validated, while one that doesn't
+ // immediately validate automatically. Because this VPN can't actually access Internet,
+ // the VPN only validates if it doesn't require validation. If the VPN requires validation
+ // but unexpectedly sends this callback, expecting LOST below will fail because the next
+ // callback will be the validated capabilities instead.
+ // In S and below, |requiresValidation| is ignored, so this callback is always sent
+ // regardless of its value. However, there is a race in Vpn(see b/228574221) that VPN may
+ // misuse VPN network itself as the underlying network. The fix is not available without
+ // SDK > T platform. Thus, verify this only on T+ platform.
+ if (!requiresValidation && TestUtils.shouldTestTApis()) {
+ cb.eventuallyExpect(CallbackEntry.NETWORK_CAPS_UPDATED, TIMEOUT_MS,
+ entry -> ((CallbackEntry.CapabilitiesChanged) entry).getCaps()
+ .hasCapability(NET_CAPABILITY_VALIDATED));
+ }
sVpnMgr.stopProvisionedVpnProfile();
- cb.waitForLost();
- assertEquals(vpnNetwork, cb.lastLostNetwork);
+ // Using expectCallback may cause the test to be flaky since test may receive other
+ // callbacks such as linkproperties change.
+ cb.eventuallyExpect(CallbackEntry.LOST, TIMEOUT_MS,
+ lost -> vpnNetwork.equals(lost.getNetwork()));
+ }
+
+ private void registerNetworkCallback(NetworkRequest request, TestableNetworkCallback callback) {
+ sCM.registerNetworkCallback(request, callback);
+ mCallbacksToUnregister.add(callback);
}
private class VerifyStartStopVpnProfileTest implements TestNetworkRunnable.Test {
private final boolean mTestIpv6Only;
+ private final boolean mRequiresValidation;
+ private final boolean mTestSessionKey;
+ private final boolean mTestIkeTunConnParams;
/**
* Constructs the test
*
* @param testIpv6Only if true, builds a IPv6-only test; otherwise builds a IPv4-only test
+ * @param requiresValidation whether this VPN should request platform validation
+ * @param testSessionKey if true, start VPN by calling startProvisionedVpnProfileSession()
*/
- VerifyStartStopVpnProfileTest(boolean testIpv6Only) {
+ VerifyStartStopVpnProfileTest(boolean testIpv6Only, boolean requiresValidation,
+ boolean testSessionKey, boolean testIkeTunConnParams) {
mTestIpv6Only = testIpv6Only;
+ mRequiresValidation = requiresValidation;
+ mTestSessionKey = testSessionKey;
+ mTestIkeTunConnParams = testIkeTunConnParams;
}
@Override
@@ -454,7 +592,8 @@
throws Exception {
final IkeTunUtils tunUtils = new IkeTunUtils(testIface.getFileDescriptor());
- checkStartStopVpnProfileBuildsNetworks(tunUtils, mTestIpv6Only);
+ checkStartStopVpnProfileBuildsNetworks(tunUtils, mTestIpv6Only, mRequiresValidation,
+ mTestSessionKey, mTestIkeTunConnParams);
}
@Override
@@ -472,22 +611,83 @@
}
}
- @Test
- public void testStartStopVpnProfileV4() throws Exception {
+ private void doTestStartStopVpnProfile(boolean testIpv6Only, boolean requiresValidation,
+ boolean testSessionKey, boolean testIkeTunConnParams) throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
-
// Requires shell permission to update appops.
runWithShellPermissionIdentity(
- new TestNetworkRunnable(new VerifyStartStopVpnProfileTest(false)));
+ new TestNetworkRunnable(new VerifyStartStopVpnProfileTest(
+ testIpv6Only, requiresValidation, testSessionKey , testIkeTunConnParams)));
+ }
+
+ @Test
+ public void testStartStopVpnProfileV4() throws Exception {
+ doTestStartStopVpnProfile(false /* testIpv6Only */, false /* requiresValidation */,
+ false /* testSessionKey */, false /* testIkeTunConnParams */);
+ }
+
+ @Test @IgnoreUpTo(SC_V2)
+ public void testStartStopVpnProfileV4WithValidation() throws Exception {
+ assumeTrue(TestUtils.shouldTestTApis());
+ doTestStartStopVpnProfile(false /* testIpv6Only */, true /* requiresValidation */,
+ false /* testSessionKey */, false /* testIkeTunConnParams */);
}
@Test
public void testStartStopVpnProfileV6() throws Exception {
- assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
+ doTestStartStopVpnProfile(true /* testIpv6Only */, false /* requiresValidation */,
+ false /* testSessionKey */, false /* testIkeTunConnParams */);
+ }
- // Requires shell permission to update appops.
- runWithShellPermissionIdentity(
- new TestNetworkRunnable(new VerifyStartStopVpnProfileTest(true)));
+ @Test @IgnoreUpTo(SC_V2)
+ public void testStartStopVpnProfileV6WithValidation() throws Exception {
+ assumeTrue(TestUtils.shouldTestTApis());
+ doTestStartStopVpnProfile(true /* testIpv6Only */, true /* requiresValidation */,
+ false /* testSessionKey */, false /* testIkeTunConnParams */);
+ }
+
+ @Test @IgnoreUpTo(SC_V2)
+ public void testStartStopVpnProfileIkeTunConnParamsV4() throws Exception {
+ assumeTrue(TestUtils.shouldTestTApis());
+ doTestStartStopVpnProfile(false /* testIpv6Only */, false /* requiresValidation */,
+ false /* testSessionKey */, true /* testIkeTunConnParams */);
+ }
+
+ @Test @IgnoreUpTo(SC_V2)
+ public void testStartStopVpnProfileIkeTunConnParamsV4WithValidation() throws Exception {
+ assumeTrue(TestUtils.shouldTestTApis());
+ doTestStartStopVpnProfile(false /* testIpv6Only */, true /* requiresValidation */,
+ false /* testSessionKey */, true /* testIkeTunConnParams */);
+ }
+
+ @Test @IgnoreUpTo(SC_V2)
+ public void testStartStopVpnProfileIkeTunConnParamsV6() throws Exception {
+ assumeTrue(TestUtils.shouldTestTApis());
+ doTestStartStopVpnProfile(true /* testIpv6Only */, false /* requiresValidation */,
+ false /* testSessionKey */, true /* testIkeTunConnParams */);
+ }
+
+ @Test @IgnoreUpTo(SC_V2)
+ public void testStartStopVpnProfileIkeTunConnParamsV6WithValidation() throws Exception {
+ assumeTrue(TestUtils.shouldTestTApis());
+ doTestStartStopVpnProfile(true /* testIpv6Only */, true /* requiresValidation */,
+ false /* testSessionKey */, true /* testIkeTunConnParams */);
+ }
+
+ @IgnoreUpTo(SC_V2)
+ @Test
+ public void testStartProvisionedVpnV4ProfileSession() throws Exception {
+ assumeTrue(TestUtils.shouldTestTApis());
+ doTestStartStopVpnProfile(false /* testIpv6Only */, false /* requiresValidation */,
+ true /* testSessionKey */, false /* testIkeTunConnParams */);
+ }
+
+ @IgnoreUpTo(SC_V2)
+ @Test
+ public void testStartProvisionedVpnV6ProfileSession() throws Exception {
+ assumeTrue(TestUtils.shouldTestTApis());
+ doTestStartStopVpnProfile(true /* testIpv6Only */, false /* requiresValidation */,
+ true /* testSessionKey */, false /* testIkeTunConnParams */);
}
private static class CertificateAndKey {
diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index 53b00db..d4f3d57 100644
--- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -72,6 +72,7 @@
import android.os.HandlerThread
import android.os.Message
import android.os.SystemClock
+import android.platform.test.annotations.AppModeFull
import android.telephony.TelephonyManager
import android.telephony.data.EpsBearerQosSessionAttributes
import android.util.DebugUtils.valueToString
@@ -106,7 +107,6 @@
import com.android.testutils.TestableNetworkCallback
import com.android.testutils.assertThrows
import org.junit.After
-import org.junit.Assume.assumeFalse
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -946,11 +946,9 @@
return Pair(agent, qosTestSocket!!)
}
+ @AppModeFull(reason = "Instant apps don't have permission to bind sockets.")
@Test
fun testQosCallbackRegisterWithUnregister() {
- // Instant apps can't bind sockets to localhost
- // TODO: use @AppModeFull when supported by DevSdkIgnoreRunner
- assumeFalse(realContext.packageManager.isInstantApp())
val (agent, socket) = setupForQosCallbackTesting()
val qosCallback = TestableQosCallback()
@@ -975,11 +973,9 @@
}
}
+ @AppModeFull(reason = "Instant apps don't have permission to bind sockets.")
@Test
fun testQosCallbackOnQosSession() {
- // Instant apps can't bind sockets to localhost
- // TODO: use @AppModeFull when supported by DevSdkIgnoreRunner
- assumeFalse(realContext.packageManager.isInstantApp())
val (agent, socket) = setupForQosCallbackTesting()
val qosCallback = TestableQosCallback()
Executors.newSingleThreadExecutor().let { executor ->
@@ -1023,11 +1019,9 @@
}
}
+ @AppModeFull(reason = "Instant apps don't have permission to bind sockets.")
@Test
fun testQosCallbackOnError() {
- // Instant apps can't bind sockets to localhost
- // TODO: use @AppModeFull when supported by DevSdkIgnoreRunner
- assumeFalse(realContext.packageManager.isInstantApp())
val (agent, socket) = setupForQosCallbackTesting()
val qosCallback = TestableQosCallback()
Executors.newSingleThreadExecutor().let { executor ->
@@ -1064,11 +1058,9 @@
}
}
+ @AppModeFull(reason = "Instant apps don't have permission to bind sockets.")
@Test
fun testQosCallbackIdsAreMappedCorrectly() {
- // Instant apps can't bind sockets to localhost
- // TODO: use @AppModeFull when supported by DevSdkIgnoreRunner
- assumeFalse(realContext.packageManager.isInstantApp())
val (agent, socket) = setupForQosCallbackTesting()
val qosCallback1 = TestableQosCallback()
val qosCallback2 = TestableQosCallback()
@@ -1107,11 +1099,9 @@
}
}
+ @AppModeFull(reason = "Instant apps don't have permission to bind sockets.")
@Test
fun testQosCallbackWhenNetworkReleased() {
- // Instant apps can't bind sockets to localhost
- // TODO: use @AppModeFull when supported by DevSdkIgnoreRunner
- assumeFalse(realContext.packageManager.isInstantApp())
val (agent, socket) = setupForQosCallbackTesting()
Executors.newSingleThreadExecutor().let { executor ->
try {
@@ -1151,8 +1141,9 @@
)
}
+ @AppModeFull(reason = "Instant apps don't have permission to bind sockets.")
@Test
- fun testDestroyAndAwaitReplacement() {
+ fun testUnregisterAfterReplacement() {
// Keeps an eye on all test networks.
val matchAllCallback = TestableNetworkCallback(timeoutMs = DEFAULT_TIMEOUT_MS)
registerNetworkCallback(makeTestNetworkRequest(), matchAllCallback)
@@ -1180,15 +1171,15 @@
// Mark the first network as awaiting replacement. This should destroy the underlying
// native network and send onNetworkDestroyed, but will not send any NetworkCallbacks,
// because for callback and scoring purposes network1 is still connected.
- agent1.destroyAndAwaitReplacement(5_000 /* timeoutMillis */)
+ agent1.unregisterAfterReplacement(5_000 /* timeoutMillis */)
agent1.expectCallback<OnNetworkDestroyed>()
assertThrows(IOException::class.java) { network1.bindSocket(DatagramSocket()) }
assertNotNull(mCM.getLinkProperties(network1))
- // Calling destroyAndAwaitReplacement more than once has no effect.
+ // Calling unregisterAfterReplacement more than once has no effect.
// If it did, this test would fail because the 1ms timeout means that the network would be
// torn down before the replacement arrives.
- agent1.destroyAndAwaitReplacement(1 /* timeoutMillis */)
+ agent1.unregisterAfterReplacement(1 /* timeoutMillis */)
// Connect a third network. Because network1 is awaiting replacement, network3 is preferred
// as soon as it validates (until then, it is outscored by network1).
@@ -1216,14 +1207,14 @@
matchAllCallback.expectCallback<Losing>(network3)
testCallback.expectAvailableCallbacks(network4, validated = true)
mCM.unregisterNetworkCallback(agent4callback)
- agent3.destroyAndAwaitReplacement(5_000)
+ agent3.unregisterAfterReplacement(5_000)
agent3.expectCallback<OnNetworkUnwanted>()
matchAllCallback.expectCallback<Lost>(network3, 1000L)
agent3.expectCallback<OnNetworkDestroyed>()
// Now mark network4 awaiting replacement with a low timeout, and check that if no
// replacement arrives, it is torn down.
- agent4.destroyAndAwaitReplacement(100 /* timeoutMillis */)
+ agent4.unregisterAfterReplacement(100 /* timeoutMillis */)
matchAllCallback.expectCallback<Lost>(network4, 1000L /* timeoutMs */)
testCallback.expectCallback<Lost>(network4, 1000L /* timeoutMs */)
agent4.expectCallback<OnNetworkDestroyed>()
@@ -1234,7 +1225,7 @@
val (agent5, network5) = connectNetwork()
matchAllCallback.expectAvailableThenValidatedCallbacks(network5)
testCallback.expectAvailableThenValidatedCallbacks(network5)
- agent5.destroyAndAwaitReplacement(5_000 /* timeoutMillis */)
+ agent5.unregisterAfterReplacement(5_000 /* timeoutMillis */)
agent5.unregister()
matchAllCallback.expectCallback<Lost>(network5, 1000L /* timeoutMs */)
testCallback.expectCallback<Lost>(network5, 1000L /* timeoutMs */)
@@ -1257,7 +1248,7 @@
it.hasCapability(NET_CAPABILITY_VALIDATED)
}
- wifiAgent.destroyAndAwaitReplacement(5_000 /* timeoutMillis */)
+ wifiAgent.unregisterAfterReplacement(5_000 /* timeoutMillis */)
wifiAgent.expectCallback<OnNetworkDestroyed>()
// Once the network is awaiting replacement, changing LinkProperties, NetworkCapabilities or
@@ -1284,4 +1275,23 @@
matchAllCallback.expectCallback<Lost>(wifiNetwork)
wifiAgent.expectCallback<OnNetworkUnwanted>()
}
+
+ @Test
+ fun testUnregisterAgentBeforeAgentFullyConnected() {
+ val specifier = UUID.randomUUID().toString()
+ val callback = TestableNetworkCallback()
+ val transports = intArrayOf(TRANSPORT_CELLULAR)
+ // Ensure this NetworkAgent is never unneeded by filing a request with its specifier.
+ requestNetwork(makeTestNetworkRequest(specifier = specifier), callback)
+ val nc = makeTestNetworkCapabilities(specifier, transports)
+ val agent = createNetworkAgent(realContext, initialNc = nc)
+ // Connect the agent
+ agent.register()
+ // Mark agent connected then unregister agent immediately. Verify that both available and
+ // lost callback should be sent still.
+ agent.markConnected()
+ agent.unregister()
+ callback.expectCallback<Available>(agent.network!!)
+ callback.eventuallyExpect<Lost> { it.network == agent.network }
+ }
}
diff --git a/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java b/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
index fb720a7..f86c5cd 100644
--- a/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
@@ -29,8 +29,20 @@
import static android.app.usage.NetworkStats.Bucket.STATE_FOREGROUND;
import static android.app.usage.NetworkStats.Bucket.TAG_NONE;
import static android.app.usage.NetworkStats.Bucket.UID_ALL;
+import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID;
+import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID_TAG;
+import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_XT;
+
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import android.app.AppOpsManager;
+import android.app.Instrumentation;
import android.app.usage.NetworkStats;
import android.app.usage.NetworkStatsManager;
import android.content.Context;
@@ -40,7 +52,11 @@
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkRequest;
+import android.net.NetworkStatsCollection;
+import android.net.NetworkStatsHistory;
import android.net.TrafficStats;
+import android.net.netstats.NetworkStatsDataMigrationUtils;
+import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Process;
@@ -48,11 +64,23 @@
import android.os.SystemClock;
import android.platform.test.annotations.AppModeFull;
import android.telephony.TelephonyManager;
-import android.test.InstrumentationTestCase;
+import android.text.TextUtils;
import android.util.Log;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
import com.android.compatibility.common.util.ShellIdentityUtils;
import com.android.compatibility.common.util.SystemUtil;
+import com.android.modules.utils.build.SdkLevel;
+import com.android.testutils.ConnectivityModuleTest;
+import com.android.testutils.DevSdkIgnoreRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.io.IOException;
import java.io.InputStream;
@@ -62,8 +90,18 @@
import java.net.UnknownHostException;
import java.text.MessageFormat;
import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
-public class NetworkStatsManagerTest extends InstrumentationTestCase {
+@ConnectivityModuleTest
+@AppModeFull(reason = "instant apps cannot be granted USAGE_STATS")
+@RunWith(AndroidJUnit4.class)
+public class NetworkStatsManagerTest {
+ @Rule
+ public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule(Build.VERSION_CODES.Q);
+
private static final String LOG_TAG = "NetworkStatsManagerTest";
private static final String APPOPS_SET_SHELL_COMMAND = "appops set {0} {1} {2}";
private static final String APPOPS_GET_SHELL_COMMAND = "appops get {0} {1}";
@@ -164,9 +202,11 @@
};
private String mPkg;
+ private Context mContext;
private NetworkStatsManager mNsm;
private ConnectivityManager mCm;
private PackageManager mPm;
+ private Instrumentation mInstrumentation;
private long mStartTime;
private long mEndTime;
@@ -224,44 +264,40 @@
}
}
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mNsm = (NetworkStatsManager) getInstrumentation().getContext()
- .getSystemService(Context.NETWORK_STATS_SERVICE);
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getContext();
+ mNsm = mContext.getSystemService(NetworkStatsManager.class);
mNsm.setPollForce(true);
- mCm = (ConnectivityManager) getInstrumentation().getContext()
- .getSystemService(Context.CONNECTIVITY_SERVICE);
+ mCm = mContext.getSystemService(ConnectivityManager.class);
+ mPm = mContext.getPackageManager();
+ mPkg = mContext.getPackageName();
- mPm = getInstrumentation().getContext().getPackageManager();
-
- mPkg = getInstrumentation().getContext().getPackageName();
-
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
mWriteSettingsMode = getAppOpsMode(AppOpsManager.OPSTR_WRITE_SETTINGS);
setAppOpsMode(AppOpsManager.OPSTR_WRITE_SETTINGS, "allow");
mUsageStatsMode = getAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS);
}
- @Override
- protected void tearDown() throws Exception {
+ @After
+ public void tearDown() throws Exception {
if (mWriteSettingsMode != null) {
setAppOpsMode(AppOpsManager.OPSTR_WRITE_SETTINGS, mWriteSettingsMode);
}
if (mUsageStatsMode != null) {
setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, mUsageStatsMode);
}
- super.tearDown();
}
private void setAppOpsMode(String appop, String mode) throws Exception {
final String command = MessageFormat.format(APPOPS_SET_SHELL_COMMAND, mPkg, appop, mode);
- SystemUtil.runShellCommand(command);
+ SystemUtil.runShellCommand(mInstrumentation, command);
}
private String getAppOpsMode(String appop) throws Exception {
final String command = MessageFormat.format(APPOPS_GET_SHELL_COMMAND, mPkg, appop);
- String result = SystemUtil.runShellCommand(command);
+ String result = SystemUtil.runShellCommand(mInstrumentation, command);
if (result == null) {
Log.w(LOG_TAG, "App op " + appop + " could not be read.");
}
@@ -269,7 +305,7 @@
}
private boolean isInForeground() throws IOException {
- String result = SystemUtil.runShellCommand(getInstrumentation(),
+ String result = SystemUtil.runShellCommand(mInstrumentation,
"cmd activity get-uid-state " + Process.myUid());
return result.contains("FOREGROUND");
}
@@ -366,15 +402,14 @@
private String getSubscriberId(int networkIndex) {
int networkType = mNetworkInterfacesToTest[networkIndex].getNetworkType();
if (ConnectivityManager.TYPE_MOBILE == networkType) {
- TelephonyManager tm = (TelephonyManager) getInstrumentation().getContext()
- .getSystemService(Context.TELEPHONY_SERVICE);
+ TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
return ShellIdentityUtils.invokeMethodWithShellPermissions(tm,
(telephonyManager) -> telephonyManager.getSubscriberId());
}
return "";
}
- @AppModeFull
+ @Test
public void testDeviceSummary() throws Exception {
for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
if (!shouldTestThisNetworkType(i, MINUTE / 2)) {
@@ -410,7 +445,7 @@
}
}
- @AppModeFull
+ @Test
public void testUserSummary() throws Exception {
for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
if (!shouldTestThisNetworkType(i, MINUTE / 2)) {
@@ -446,7 +481,7 @@
}
}
- @AppModeFull
+ @Test
public void testAppSummary() throws Exception {
for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
// Use tolerance value that large enough to make sure stats of at
@@ -522,7 +557,7 @@
}
}
- @AppModeFull
+ @Test
public void testAppDetails() throws Exception {
for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
// Relatively large tolerance to accommodate for history bucket size.
@@ -565,7 +600,7 @@
}
}
- @AppModeFull
+ @Test
public void testUidDetails() throws Exception {
for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
// Relatively large tolerance to accommodate for history bucket size.
@@ -619,7 +654,7 @@
}
}
- @AppModeFull
+ @Test
public void testTagDetails() throws Exception {
for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
// Relatively large tolerance to accommodate for history bucket size.
@@ -726,7 +761,7 @@
bucket.getRxBytes(), bucket.getTxBytes()));
}
- @AppModeFull
+ @Test
public void testUidTagStateDetails() throws Exception {
for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
// Relatively large tolerance to accommodate for history bucket size.
@@ -803,7 +838,7 @@
}
}
- @AppModeFull
+ @Test
public void testCallback() throws Exception {
for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
// Relatively large tolerance to accommodate for history bucket size.
@@ -825,6 +860,43 @@
// storing files of >2MB in CTS.
mNsm.unregisterUsageCallback(usageCallback);
+
+ // For T- devices, the registerUsageCallback invocation below will need a looper
+ // from the thread that calls into the API, which is not available in the test.
+ if (SdkLevel.isAtLeastT()) {
+ mNsm.registerUsageCallback(mNetworkInterfacesToTest[i].getNetworkType(),
+ getSubscriberId(i), THRESHOLD_BYTES, usageCallback);
+ mNsm.unregisterUsageCallback(usageCallback);
+ }
+ }
+ }
+
+ @DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
+ @Test
+ public void testDataMigrationUtils() throws Exception {
+ final List<String> prefixes = List.of(PREFIX_UID, PREFIX_XT, PREFIX_UID_TAG);
+ for (final String prefix : prefixes) {
+ final long duration = TextUtils.equals(PREFIX_XT, prefix) ? TimeUnit.HOURS.toMillis(1)
+ : TimeUnit.HOURS.toMillis(2);
+
+ final NetworkStatsCollection collection =
+ NetworkStatsDataMigrationUtils.readPlatformCollection(prefix, duration);
+
+ final long now = System.currentTimeMillis();
+ final Set<Map.Entry<NetworkStatsCollection.Key, NetworkStatsHistory>> entries =
+ collection.getEntries().entrySet();
+ for (final Map.Entry<NetworkStatsCollection.Key, NetworkStatsHistory> entry : entries) {
+ for (final NetworkStatsHistory.Entry historyEntry : entry.getValue().getEntries()) {
+ // Verify all value fields are reasonable.
+ assertTrue(historyEntry.getBucketStart() <= now);
+ assertTrue(historyEntry.getActiveTime() <= duration);
+ assertTrue(historyEntry.getRxBytes() >= 0);
+ assertTrue(historyEntry.getRxPackets() >= 0);
+ assertTrue(historyEntry.getTxBytes() >= 0);
+ assertTrue(historyEntry.getTxPackets() >= 0);
+ assertTrue(historyEntry.getOperations() >= 0);
+ }
+ }
}
}
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index 9506081..64cc97d 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -22,6 +22,7 @@
import android.net.Network
import android.net.NetworkAgentConfig
import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED
import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED
import android.net.NetworkCapabilities.TRANSPORT_TEST
import android.net.NetworkRequest
@@ -45,17 +46,16 @@
import android.net.nsd.NsdManager.RegistrationListener
import android.net.nsd.NsdManager.ResolveListener
import android.net.nsd.NsdServiceInfo
+import android.os.Handler
import android.os.HandlerThread
+import android.os.Process.myTid
import android.platform.test.annotations.AppModeFull
import android.util.Log
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.runner.AndroidJUnit4
import com.android.net.module.util.ArrayTrackRecord
import com.android.net.module.util.TrackRecord
-import com.android.networkstack.apishim.ConstantsShim
import com.android.networkstack.apishim.NsdShimImpl
-import com.android.testutils.DevSdkIgnoreRule
-import com.android.testutils.SC_V2
import com.android.testutils.TestableNetworkAgent
import com.android.testutils.TestableNetworkCallback
import com.android.testutils.runAsShell
@@ -65,12 +65,12 @@
import org.junit.Assert.assertTrue
import org.junit.Assume.assumeTrue
import org.junit.Before
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import java.net.ServerSocket
import java.nio.charset.StandardCharsets
import java.util.Random
+import java.util.concurrent.Executor
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertNotNull
@@ -81,6 +81,7 @@
private const val TAG = "NsdManagerTest"
private const val SERVICE_TYPE = "_nmt._tcp"
private const val TIMEOUT_MS = 2000L
+private const val NO_CALLBACK_TIMEOUT_MS = 200L
private const val DBG = false
private val nsdShim = NsdShimImpl.newInstance()
@@ -88,10 +89,6 @@
@AppModeFull(reason = "Socket cannot bind in instant app mode")
@RunWith(AndroidJUnit4::class)
class NsdManagerTest {
- // NsdManager is not updatable before S, so tests do not need to be backwards compatible
- @get:Rule
- val ignoreRule = DevSdkIgnoreRule(ignoreClassUpTo = SC_V2)
-
private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
private val nsdManager by lazy { context.getSystemService(NsdManager::class.java) }
@@ -117,12 +114,20 @@
private interface NsdEvent
private open class NsdRecord<T : NsdEvent> private constructor(
- private val history: ArrayTrackRecord<T>
+ private val history: ArrayTrackRecord<T>,
+ private val expectedThreadId: Int? = null
) : TrackRecord<T> by history {
- constructor() : this(ArrayTrackRecord())
+ constructor(expectedThreadId: Int? = null) : this(ArrayTrackRecord(), expectedThreadId)
val nextEvents = history.newReadHead()
+ override fun add(e: T): Boolean {
+ if (expectedThreadId != null) {
+ assertEquals(expectedThreadId, myTid(), "Callback is running on the wrong thread")
+ }
+ return history.add(e)
+ }
+
inline fun <reified V : NsdEvent> expectCallbackEventually(
crossinline predicate: (V) -> Boolean = { true }
): V = nextEvents.poll(TIMEOUT_MS) { e -> e is V && predicate(e) } as V?
@@ -135,10 +140,15 @@
nextEvent.javaClass.simpleName)
return nextEvent
}
+
+ inline fun assertNoCallback(timeoutMs: Long = NO_CALLBACK_TIMEOUT_MS) {
+ val cb = nextEvents.poll(timeoutMs)
+ assertNull(cb, "Expected no callback but got $cb")
+ }
}
- private class NsdRegistrationRecord : RegistrationListener,
- NsdRecord<NsdRegistrationRecord.RegistrationEvent>() {
+ private class NsdRegistrationRecord(expectedThreadId: Int? = null) : RegistrationListener,
+ NsdRecord<NsdRegistrationRecord.RegistrationEvent>(expectedThreadId) {
sealed class RegistrationEvent : NsdEvent {
abstract val serviceInfo: NsdServiceInfo
@@ -175,8 +185,8 @@
}
}
- private class NsdDiscoveryRecord : DiscoveryListener,
- NsdRecord<NsdDiscoveryRecord.DiscoveryEvent>() {
+ private class NsdDiscoveryRecord(expectedThreadId: Int? = null) :
+ DiscoveryListener, NsdRecord<NsdDiscoveryRecord.DiscoveryEvent>(expectedThreadId) {
sealed class DiscoveryEvent : NsdEvent {
data class StartDiscoveryFailed(val serviceType: String, val errorCode: Int)
: DiscoveryEvent()
@@ -248,9 +258,11 @@
fun setUp() {
handlerThread.start()
- runAsShell(MANAGE_TEST_NETWORKS) {
- testNetwork1 = createTestNetwork()
- testNetwork2 = createTestNetwork()
+ if (TestUtils.shouldTestTApis()) {
+ runAsShell(MANAGE_TEST_NETWORKS) {
+ testNetwork1 = createTestNetwork()
+ testNetwork2 = createTestNetwork()
+ }
}
}
@@ -289,9 +301,11 @@
@After
fun tearDown() {
- runAsShell(MANAGE_TEST_NETWORKS) {
- testNetwork1.close(cm)
- testNetwork2.close(cm)
+ if (TestUtils.shouldTestTApis()) {
+ runAsShell(MANAGE_TEST_NETWORKS) {
+ testNetwork1.close(cm)
+ testNetwork2.close(cm)
+ }
}
handlerThread.quitSafely()
}
@@ -342,9 +356,12 @@
if (DBG) Log.d(TAG, "Port = $localPort")
val registrationRecord = NsdRegistrationRecord()
- val registeredInfo = registerService(registrationRecord, si)
+ // Test registering without an Executor
+ nsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, registrationRecord)
+ val registeredInfo = registrationRecord.expectCallback<ServiceRegistered>().serviceInfo
val discoveryRecord = NsdDiscoveryRecord()
+ // Test discovering without an Executor
nsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryRecord)
// Expect discovery started
@@ -353,7 +370,10 @@
// Expect a service record to be discovered
val foundInfo = discoveryRecord.waitForServiceDiscovered(registeredInfo.serviceName)
- val resolvedService = resolveService(foundInfo)
+ // Test resolving without an Executor
+ val resolveRecord = NsdResolveRecord()
+ nsdManager.resolveService(foundInfo, resolveRecord)
+ val resolvedService = resolveRecord.expectCallback<ServiceResolved>().serviceInfo
// Check Txt attributes
assertEquals(8, resolvedService.attributes.size)
@@ -386,14 +406,17 @@
si2.serviceName = serviceName
si2.port = localPort
val registrationRecord2 = NsdRegistrationRecord()
- val registeredInfo2 = registerService(registrationRecord2, si2)
+ nsdManager.registerService(si2, NsdManager.PROTOCOL_DNS_SD, registrationRecord2)
+ val registeredInfo2 = registrationRecord2.expectCallback<ServiceRegistered>().serviceInfo
// Expect a service record to be discovered (and filter the ones
// that are unrelated to this test)
val foundInfo2 = discoveryRecord.waitForServiceDiscovered(registeredInfo2.serviceName)
// Resolve the service
- val resolvedService2 = resolveService(foundInfo2)
+ val resolveRecord2 = NsdResolveRecord()
+ nsdManager.resolveService(foundInfo2, resolveRecord2)
+ val resolvedService2 = resolveRecord2.expectCallback<ServiceResolved>().serviceInfo
// Check that the resolved service doesn't have any TXT records
assertEquals(0, resolvedService2.attributes.size)
@@ -408,8 +431,8 @@
@Test
fun testNsdManager_DiscoverOnNetwork() {
- // This tests requires shims supporting T+ APIs (discovering on specific network)
- assumeTrue(ConstantsShim.VERSION > SC_V2)
+ // This test requires shims supporting T+ APIs (discovering on specific network)
+ assumeTrue(TestUtils.shouldTestTApis())
val si = NsdServiceInfo()
si.serviceType = SERVICE_TYPE
@@ -422,7 +445,7 @@
tryTest {
val discoveryRecord = NsdDiscoveryRecord()
nsdShim.discoverServices(nsdManager, SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD,
- testNetwork1.network, discoveryRecord)
+ testNetwork1.network, Executor { it.run() }, discoveryRecord)
val foundInfo = discoveryRecord.waitForServiceDiscovered(
serviceName, testNetwork1.network)
@@ -442,17 +465,20 @@
@Test
fun testNsdManager_DiscoverWithNetworkRequest() {
- // This tests requires shims supporting T+ APIs (discovering on network request)
- assumeTrue(ConstantsShim.VERSION > SC_V2)
+ // This test requires shims supporting T+ APIs (discovering on network request)
+ assumeTrue(TestUtils.shouldTestTApis())
val si = NsdServiceInfo()
si.serviceType = SERVICE_TYPE
si.serviceName = this.serviceName
si.port = 12345 // Test won't try to connect so port does not matter
- val registrationRecord = NsdRegistrationRecord()
- val registeredInfo1 = registerService(registrationRecord, si)
- val discoveryRecord = NsdDiscoveryRecord()
+ val handler = Handler(handlerThread.looper)
+ val executor = Executor { handler.post(it) }
+
+ val registrationRecord = NsdRegistrationRecord(expectedThreadId = handlerThread.threadId)
+ val registeredInfo1 = registerService(registrationRecord, si, executor)
+ val discoveryRecord = NsdDiscoveryRecord(expectedThreadId = handlerThread.threadId)
tryTest {
val specifier = TestNetworkSpecifier(testNetwork1.iface.interfaceName)
@@ -462,7 +488,7 @@
.addTransportType(TRANSPORT_TEST)
.setNetworkSpecifier(specifier)
.build(),
- discoveryRecord)
+ executor, discoveryRecord)
val discoveryStarted = discoveryRecord.expectCallback<DiscoveryStarted>()
assertEquals(SERVICE_TYPE, discoveryStarted.serviceType)
@@ -478,7 +504,7 @@
assertEquals(testNetwork1.network, nsdShim.getNetwork(serviceLost1.serviceInfo))
registrationRecord.expectCallback<ServiceUnregistered>()
- val registeredInfo2 = registerService(registrationRecord, si)
+ val registeredInfo2 = registerService(registrationRecord, si, executor)
val serviceDiscovered2 = discoveryRecord.expectCallback<ServiceFound>()
assertEquals(registeredInfo2.serviceName, serviceDiscovered2.serviceInfo.serviceName)
assertEquals(testNetwork1.network, nsdShim.getNetwork(serviceDiscovered2.serviceInfo))
@@ -506,9 +532,42 @@
}
@Test
+ fun testNsdManager_DiscoverWithNetworkRequest_NoMatchingNetwork() {
+ // This test requires shims supporting T+ APIs (discovering on network request)
+ assumeTrue(TestUtils.shouldTestTApis())
+
+ val si = NsdServiceInfo()
+ si.serviceType = SERVICE_TYPE
+ si.serviceName = this.serviceName
+ si.port = 12345 // Test won't try to connect so port does not matter
+
+ val handler = Handler(handlerThread.looper)
+ val executor = Executor { handler.post(it) }
+
+ val discoveryRecord = NsdDiscoveryRecord(expectedThreadId = handlerThread.threadId)
+ val specifier = TestNetworkSpecifier(testNetwork1.iface.interfaceName)
+
+ tryTest {
+ nsdShim.discoverServices(nsdManager, SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD,
+ NetworkRequest.Builder()
+ .removeCapability(NET_CAPABILITY_TRUSTED)
+ .addTransportType(TRANSPORT_TEST)
+ // Specified network does not have this capability
+ .addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)
+ .setNetworkSpecifier(specifier)
+ .build(),
+ executor, discoveryRecord)
+ discoveryRecord.expectCallback<DiscoveryStarted>()
+ } cleanup {
+ nsdManager.stopServiceDiscovery(discoveryRecord)
+ discoveryRecord.expectCallback<DiscoveryStopped>()
+ }
+ }
+
+ @Test
fun testNsdManager_ResolveOnNetwork() {
- // This tests requires shims supporting T+ APIs (NsdServiceInfo.network)
- assumeTrue(ConstantsShim.VERSION > SC_V2)
+ // This test requires shims supporting T+ APIs (NsdServiceInfo.network)
+ assumeTrue(TestUtils.shouldTestTApis())
val si = NsdServiceInfo()
si.serviceType = SERVICE_TYPE
@@ -532,7 +591,7 @@
serviceName, testNetwork2.network)
assertEquals(testNetwork2.network, nsdShim.getNetwork(foundInfo2))
- nsdManager.resolveService(foundInfo1, resolveRecord)
+ nsdShim.resolveService(nsdManager, foundInfo1, Executor { it.run() }, resolveRecord)
val cb = resolveRecord.expectCallback<ServiceResolved>()
cb.serviceInfo.let {
// Resolved service type has leading dot
@@ -549,11 +608,99 @@
}
}
+ @Test
+ fun testNsdManager_RegisterOnNetwork() {
+ // This test requires shims supporting T+ APIs (NsdServiceInfo.network)
+ assumeTrue(TestUtils.shouldTestTApis())
+
+ val si = NsdServiceInfo()
+ si.serviceType = SERVICE_TYPE
+ si.serviceName = this.serviceName
+ si.network = testNetwork1.network
+ si.port = 12345 // Test won't try to connect so port does not matter
+
+ // Register service on testNetwork1
+ val registrationRecord = NsdRegistrationRecord()
+ registerService(registrationRecord, si)
+ val discoveryRecord = NsdDiscoveryRecord()
+ val discoveryRecord2 = NsdDiscoveryRecord()
+ val discoveryRecord3 = NsdDiscoveryRecord()
+
+ tryTest {
+ // Discover service on testNetwork1.
+ nsdShim.discoverServices(nsdManager, SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD,
+ testNetwork1.network, Executor { it.run() }, discoveryRecord)
+ // Expect that service is found on testNetwork1
+ val foundInfo = discoveryRecord.waitForServiceDiscovered(
+ serviceName, testNetwork1.network)
+ assertEquals(testNetwork1.network, nsdShim.getNetwork(foundInfo))
+
+ // Discover service on testNetwork2.
+ nsdShim.discoverServices(nsdManager, SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD,
+ testNetwork2.network, Executor { it.run() }, discoveryRecord2)
+ // Expect that discovery is started then no other callbacks.
+ discoveryRecord2.expectCallback<DiscoveryStarted>()
+ discoveryRecord2.assertNoCallback()
+
+ // Discover service on all networks (not specify any network).
+ nsdShim.discoverServices(nsdManager, SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD,
+ null as Network? /* network */, Executor { it.run() }, discoveryRecord3)
+ // Expect that service is found on testNetwork1
+ val foundInfo3 = discoveryRecord3.waitForServiceDiscovered(
+ serviceName, testNetwork1.network)
+ assertEquals(testNetwork1.network, nsdShim.getNetwork(foundInfo3))
+ } cleanupStep {
+ nsdManager.stopServiceDiscovery(discoveryRecord2)
+ discoveryRecord2.expectCallback<DiscoveryStopped>()
+ } cleanup {
+ nsdManager.unregisterService(registrationRecord)
+ }
+ }
+
+ @Test
+ fun testNsdManager_RegisterServiceNameWithNonStandardCharacters() {
+ val serviceNames = "^Nsd.Test|Non-#AsCiI\\Characters&\\ufffe テスト 測試"
+ val si = NsdServiceInfo().apply {
+ serviceType = SERVICE_TYPE
+ serviceName = serviceNames
+ port = 12345 // Test won't try to connect so port does not matter
+ }
+
+ // Register the service name which contains non-standard characters.
+ val registrationRecord = NsdRegistrationRecord()
+ nsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, registrationRecord)
+ registrationRecord.expectCallback<ServiceRegistered>()
+
+ tryTest {
+ // Discover that service name.
+ val discoveryRecord = NsdDiscoveryRecord()
+ nsdManager.discoverServices(
+ SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryRecord
+ )
+ val foundInfo = discoveryRecord.waitForServiceDiscovered(serviceNames)
+
+ // Expect that resolving the service name works properly even service name contains
+ // non-standard characters.
+ val resolveRecord = NsdResolveRecord()
+ nsdManager.resolveService(foundInfo, resolveRecord)
+ val resolvedCb = resolveRecord.expectCallback<ServiceResolved>()
+ assertEquals(foundInfo.serviceName, resolvedCb.serviceInfo.serviceName)
+ } cleanupStep {
+ nsdManager.unregisterService(registrationRecord)
+ } cleanup {
+ registrationRecord.expectCallback<ServiceUnregistered>()
+ }
+ }
+
/**
* Register a service and return its registration record.
*/
- private fun registerService(record: NsdRegistrationRecord, si: NsdServiceInfo): NsdServiceInfo {
- nsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, record)
+ private fun registerService(
+ record: NsdRegistrationRecord,
+ si: NsdServiceInfo,
+ executor: Executor = Executor { it.run() }
+ ): NsdServiceInfo {
+ nsdShim.registerService(nsdManager, si, NsdManager.PROTOCOL_DNS_SD, executor, record)
// We may not always get the name that we tried to register;
// This events tells us the name that was registered.
val cb = record.expectCallback<ServiceRegistered>()
@@ -562,7 +709,7 @@
private fun resolveService(discoveredInfo: NsdServiceInfo): NsdServiceInfo {
val record = NsdResolveRecord()
- nsdManager.resolveService(discoveredInfo, record)
+ nsdShim.resolveService(nsdManager, discoveredInfo, Executor { it.run() }, record)
val resolvedCb = record.expectCallback<ServiceResolved>()
assertEquals(discoveredInfo.serviceName, resolvedCb.serviceInfo.serviceName)
diff --git a/tests/cts/net/src/android/net/cts/TestUtils.java b/tests/cts/net/src/android/net/cts/TestUtils.java
index c1100b1..001aa01 100644
--- a/tests/cts/net/src/android/net/cts/TestUtils.java
+++ b/tests/cts/net/src/android/net/cts/TestUtils.java
@@ -16,6 +16,8 @@
package android.net.cts;
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
+
import android.os.Build;
import com.android.modules.utils.build.SdkLevel;
@@ -33,4 +35,13 @@
public static boolean shouldTestSApis() {
return SdkLevel.isAtLeastS() && ConstantsShim.VERSION > Build.VERSION_CODES.R;
}
+
+ /**
+ * Whether to test T+ APIs. This requires a) that the test be running on an S+ device, and
+ * b) that the code be compiled against shims new enough to access these APIs.
+ */
+ public static boolean shouldTestTApis() {
+ // TODO: replace SC_V2 with Build.VERSION_CODES.S_V2 when it's available in mainline branch.
+ return SdkLevel.isAtLeastT() && ConstantsShim.VERSION > SC_V2;
+ }
}
diff --git a/tests/cts/net/util/java/android/net/cts/util/IkeSessionTestUtils.java b/tests/cts/net/util/java/android/net/cts/util/IkeSessionTestUtils.java
new file mode 100644
index 0000000..244bfc5
--- /dev/null
+++ b/tests/cts/net/util/java/android/net/cts/util/IkeSessionTestUtils.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.cts.util;
+
+import static android.net.ipsec.ike.SaProposal.DH_GROUP_4096_BIT_MODP;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_CBC;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_256_128;
+import static android.net.ipsec.ike.SaProposal.KEY_LEN_AES_128;
+import static android.net.ipsec.ike.SaProposal.KEY_LEN_AES_256;
+import static android.net.ipsec.ike.SaProposal.PSEUDORANDOM_FUNCTION_AES128_XCBC;
+
+import android.net.InetAddresses;
+import android.net.ipsec.ike.ChildSaProposal;
+import android.net.ipsec.ike.IkeFqdnIdentification;
+import android.net.ipsec.ike.IkeIpv4AddrIdentification;
+import android.net.ipsec.ike.IkeIpv6AddrIdentification;
+import android.net.ipsec.ike.IkeSaProposal;
+import android.net.ipsec.ike.IkeSessionParams;
+import android.net.ipsec.ike.TunnelModeChildSessionParams;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+
+/** Shared testing parameters and util methods for testing IKE */
+public class IkeSessionTestUtils {
+ private static final String TEST_SERVER_ADDR_V4 = "192.0.2.2";
+ private static final String TEST_SERVER_ADDR_V6 = "2001:db8::2";
+ private static final String TEST_IDENTITY = "client.cts.android.com";
+ private static final byte[] TEST_PSK = "ikeAndroidPsk".getBytes();
+ public static final IkeSessionParams IKE_PARAMS_V4 = getTestIkeSessionParams(false);
+ public static final IkeSessionParams IKE_PARAMS_V6 = getTestIkeSessionParams(true);
+
+ public static final TunnelModeChildSessionParams CHILD_PARAMS = getChildSessionParams();
+
+ private static TunnelModeChildSessionParams getChildSessionParams() {
+ final TunnelModeChildSessionParams.Builder childOptionsBuilder =
+ new TunnelModeChildSessionParams.Builder()
+ .addSaProposal(getChildSaProposals());
+
+ return childOptionsBuilder.build();
+ }
+
+ private static IkeSessionParams getTestIkeSessionParams(boolean testIpv6) {
+ final String testServer = testIpv6 ? TEST_SERVER_ADDR_V6 : TEST_SERVER_ADDR_V4;
+ final InetAddress addr = InetAddresses.parseNumericAddress(testServer);
+ final IkeSessionParams.Builder ikeOptionsBuilder =
+ new IkeSessionParams.Builder()
+ .setServerHostname(testServer)
+ .setLocalIdentification(new IkeFqdnIdentification(TEST_IDENTITY))
+ .setRemoteIdentification(testIpv6
+ ? new IkeIpv6AddrIdentification((Inet6Address) addr)
+ : new IkeIpv4AddrIdentification((Inet4Address) addr))
+ .setAuthPsk(TEST_PSK)
+ .addSaProposal(getIkeSaProposals());
+
+ return ikeOptionsBuilder.build();
+ }
+
+ private static IkeSaProposal getIkeSaProposals() {
+ return new IkeSaProposal.Builder()
+ .addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_256)
+ .addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_256_128)
+ .addDhGroup(DH_GROUP_4096_BIT_MODP)
+ .addPseudorandomFunction(PSEUDORANDOM_FUNCTION_AES128_XCBC).build();
+ }
+
+ private static ChildSaProposal getChildSaProposals() {
+ return new ChildSaProposal.Builder()
+ .addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_128)
+ .build();
+ }
+}
diff --git a/tests/cts/netpermission/internetpermission/Android.bp b/tests/cts/netpermission/internetpermission/Android.bp
new file mode 100644
index 0000000..37ad7cb
--- /dev/null
+++ b/tests/cts/netpermission/internetpermission/Android.bp
@@ -0,0 +1,33 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "CtsNetTestCasesInternetPermission",
+ defaults: ["cts_defaults"],
+
+ srcs: ["src/**/*.java"],
+
+ static_libs: ["ctstestrunner-axt"],
+
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+
+}
diff --git a/tests/cts/netpermission/internetpermission/AndroidManifest.xml b/tests/cts/netpermission/internetpermission/AndroidManifest.xml
new file mode 100644
index 0000000..45ef5bd
--- /dev/null
+++ b/tests/cts/netpermission/internetpermission/AndroidManifest.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.networkpermission.internetpermission.cts">
+
+ <application>
+ <uses-library android:name="android.test.runner"/>
+ <activity android:name="android.networkpermission.internetpermission.cts.InternetPermissionTest"
+ android:label="InternetPermissionTest"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
+ </intent-filter>
+ </activity>
+ </application>
+
+ <!--
+ The CTS stubs package cannot be used as the target application here,
+ since that requires many permissions to be set. Instead, specify this
+ package itself as the target and include any stub activities needed.
+
+ This test package uses the default InstrumentationTestRunner, because
+ the InstrumentationCtsTestRunner is only available in the stubs
+ package. That runner cannot be added to this package either, since it
+ relies on hidden APIs.
+ -->
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.networkpermission.internetpermission.cts"
+ android:label="CTS tests for INTERNET permissions">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener"/>
+ </instrumentation>
+
+</manifest>
diff --git a/tests/cts/netpermission/internetpermission/AndroidTest.xml b/tests/cts/netpermission/internetpermission/AndroidTest.xml
new file mode 100644
index 0000000..3b23e72
--- /dev/null
+++ b/tests/cts/netpermission/internetpermission/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Config for CTS internet permission test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="networking" />
+ <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <option name="not-shardable" value="true" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsNetTestCasesInternetPermission.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.networkpermission.internetpermission.cts" />
+ <option name="runtime-hint" value="10s" />
+ </test>
+</configuration>
diff --git a/tests/cts/netpermission/internetpermission/TEST_MAPPING b/tests/cts/netpermission/internetpermission/TEST_MAPPING
new file mode 100644
index 0000000..60877f4
--- /dev/null
+++ b/tests/cts/netpermission/internetpermission/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsNetTestCasesInternetPermission"
+ }
+ ]
+}
diff --git a/tests/cts/netpermission/internetpermission/src/android/net/cts/network/permission/InternetPermissionTest.java b/tests/cts/netpermission/internetpermission/src/android/net/cts/network/permission/InternetPermissionTest.java
new file mode 100644
index 0000000..2b7c8b5
--- /dev/null
+++ b/tests/cts/netpermission/internetpermission/src/android/net/cts/network/permission/InternetPermissionTest.java
@@ -0,0 +1,50 @@
+/*
+ * 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.cts.networkpermission.internetpermission;
+
+import static org.junit.Assert.fail;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Socket;
+/**
+* Test that protected android.net.ConnectivityManager methods cannot be called without
+* permissions
+*/
+@RunWith(AndroidJUnit4.class)
+public class InternetPermissionTest {
+
+ /**
+ * Verify that create inet socket failed because of the permission is missing.
+ * <p>Tests Permission:
+ * {@link android.Manifest.permission#INTERNET}.
+ */
+ @SmallTest
+ @Test
+ public void testCreateSocket() throws Exception {
+ try {
+ Socket socket = new Socket("example.com", 80);
+ fail("Ceate inet socket did not throw SecurityException as expected");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+}
diff --git a/tests/cts/netpermission/updatestatspermission/Android.bp b/tests/cts/netpermission/updatestatspermission/Android.bp
new file mode 100644
index 0000000..7a24886
--- /dev/null
+++ b/tests/cts/netpermission/updatestatspermission/Android.bp
@@ -0,0 +1,33 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "CtsNetTestCasesUpdateStatsPermission",
+ defaults: ["cts_defaults"],
+
+ srcs: ["src/**/*.java"],
+
+ static_libs: ["ctstestrunner-axt"],
+
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+
+}
diff --git a/tests/cts/netpermission/updatestatspermission/AndroidManifest.xml b/tests/cts/netpermission/updatestatspermission/AndroidManifest.xml
new file mode 100644
index 0000000..6babe8f
--- /dev/null
+++ b/tests/cts/netpermission/updatestatspermission/AndroidManifest.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.networkpermission.updatestatspermission.cts">
+
+ <!--
+ This CTS test is designed to test that an unprivileged app cannot get the
+ UPDATE_DEVICE_STATS permission even if it specified it in the manifest. the
+ UPDATE_DEVICE_STATS permission is a signature|privileged permission that CTS
+ test cannot have.
+ -->
+ <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS"/>
+ <uses-permission android:name="android.permission.INTERNET"/>
+ <application>
+ <uses-library android:name="android.test.runner"/>
+ <activity android:name="android.networkpermission.updatestatspermission.cts.UpdateStatsPermissionTest"
+ android:label="UpdateStatsPermissionTest"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
+ </intent-filter>
+ </activity>
+ </application>
+
+ <!--
+ The CTS stubs package cannot be used as the target application here,
+ since that requires many permissions to be set. Instead, specify this
+ package itself as the target and include any stub activities needed.
+
+ This test package uses the default InstrumentationTestRunner, because
+ the InstrumentationCtsTestRunner is only available in the stubs
+ package. That runner cannot be added to this package either, since it
+ relies on hidden APIs.
+ -->
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.networkpermission.updatestatspermission.cts"
+ android:label="CTS tests for UPDATE_DEVICE_STATS permissions">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener"/>
+ </instrumentation>
+
+</manifest>
diff --git a/tests/cts/netpermission/updatestatspermission/AndroidTest.xml b/tests/cts/netpermission/updatestatspermission/AndroidTest.xml
new file mode 100644
index 0000000..c47cad9
--- /dev/null
+++ b/tests/cts/netpermission/updatestatspermission/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Config for CTS update stats permission test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="networking" />
+ <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <option name="not-shardable" value="true" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsNetTestCasesUpdateStatsPermission.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.networkpermission.updatestatspermission.cts" />
+ <option name="runtime-hint" value="10s" />
+ </test>
+</configuration>
diff --git a/tests/cts/netpermission/updatestatspermission/TEST_MAPPING b/tests/cts/netpermission/updatestatspermission/TEST_MAPPING
new file mode 100644
index 0000000..6d6dfe0
--- /dev/null
+++ b/tests/cts/netpermission/updatestatspermission/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsNetTestCasesUpdateStatsPermission"
+ }
+ ]
+}
diff --git a/tests/cts/netpermission/updatestatspermission/src/android/net/cts/network/permission/UpdateStatsPermissionTest.java b/tests/cts/netpermission/updatestatspermission/src/android/net/cts/network/permission/UpdateStatsPermissionTest.java
new file mode 100644
index 0000000..bea843c
--- /dev/null
+++ b/tests/cts/netpermission/updatestatspermission/src/android/net/cts/network/permission/UpdateStatsPermissionTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.cts.networkpermission.updatestatspermission;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.net.TrafficStats;
+import android.os.Process;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.OutputStream;
+import java.net.Socket;
+
+/**
+* Test that protected android.net.ConnectivityManager methods cannot be called without
+* permissions
+*/
+@RunWith(AndroidJUnit4.class)
+public class UpdateStatsPermissionTest {
+
+ /**
+ * Verify that setCounterSet for a different uid failed because of the permission cannot be
+ * granted to a third-party app.
+ * <p>Tests Permission:
+ * {@link android.Manifest.permission#UPDATE_DEVICE_STATS}.
+ */
+ @SmallTest
+ @Test
+ public void testUpdateDeviceStatsPermission() throws Exception {
+
+ // Set the current thread uid to a another uid. It should silently fail when tagging the
+ // socket since the current process doesn't have UPDATE_DEVICE_STATS permission.
+ TrafficStats.setThreadStatsTag(0);
+ TrafficStats.setThreadStatsUid(/*root uid*/ 0);
+ Socket socket = new Socket("example.com", 80);
+ TrafficStats.tagSocket(socket);
+
+ // Transfer 1K of data to a remote host and verify the stats is still billed to the current
+ // uid.
+ final int byteCount = 1024;
+
+ socket.setTcpNoDelay(true);
+ socket.setSoLinger(true, 0);
+ OutputStream out = socket.getOutputStream();
+ byte[] buf = new byte[byteCount];
+ final long uidTxBytesBefore = TrafficStats.getUidTxBytes(Process.myUid());
+ out.write(buf);
+ out.close();
+ socket.close();
+ long uidTxBytesAfter = TrafficStats.getUidTxBytes(Process.myUid());
+ long uidTxDeltaBytes = uidTxBytesAfter - uidTxBytesBefore;
+ assertTrue("uidtxb: " + uidTxBytesBefore + " -> " + uidTxBytesAfter + " delta="
+ + uidTxDeltaBytes + " >= " + byteCount, uidTxDeltaBytes >= byteCount);
+ }
+
+ static final int UNSUPPORTED = -1;
+
+ /**
+ * Verify that get TrafficStats of a different uid failed because of the permission is not
+ * granted to a third-party app.
+ * <p>Tests Permission:
+ * {@link android.Manifest.permission#UPDATE_DEVICE_STATS}.
+ */
+ @SmallTest
+ @Test
+ public void testGetStatsOfOtherUid() throws Exception {
+ // Test get stats of another uid failed since the current process does not have permission
+ assertEquals(UNSUPPORTED, TrafficStats.getUidRxBytes(/*root uid*/ 0));
+ }
+}
diff --git a/tests/cts/tethering/Android.bp b/tests/cts/tethering/Android.bp
index e9c4e5a..6096a8b 100644
--- a/tests/cts/tethering/Android.bp
+++ b/tests/cts/tethering/Android.bp
@@ -56,7 +56,7 @@
defaults: ["CtsTetheringTestDefaults"],
min_sdk_version: "30",
- target_sdk_version: "30",
+ target_sdk_version: "33",
static_libs: [
"TetheringIntegrationTestsLatestSdkLib",
diff --git a/tests/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/integration/util/com/android/server/NetworkAgentWrapper.java
index 365c0cf..2763f5a 100644
--- a/tests/integration/util/com/android/server/NetworkAgentWrapper.java
+++ b/tests/integration/util/com/android/server/NetworkAgentWrapper.java
@@ -343,6 +343,10 @@
return mNetworkAgent;
}
+ public NetworkAgentConfig getNetworkAgentConfig() {
+ return mNetworkAgentConfig;
+ }
+
public NetworkCapabilities getNetworkCapabilities() {
return mNetworkCapabilities;
}
diff --git a/tests/mts/bpf_existence_test.cpp b/tests/mts/bpf_existence_test.cpp
index 2bba282..db39e6f 100644
--- a/tests/mts/bpf_existence_test.cpp
+++ b/tests/mts/bpf_existence_test.cpp
@@ -42,6 +42,9 @@
#define PLATFORM "/sys/fs/bpf/"
#define TETHERING "/sys/fs/bpf/tethering/"
+#define PRIVATE "/sys/fs/bpf/net_private/"
+#define SHARED "/sys/fs/bpf/net_shared/"
+#define NETD "/sys/fs/bpf/netd_shared/"
class BpfExistenceTest : public ::testing::Test {
};
@@ -84,6 +87,45 @@
};
static const set<string> INTRODUCED_T = {
+ SHARED "map_block_blocked_ports_map",
+ SHARED "map_clatd_clat_egress4_map",
+ SHARED "map_clatd_clat_ingress6_map",
+ SHARED "map_dscp_policy_ipv4_dscp_policies_map",
+ SHARED "map_dscp_policy_ipv4_socket_to_policies_map_A",
+ SHARED "map_dscp_policy_ipv4_socket_to_policies_map_B",
+ SHARED "map_dscp_policy_ipv6_dscp_policies_map",
+ SHARED "map_dscp_policy_ipv6_socket_to_policies_map_A",
+ SHARED "map_dscp_policy_ipv6_socket_to_policies_map_B",
+ SHARED "map_dscp_policy_switch_comp_map",
+ NETD "map_netd_app_uid_stats_map",
+ NETD "map_netd_configuration_map",
+ NETD "map_netd_cookie_tag_map",
+ NETD "map_netd_iface_index_name_map",
+ NETD "map_netd_iface_stats_map",
+ NETD "map_netd_stats_map_A",
+ NETD "map_netd_stats_map_B",
+ NETD "map_netd_uid_counterset_map",
+ NETD "map_netd_uid_owner_map",
+ NETD "map_netd_uid_permission_map",
+ SHARED "prog_clatd_schedcls_egress4_clat_ether",
+ SHARED "prog_clatd_schedcls_egress4_clat_rawip",
+ SHARED "prog_clatd_schedcls_ingress6_clat_ether",
+ SHARED "prog_clatd_schedcls_ingress6_clat_rawip",
+ NETD "prog_netd_cgroupskb_egress_stats",
+ NETD "prog_netd_cgroupskb_ingress_stats",
+ NETD "prog_netd_cgroupsock_inet_create",
+ NETD "prog_netd_schedact_ingress_account",
+ NETD "prog_netd_skfilter_allowlist_xtbpf",
+ NETD "prog_netd_skfilter_denylist_xtbpf",
+ NETD "prog_netd_skfilter_egress_xtbpf",
+ NETD "prog_netd_skfilter_ingress_xtbpf",
+};
+
+static const set<string> INTRODUCED_T_5_4 = {
+ SHARED "prog_block_bind4_block_port",
+ SHARED "prog_block_bind6_block_port",
+ SHARED "prog_dscp_policy_schedcls_set_dscp_ether",
+ SHARED "prog_dscp_policy_schedcls_set_dscp_raw_ip",
};
static const set<string> REMOVED_T = {
@@ -125,6 +167,7 @@
if (IsAtLeastT()) {
addAll(expected, INTRODUCED_T);
+ if (android::bpf::isAtLeastKernelVersion(5, 4, 0)) addAll(expected, INTRODUCED_T_5_4);
removeAll(expected, REMOVED_T);
addAll(unexpected, REMOVED_T);
diff --git a/tests/native/Android.bp b/tests/native/Android.bp
new file mode 100644
index 0000000..7d43aa8
--- /dev/null
+++ b/tests/native/Android.bp
@@ -0,0 +1,40 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+ name: "connectivity_native_test",
+ test_suites: [
+ "general-tests",
+ "mts-tethering",
+ "vts",
+ ],
+ test_config_template: "AndroidTestTemplate.xml",
+ min_sdk_version: "31",
+ tidy: false,
+ srcs: [
+ "connectivity_native_test.cpp",
+ ],
+ header_libs: ["bpf_connectivity_headers"],
+ shared_libs: [
+ "libbase",
+ "libbinder_ndk",
+ "liblog",
+ "libnetutils",
+ "libprocessgroup",
+ ],
+ static_libs: [
+ "connectivity_native_aidl_interface-lateststable-ndk",
+ "libcutils",
+ "libmodules-utils-build",
+ "libutils",
+ ],
+ compile_multilib: "first",
+}
+
+filegroup {
+ name: "net_native_test_config_template",
+ srcs: [
+ "NetNativeTestConfigTemplate.xml",
+ ],
+}
diff --git a/tests/native/AndroidTestTemplate.xml b/tests/native/AndroidTestTemplate.xml
new file mode 100644
index 0000000..44e35a9
--- /dev/null
+++ b/tests/native/AndroidTestTemplate.xml
@@ -0,0 +1,30 @@
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Configuration for connectivity {MODULE} tests">
+ <option name="test-suite-tag" value="mts" />
+ <option name="config-descriptor:metadata" key="mainline-param" value="CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex" />
+ <!-- The tested code is only part of a SDK 30+ module (Tethering) -->
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
+
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="cleanup" value="true" />
+ <option name="push" value="{MODULE}->/data/local/tmp/{MODULE}" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="{MODULE}" />
+ </test>
+</configuration>
diff --git a/tests/native/NetNativeTestConfigTemplate.xml b/tests/native/NetNativeTestConfigTemplate.xml
new file mode 100644
index 0000000..b71e9aa
--- /dev/null
+++ b/tests/native/NetNativeTestConfigTemplate.xml
@@ -0,0 +1,31 @@
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Configuration for {MODULE} tests">
+ <option name="test-suite-tag" value="mts" />
+ <option name="config-descriptor:metadata" key="mainline-param" value="CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex" />
+ <!-- Only run tests if the device under test is SDK version 33 (Android 13) or above. -->
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk33ModuleController" />
+
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="cleanup" value="true" />
+ <option name="push" value="{MODULE}->/data/local/tmp/{MODULE}" />
+ <option name="append-bitness" value="true" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="{MODULE}" />
+ </test>
+</configuration>
diff --git a/tests/native/OWNERS b/tests/native/OWNERS
new file mode 100644
index 0000000..8dfa455
--- /dev/null
+++ b/tests/native/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 31808
+set noparent
+file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking_xts
diff --git a/tests/native/connectivity_native_test.cpp b/tests/native/connectivity_native_test.cpp
new file mode 100644
index 0000000..3db5265
--- /dev/null
+++ b/tests/native/connectivity_native_test.cpp
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <aidl/android/net/connectivity/aidl/ConnectivityNative.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+#include <android-modules-utils/sdk_level.h>
+#include <cutils/misc.h> // FIRST_APPLICATION_UID
+#include <gtest/gtest.h>
+#include <netinet/in.h>
+
+#include "bpf/BpfUtils.h"
+
+using aidl::android::net::connectivity::aidl::IConnectivityNative;
+
+class ConnectivityNativeBinderTest : public ::testing::Test {
+ public:
+ std::vector<int32_t> mActualBlockedPorts;
+
+ ConnectivityNativeBinderTest() {
+ AIBinder* binder = AServiceManager_getService("connectivity_native");
+ ndk::SpAIBinder sBinder = ndk::SpAIBinder(binder);
+ mService = aidl::android::net::connectivity::aidl::IConnectivityNative::fromBinder(sBinder);
+ }
+
+ void SetUp() override {
+ // Skip test case if not on T.
+ if (!android::modules::sdklevel::IsAtLeastT()) GTEST_SKIP() <<
+ "Should be at least T device.";
+
+ // Skip test case if not on 5.4 kernel which is required by bpf prog.
+ if (!android::bpf::isAtLeastKernelVersion(5, 4, 0)) GTEST_SKIP() <<
+ "Kernel should be at least 5.4.";
+
+ ASSERT_NE(nullptr, mService.get());
+
+ // If there are already ports being blocked on device unblockAllPortsForBind() store
+ // the currently blocked ports and add them back at the end of the test. Do this for
+ // every test case so additional test cases do not forget to add ports back.
+ ndk::ScopedAStatus status = mService->getPortsBlockedForBind(&mActualBlockedPorts);
+ EXPECT_TRUE(status.isOk()) << status.getDescription ();
+
+ }
+
+ void TearDown() override {
+ ndk::ScopedAStatus status;
+ if (mActualBlockedPorts.size() > 0) {
+ for (int i : mActualBlockedPorts) {
+ mService->blockPortForBind(i);
+ EXPECT_TRUE(status.isOk()) << status.getDescription ();
+ }
+ }
+ }
+
+ protected:
+ std::shared_ptr<IConnectivityNative> mService;
+
+ void runSocketTest (sa_family_t family, const int type, bool blockPort) {
+ ndk::ScopedAStatus status;
+ in_port_t port = 0;
+ int sock, sock2;
+ // Open two sockets with SO_REUSEADDR and expect they can both bind to port.
+ sock = openSocket(&port, family, type, false /* expectBindFail */);
+ sock2 = openSocket(&port, family, type, false /* expectBindFail */);
+
+ int blockedPort = 0;
+ if (blockPort) {
+ blockedPort = ntohs(port);
+ status = mService->blockPortForBind(blockedPort);
+ EXPECT_TRUE(status.isOk()) << status.getDescription ();
+ }
+
+ int sock3 = openSocket(&port, family, type, blockPort /* expectBindFail */);
+
+ if (blockPort) {
+ EXPECT_EQ(-1, sock3);
+ status = mService->unblockPortForBind(blockedPort);
+ EXPECT_TRUE(status.isOk()) << status.getDescription ();
+ } else {
+ EXPECT_NE(-1, sock3);
+ }
+
+ close(sock);
+ close(sock2);
+ close(sock3);
+ }
+
+ /*
+ * Open the socket and update the port.
+ */
+ int openSocket(in_port_t* port, sa_family_t family, const int type, bool expectBindFail) {
+ int ret = 0;
+ int enable = 1;
+ const int sock = socket(family, type, 0);
+ ret = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable));
+ EXPECT_EQ(0, ret);
+
+ if (family == AF_INET) {
+ struct sockaddr_in addr4 = { .sin_family = family, .sin_port = htons(*port) };
+ ret = bind(sock, (struct sockaddr*) &addr4, sizeof(addr4));
+ } else {
+ struct sockaddr_in6 addr6 = { .sin6_family = family, .sin6_port = htons(*port) };
+ ret = bind(sock, (struct sockaddr*) &addr6, sizeof(addr6));
+ }
+
+ if (expectBindFail) {
+ EXPECT_NE(0, ret);
+ // If port is blocked, return here since the port is not needed
+ // for subsequent sockets.
+ close(sock);
+ return -1;
+ }
+ EXPECT_EQ(0, ret) << "bind unexpectedly failed, errno: " << errno;
+
+ if (family == AF_INET) {
+ struct sockaddr_in sin;
+ socklen_t len = sizeof(sin);
+ EXPECT_NE(-1, getsockname(sock, (struct sockaddr *)&sin, &len));
+ EXPECT_NE(0, ntohs(sin.sin_port));
+ if (*port != 0) EXPECT_EQ(*port, ntohs(sin.sin_port));
+ *port = ntohs(sin.sin_port);
+ } else {
+ struct sockaddr_in6 sin;
+ socklen_t len = sizeof(sin);
+ EXPECT_NE(-1, getsockname(sock, (struct sockaddr *)&sin, &len));
+ EXPECT_NE(0, ntohs(sin.sin6_port));
+ if (*port != 0) EXPECT_EQ(*port, ntohs(sin.sin6_port));
+ *port = ntohs(sin.sin6_port);
+ }
+ return sock;
+ }
+};
+
+TEST_F(ConnectivityNativeBinderTest, PortUnblockedV4Udp) {
+ runSocketTest(AF_INET, SOCK_DGRAM, false);
+}
+
+TEST_F(ConnectivityNativeBinderTest, PortUnblockedV4Tcp) {
+ runSocketTest(AF_INET, SOCK_STREAM, false);
+}
+
+TEST_F(ConnectivityNativeBinderTest, PortUnblockedV6Udp) {
+ runSocketTest(AF_INET6, SOCK_DGRAM, false);
+}
+
+TEST_F(ConnectivityNativeBinderTest, PortUnblockedV6Tcp) {
+ runSocketTest(AF_INET6, SOCK_STREAM, false);
+}
+
+TEST_F(ConnectivityNativeBinderTest, BlockPort4Udp) {
+ runSocketTest(AF_INET, SOCK_DGRAM, true);
+}
+
+TEST_F(ConnectivityNativeBinderTest, BlockPort4Tcp) {
+ runSocketTest(AF_INET, SOCK_STREAM, true);
+}
+
+TEST_F(ConnectivityNativeBinderTest, BlockPort6Udp) {
+ runSocketTest(AF_INET6, SOCK_DGRAM, true);
+}
+
+TEST_F(ConnectivityNativeBinderTest, BlockPort6Tcp) {
+ runSocketTest(AF_INET6, SOCK_STREAM, true);
+}
+
+TEST_F(ConnectivityNativeBinderTest, BlockPortTwice) {
+ ndk::ScopedAStatus status = mService->blockPortForBind(5555);
+ EXPECT_TRUE(status.isOk()) << status.getDescription ();
+ status = mService->blockPortForBind(5555);
+ EXPECT_TRUE(status.isOk()) << status.getDescription ();
+ status = mService->unblockPortForBind(5555);
+ EXPECT_TRUE(status.isOk()) << status.getDescription ();
+}
+
+TEST_F(ConnectivityNativeBinderTest, GetBlockedPorts) {
+ ndk::ScopedAStatus status;
+ std::vector<int> blockedPorts{1, 100, 1220, 1333, 2700, 5555, 5600, 65000};
+ for (int i : blockedPorts) {
+ status = mService->blockPortForBind(i);
+ EXPECT_TRUE(status.isOk()) << status.getDescription ();
+ }
+ std::vector<int32_t> actualBlockedPorts;
+ status = mService->getPortsBlockedForBind(&actualBlockedPorts);
+ EXPECT_TRUE(status.isOk()) << status.getDescription ();
+ EXPECT_FALSE(actualBlockedPorts.empty());
+ EXPECT_EQ(blockedPorts, actualBlockedPorts);
+
+ // Remove the ports we added.
+ status = mService->unblockAllPortsForBind();
+ EXPECT_TRUE(status.isOk()) << status.getDescription ();
+ status = mService->getPortsBlockedForBind(&actualBlockedPorts);
+ EXPECT_TRUE(status.isOk()) << status.getDescription ();
+ EXPECT_TRUE(actualBlockedPorts.empty());
+}
+
+TEST_F(ConnectivityNativeBinderTest, UnblockAllPorts) {
+ ndk::ScopedAStatus status;
+ std::vector<int> blockedPorts{1, 100, 1220, 1333, 2700, 5555, 5600, 65000};
+
+ if (mActualBlockedPorts.size() > 0) {
+ status = mService->unblockAllPortsForBind();
+ }
+
+ for (int i : blockedPorts) {
+ status = mService->blockPortForBind(i);
+ EXPECT_TRUE(status.isOk()) << status.getDescription ();
+ }
+
+ std::vector<int32_t> actualBlockedPorts;
+ status = mService->getPortsBlockedForBind(&actualBlockedPorts);
+ EXPECT_TRUE(status.isOk()) << status.getDescription ();
+ EXPECT_FALSE(actualBlockedPorts.empty());
+
+ status = mService->unblockAllPortsForBind();
+ EXPECT_TRUE(status.isOk()) << status.getDescription ();
+ status = mService->getPortsBlockedForBind(&actualBlockedPorts);
+ EXPECT_TRUE(status.isOk()) << status.getDescription ();
+ EXPECT_TRUE(actualBlockedPorts.empty());
+ // If mActualBlockedPorts is not empty, ports will be added back in teardown.
+}
+
+TEST_F(ConnectivityNativeBinderTest, BlockNegativePort) {
+ int retry = 0;
+ ndk::ScopedAStatus status;
+ do {
+ status = mService->blockPortForBind(-1);
+ // TODO: find out why transaction failed is being thrown on the first attempt.
+ } while (status.getExceptionCode() == EX_TRANSACTION_FAILED && retry++ < 5);
+ EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode());
+}
+
+TEST_F(ConnectivityNativeBinderTest, UnblockNegativePort) {
+ int retry = 0;
+ ndk::ScopedAStatus status;
+ do {
+ status = mService->unblockPortForBind(-1);
+ // TODO: find out why transaction failed is being thrown on the first attempt.
+ } while (status.getExceptionCode() == EX_TRANSACTION_FAILED && retry++ < 5);
+ EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode());
+}
+
+TEST_F(ConnectivityNativeBinderTest, BlockMaxPort) {
+ int retry = 0;
+ ndk::ScopedAStatus status;
+ do {
+ status = mService->blockPortForBind(65536);
+ // TODO: find out why transaction failed is being thrown on the first attempt.
+ } while (status.getExceptionCode() == EX_TRANSACTION_FAILED && retry++ < 5);
+ EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode());
+}
+
+TEST_F(ConnectivityNativeBinderTest, UnblockMaxPort) {
+ int retry = 0;
+ ndk::ScopedAStatus status;
+ do {
+ status = mService->unblockPortForBind(65536);
+ // TODO: find out why transaction failed is being thrown on the first attempt.
+ } while (status.getExceptionCode() == EX_TRANSACTION_FAILED && retry++ < 5);
+ EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode());
+}
+
+TEST_F(ConnectivityNativeBinderTest, CheckPermission) {
+ int retry = 0;
+ int curUid = getuid();
+ EXPECT_EQ(0, seteuid(FIRST_APPLICATION_UID + 2000)) << "seteuid failed: " << strerror(errno);
+ ndk::ScopedAStatus status;
+ do {
+ status = mService->blockPortForBind(5555);
+ // TODO: find out why transaction failed is being thrown on the first attempt.
+ } while (status.getExceptionCode() == EX_TRANSACTION_FAILED && retry++ < 5);
+ EXPECT_EQ(EX_SECURITY, status.getExceptionCode());
+ EXPECT_EQ(0, seteuid(curUid)) << "seteuid failed: " << strerror(errno);
+}
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index 07dcae3..3ea27f7 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -54,39 +54,21 @@
filegroup {
name: "non-connectivity-module-test",
srcs: [
- "java/android/app/usage/*.java",
- "java/android/net/EthernetNetworkUpdateRequestTest.java",
"java/android/net/Ikev2VpnProfileTest.java",
"java/android/net/IpMemoryStoreTest.java",
- "java/android/net/IpSecAlgorithmTest.java",
- "java/android/net/IpSecConfigTest.java",
- "java/android/net/IpSecManagerTest.java",
- "java/android/net/IpSecTransformTest.java",
- "java/android/net/KeepalivePacketDataUtilTest.java",
- "java/android/net/NetworkIdentityTest.kt",
- "java/android/net/NetworkStats*.java",
- "java/android/net/NetworkTemplateTest.kt",
"java/android/net/TelephonyNetworkSpecifierTest.java",
"java/android/net/VpnManagerTest.java",
"java/android/net/ipmemorystore/*.java",
"java/android/net/netstats/NetworkStatsDataMigrationUtilsTest.kt",
- "java/android/net/nsd/*.java",
"java/com/android/internal/net/NetworkUtilsInternalTest.java",
"java/com/android/internal/net/VpnProfileTest.java",
- "java/com/android/server/IpSecServiceParameterizedTest.java",
- "java/com/android/server/IpSecServiceRefcountedResourceTest.java",
- "java/com/android/server/IpSecServiceTest.java",
"java/com/android/server/NetworkManagementServiceTest.java",
- "java/com/android/server/NsdServiceTest.java",
"java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java",
"java/com/android/server/connectivity/IpConnectivityMetricsTest.java",
"java/com/android/server/connectivity/MultipathPolicyTrackerTest.java",
"java/com/android/server/connectivity/NetdEventListenerServiceTest.java",
"java/com/android/server/connectivity/VpnTest.java",
"java/com/android/server/net/ipmemorystore/*.java",
- "java/com/android/server/net/BpfInterfaceMapUpdaterTest.java",
- "java/com/android/server/net/NetworkStats*.java",
- "java/com/android/server/net/TestableUsageCallback.kt",
]
}
@@ -105,7 +87,7 @@
name: "FrameworksNetTestsDefaults",
min_sdk_version: "30",
defaults: [
- "framework-connectivity-test-defaults",
+ "framework-connectivity-internal-test-defaults",
],
srcs: [
"java/**/*.java",
@@ -128,6 +110,7 @@
"service-connectivity-pre-jarjar",
"service-connectivity-tiramisu-pre-jarjar",
"services.core-vpn",
+ "cts-net-utils"
],
libs: [
"android.net.ipsec.ike.stubs.module_lib",
diff --git a/tests/unit/AndroidManifest.xml b/tests/unit/AndroidManifest.xml
index 887f171..54e1cd0 100644
--- a/tests/unit/AndroidManifest.xml
+++ b/tests/unit/AndroidManifest.xml
@@ -50,7 +50,7 @@
<uses-permission android:name="android.permission.NETWORK_STATS_PROVIDER" />
<uses-permission android:name="android.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE" />
- <application>
+ <application android:testOnly="true">
<uses-library android:name="android.test.runner" />
<uses-library android:name="android.net.ipsec.ike" />
<activity
diff --git a/tests/unit/AndroidTest.xml b/tests/unit/AndroidTest.xml
index 939ae49..2d32e55 100644
--- a/tests/unit/AndroidTest.xml
+++ b/tests/unit/AndroidTest.xml
@@ -15,7 +15,8 @@
-->
<configuration description="Runs Frameworks Networking Tests.">
<target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
- <option name="test-file-name" value="FrameworksNetTests.apk" />
+ <option name="test-file-name" value="FrameworksNetTests.apk" />
+ <option name="install-arg" value="-t" />
</target_preparer>
<option name="test-suite-tag" value="apct" />
diff --git a/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java b/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java
index 561e621..b1b76ec 100644
--- a/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java
+++ b/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java
@@ -54,7 +54,7 @@
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class NetworkStatsManagerTest {
private static final String TEST_SUBSCRIBER_ID = "subid";
diff --git a/tests/unit/java/android/net/ConnectivityManagerTest.java b/tests/unit/java/android/net/ConnectivityManagerTest.java
index f324630..c327868 100644
--- a/tests/unit/java/android/net/ConnectivityManagerTest.java
+++ b/tests/unit/java/android/net/ConnectivityManagerTest.java
@@ -41,6 +41,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -72,6 +73,7 @@
import androidx.test.filters.SmallTest;
+import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
@@ -82,6 +84,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.lang.ref.WeakReference;
+
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
@DevSdkIgnoreRule.IgnoreUpTo(VERSION_CODES.R)
@@ -461,4 +465,49 @@
}
fail("expected exception of type " + throwableType);
}
+
+ private static class MockContext extends BroadcastInterceptingContext {
+ MockContext(Context base) {
+ super(base);
+ }
+
+ @Override
+ public Context getApplicationContext() {
+ return mock(Context.class);
+ }
+ }
+
+ private WeakReference<Context> makeConnectivityManagerAndReturnContext() {
+ // Mockito may have an internal reference to the mock, creating MockContext for testing.
+ final Context c = new MockContext(mock(Context.class));
+
+ new ConnectivityManager(c, mService);
+
+ return new WeakReference<>(c);
+ }
+
+ private void forceGC() {
+ // First GC ensures that objects are collected for finalization, then second GC ensures
+ // they're garbage-collected after being finalized.
+ System.gc();
+ System.runFinalization();
+ System.gc();
+ }
+
+ @Test
+ public void testConnectivityManagerDoesNotLeakContext() throws Exception {
+ final WeakReference<Context> ref = makeConnectivityManagerAndReturnContext();
+
+ final int attempts = 100;
+ final long waitIntervalMs = 50;
+ for (int i = 0; i < attempts; i++) {
+ forceGC();
+ if (ref.get() == null) break;
+
+ Thread.sleep(waitIntervalMs);
+ }
+
+ assertNull("ConnectivityManager weak reference still not null after " + attempts
+ + " attempts", ref.get());
+ }
}
diff --git a/tests/unit/java/android/net/Ikev2VpnProfileTest.java b/tests/unit/java/android/net/Ikev2VpnProfileTest.java
index 8559c20..5cb014f 100644
--- a/tests/unit/java/android/net/Ikev2VpnProfileTest.java
+++ b/tests/unit/java/android/net/Ikev2VpnProfileTest.java
@@ -16,6 +16,9 @@
package android.net;
+import static android.net.cts.util.IkeSessionTestUtils.CHILD_PARAMS;
+import static android.net.cts.util.IkeSessionTestUtils.IKE_PARAMS_V6;
+
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static org.junit.Assert.assertArrayEquals;
@@ -25,6 +28,7 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import android.net.ipsec.ike.IkeTunnelConnectionParams;
import android.os.Build;
import android.test.mock.MockContext;
@@ -441,6 +445,33 @@
assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
}
+ @Test
+ public void testConversionIsLosslessWithIkeTunConnParams() throws Exception {
+ final IkeTunnelConnectionParams tunnelParams =
+ new IkeTunnelConnectionParams(IKE_PARAMS_V6, CHILD_PARAMS);
+ // Config authentication related fields is not required while building with
+ // IkeTunnelConnectionParams.
+ final Ikev2VpnProfile ikeProfile = new Ikev2VpnProfile.Builder(tunnelParams).build();
+ assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
+ }
+
+ @Test
+ public void testEquals() throws Exception {
+ // Verify building without IkeTunnelConnectionParams
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+ builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+ assertEquals(builder.build(), builder.build());
+
+ // Verify building with IkeTunnelConnectionParams
+ final IkeTunnelConnectionParams tunnelParams =
+ new IkeTunnelConnectionParams(IKE_PARAMS_V6, CHILD_PARAMS);
+ final IkeTunnelConnectionParams tunnelParams2 =
+ new IkeTunnelConnectionParams(IKE_PARAMS_V6, CHILD_PARAMS);
+ assertEquals(new Ikev2VpnProfile.Builder(tunnelParams).build(),
+ new Ikev2VpnProfile.Builder(tunnelParams2).build());
+ }
+
+
private static class CertificateAndKey {
public final X509Certificate cert;
public final PrivateKey key;
diff --git a/tests/unit/java/android/net/IpSecAlgorithmTest.java b/tests/unit/java/android/net/IpSecAlgorithmTest.java
index c473e82..1482055 100644
--- a/tests/unit/java/android/net/IpSecAlgorithmTest.java
+++ b/tests/unit/java/android/net/IpSecAlgorithmTest.java
@@ -47,7 +47,7 @@
/** Unit tests for {@link IpSecAlgorithm}. */
@SmallTest
@RunWith(DevSdkIgnoreRunner.class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class IpSecAlgorithmTest {
private static final byte[] KEY_MATERIAL;
diff --git a/tests/unit/java/android/net/IpSecConfigTest.java b/tests/unit/java/android/net/IpSecConfigTest.java
index b87cb48..9f83036 100644
--- a/tests/unit/java/android/net/IpSecConfigTest.java
+++ b/tests/unit/java/android/net/IpSecConfigTest.java
@@ -36,7 +36,7 @@
/** Unit tests for {@link IpSecConfig}. */
@SmallTest
@RunWith(DevSdkIgnoreRunner.class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class IpSecConfigTest {
@Test
diff --git a/tests/unit/java/android/net/IpSecManagerTest.java b/tests/unit/java/android/net/IpSecManagerTest.java
index cda8eb7..335f539 100644
--- a/tests/unit/java/android/net/IpSecManagerTest.java
+++ b/tests/unit/java/android/net/IpSecManagerTest.java
@@ -52,7 +52,7 @@
/** Unit tests for {@link IpSecManager}. */
@SmallTest
@RunWith(DevSdkIgnoreRunner.class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class IpSecManagerTest {
private static final int TEST_UDP_ENCAP_PORT = 34567;
diff --git a/tests/unit/java/android/net/IpSecTransformTest.java b/tests/unit/java/android/net/IpSecTransformTest.java
index 81375f1..c1bd719 100644
--- a/tests/unit/java/android/net/IpSecTransformTest.java
+++ b/tests/unit/java/android/net/IpSecTransformTest.java
@@ -32,7 +32,7 @@
/** Unit tests for {@link IpSecTransform}. */
@SmallTest
@RunWith(DevSdkIgnoreRunner.class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class IpSecTransformTest {
@Test
diff --git a/tests/unit/java/android/net/NetworkIdentitySetTest.kt b/tests/unit/java/android/net/NetworkIdentitySetTest.kt
new file mode 100644
index 0000000..d61ebf9
--- /dev/null
+++ b/tests/unit/java/android/net/NetworkIdentitySetTest.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net
+
+import android.content.Context
+import android.net.ConnectivityManager.TYPE_MOBILE
+import android.os.Build
+import android.telephony.TelephonyManager
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import kotlin.test.assertEquals
+
+private const val TEST_IMSI1 = "testimsi1"
+
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
+@RunWith(DevSdkIgnoreRunner::class)
+class NetworkIdentitySetTest {
+ private val mockContext = mock(Context::class.java)
+
+ private fun buildMobileNetworkStateSnapshot(
+ caps: NetworkCapabilities,
+ subscriberId: String
+ ): NetworkStateSnapshot {
+ return NetworkStateSnapshot(mock(Network::class.java), caps,
+ LinkProperties(), subscriberId, TYPE_MOBILE)
+ }
+
+ @Test
+ fun testCompare() {
+ val ident1 = NetworkIdentity.buildNetworkIdentity(mockContext,
+ buildMobileNetworkStateSnapshot(NetworkCapabilities(), TEST_IMSI1),
+ false /* defaultNetwork */, TelephonyManager.NETWORK_TYPE_UMTS)
+ val ident2 = NetworkIdentity.buildNetworkIdentity(mockContext,
+ buildMobileNetworkStateSnapshot(NetworkCapabilities(), TEST_IMSI1),
+ true /* defaultNetwork */, TelephonyManager.NETWORK_TYPE_UMTS)
+
+ // Verify that the results of comparing two empty sets are equal
+ assertEquals(0, NetworkIdentitySet.compare(NetworkIdentitySet(), NetworkIdentitySet()))
+
+ val identSet1 = NetworkIdentitySet()
+ val identSet2 = NetworkIdentitySet()
+ identSet1.add(ident1)
+ identSet2.add(ident2)
+ assertEquals(-1, NetworkIdentitySet.compare(NetworkIdentitySet(), identSet1))
+ assertEquals(1, NetworkIdentitySet.compare(identSet1, NetworkIdentitySet()))
+ assertEquals(0, NetworkIdentitySet.compare(identSet1, identSet1))
+ assertEquals(-1, NetworkIdentitySet.compare(identSet1, identSet2))
+ }
+}
diff --git a/tests/unit/java/android/net/NetworkIdentityTest.kt b/tests/unit/java/android/net/NetworkIdentityTest.kt
index bf5568d..d84328c 100644
--- a/tests/unit/java/android/net/NetworkIdentityTest.kt
+++ b/tests/unit/java/android/net/NetworkIdentityTest.kt
@@ -47,7 +47,7 @@
private const val TEST_SUBID2 = 2
@RunWith(DevSdkIgnoreRunner::class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
class NetworkIdentityTest {
private val mockContext = mock(Context::class.java)
diff --git a/tests/unit/java/android/net/NetworkStatsAccessTest.java b/tests/unit/java/android/net/NetworkStatsAccessTest.java
index 97a93ca..a74056b 100644
--- a/tests/unit/java/android/net/NetworkStatsAccessTest.java
+++ b/tests/unit/java/android/net/NetworkStatsAccessTest.java
@@ -19,6 +19,7 @@
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.when;
import android.Manifest;
@@ -66,6 +67,10 @@
when(mContext.getSystemServiceName(DevicePolicyManager.class))
.thenReturn(Context.DEVICE_POLICY_SERVICE);
when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(mDpm);
+ if (mContext.getSystemService(DevicePolicyManager.class) == null) {
+ // Test is using mockito-extended
+ doCallRealMethod().when(mContext).getSystemService(DevicePolicyManager.class);
+ }
setHasCarrierPrivileges(false);
setIsDeviceOwner(false);
diff --git a/tests/unit/java/android/net/NetworkStatsCollectionTest.java b/tests/unit/java/android/net/NetworkStatsCollectionTest.java
index 32c106d..b518a61 100644
--- a/tests/unit/java/android/net/NetworkStatsCollectionTest.java
+++ b/tests/unit/java/android/net/NetworkStatsCollectionTest.java
@@ -37,6 +37,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
+import android.annotation.NonNull;
import android.content.res.Resources;
import android.net.NetworkStatsCollection.Key;
import android.os.Process;
@@ -76,6 +77,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* Tests for {@link NetworkStatsCollection}.
@@ -534,50 +536,84 @@
assertThrows(ArithmeticException.class, () -> multiplySafeByRational(30, 3, 0));
}
+ private static void assertCollectionEntries(
+ @NonNull Map<Key, NetworkStatsHistory> expectedEntries,
+ @NonNull NetworkStatsCollection collection) {
+ final Map<Key, NetworkStatsHistory> actualEntries = collection.getEntries();
+ assertEquals(expectedEntries.size(), actualEntries.size());
+ for (Key expectedKey : expectedEntries.keySet()) {
+ final NetworkStatsHistory expectedHistory = expectedEntries.get(expectedKey);
+ final NetworkStatsHistory actualHistory = actualEntries.get(expectedKey);
+ assertNotNull(actualHistory);
+ assertEquals(expectedHistory.getEntries(), actualHistory.getEntries());
+ actualEntries.remove(expectedKey);
+ }
+ assertEquals(0, actualEntries.size());
+ }
+
@Test
- public void testBuilder() {
- final Map<Key, NetworkStatsHistory> expectedEntries = new ArrayMap<>();
- final NetworkStats.Entry entry = new NetworkStats.Entry();
- final NetworkIdentitySet ident = new NetworkIdentitySet();
- final Key key1 = new Key(ident, 0, 0, 0);
- final Key key2 = new Key(ident, 1, 0, 0);
+ public void testRemoveHistoryBefore() {
+ final NetworkIdentity testIdent = new NetworkIdentity.Builder()
+ .setSubscriberId(TEST_IMSI).build();
+ final Key key1 = new Key(Set.of(testIdent), 0, 0, 0);
+ final Key key2 = new Key(Set.of(testIdent), 1, 0, 0);
final long bucketDuration = 10;
+ // Prepare entries for testing, with different bucket start timestamps.
final NetworkStatsHistory.Entry entry1 = new NetworkStatsHistory.Entry(10, 10, 40,
4, 50, 5, 60);
- final NetworkStatsHistory.Entry entry2 = new NetworkStatsHistory.Entry(30, 10, 3,
+ final NetworkStatsHistory.Entry entry2 = new NetworkStatsHistory.Entry(20, 10, 3,
41, 7, 1, 0);
+ final NetworkStatsHistory.Entry entry3 = new NetworkStatsHistory.Entry(30, 10, 1,
+ 21, 70, 4, 1);
NetworkStatsHistory history1 = new NetworkStatsHistory.Builder(10, 5)
.addEntry(entry1)
.addEntry(entry2)
.build();
-
- NetworkStatsHistory history2 = new NetworkStatsHistory(10, 5);
-
- NetworkStatsCollection actualCollection = new NetworkStatsCollection.Builder(bucketDuration)
+ NetworkStatsHistory history2 = new NetworkStatsHistory.Builder(10, 5)
+ .addEntry(entry2)
+ .addEntry(entry3)
+ .build();
+ NetworkStatsCollection collection = new NetworkStatsCollection.Builder(bucketDuration)
.addEntry(key1, history1)
.addEntry(key2, history2)
.build();
- // The builder will omit any entry with empty history. Thus, history2
- // is not expected in the result collection.
+ // Verify nothing is removed if the cutoff time is equal to bucketStart.
+ collection.removeHistoryBefore(10);
+ final Map<Key, NetworkStatsHistory> expectedEntries = new ArrayMap<>();
expectedEntries.put(key1, history1);
+ expectedEntries.put(key2, history2);
+ assertCollectionEntries(expectedEntries, collection);
- final Map<Key, NetworkStatsHistory> actualEntries = actualCollection.getEntries();
+ // Verify entry1 will be removed if its bucket start before to cutoff timestamp.
+ collection.removeHistoryBefore(11);
+ history1 = new NetworkStatsHistory.Builder(10, 5)
+ .addEntry(entry2)
+ .build();
+ history2 = new NetworkStatsHistory.Builder(10, 5)
+ .addEntry(entry2)
+ .addEntry(entry3)
+ .build();
+ final Map<Key, NetworkStatsHistory> cutoff1Entries1 = new ArrayMap<>();
+ cutoff1Entries1.put(key1, history1);
+ cutoff1Entries1.put(key2, history2);
+ assertCollectionEntries(cutoff1Entries1, collection);
- assertEquals(expectedEntries.size(), actualEntries.size());
- for (Key expectedKey : expectedEntries.keySet()) {
- final NetworkStatsHistory expectedHistory = expectedEntries.get(expectedKey);
+ // Verify entry2 will be removed if its bucket start covers by cutoff timestamp.
+ collection.removeHistoryBefore(22);
+ history2 = new NetworkStatsHistory.Builder(10, 5)
+ .addEntry(entry3)
+ .build();
+ final Map<Key, NetworkStatsHistory> cutoffEntries2 = new ArrayMap<>();
+ // History1 is not expected since the collection will omit empty entries.
+ cutoffEntries2.put(key2, history2);
+ assertCollectionEntries(cutoffEntries2, collection);
- final NetworkStatsHistory actualHistory = actualEntries.get(expectedKey);
- assertNotNull(actualHistory);
-
- assertEquals(expectedHistory.getEntries(), actualHistory.getEntries());
-
- actualEntries.remove(expectedKey);
- }
- assertEquals(0, actualEntries.size());
+ // Verify all entries will be removed if cutoff timestamp covers all.
+ collection.removeHistoryBefore(Long.MAX_VALUE);
+ assertEquals(0, collection.getEntries().size());
}
/**
diff --git a/tests/unit/java/android/net/NetworkStatsHistoryTest.java b/tests/unit/java/android/net/NetworkStatsHistoryTest.java
index c170605..43e331b 100644
--- a/tests/unit/java/android/net/NetworkStatsHistoryTest.java
+++ b/tests/unit/java/android/net/NetworkStatsHistoryTest.java
@@ -56,12 +56,11 @@
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
-import java.util.List;
import java.util.Random;
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class NetworkStatsHistoryTest {
private static final String TAG = "NetworkStatsHistoryTest";
@@ -271,7 +270,7 @@
}
@Test
- public void testRemove() throws Exception {
+ public void testRemoveStartingBefore() throws Exception {
stats = new NetworkStatsHistory(HOUR_IN_MILLIS);
// record some data across 24 buckets
@@ -279,28 +278,28 @@
assertEquals(24, stats.size());
// try removing invalid data; should be no change
- stats.removeBucketsBefore(0 - DAY_IN_MILLIS);
+ stats.removeBucketsStartingBefore(0 - DAY_IN_MILLIS);
assertEquals(24, stats.size());
// try removing far before buckets; should be no change
- stats.removeBucketsBefore(TEST_START - YEAR_IN_MILLIS);
+ stats.removeBucketsStartingBefore(TEST_START - YEAR_IN_MILLIS);
assertEquals(24, stats.size());
// try removing just moments into first bucket; should be no change
- // since that bucket contains data beyond the cutoff
- stats.removeBucketsBefore(TEST_START + SECOND_IN_MILLIS);
+ // since that bucket doesn't contain data starts before the cutoff
+ stats.removeBucketsStartingBefore(TEST_START);
assertEquals(24, stats.size());
// try removing single bucket
- stats.removeBucketsBefore(TEST_START + HOUR_IN_MILLIS);
+ stats.removeBucketsStartingBefore(TEST_START + HOUR_IN_MILLIS);
assertEquals(23, stats.size());
// try removing multiple buckets
- stats.removeBucketsBefore(TEST_START + (4 * HOUR_IN_MILLIS));
+ stats.removeBucketsStartingBefore(TEST_START + (4 * HOUR_IN_MILLIS));
assertEquals(20, stats.size());
// try removing all buckets
- stats.removeBucketsBefore(TEST_START + YEAR_IN_MILLIS);
+ stats.removeBucketsStartingBefore(TEST_START + YEAR_IN_MILLIS);
assertEquals(0, stats.size());
}
@@ -350,7 +349,7 @@
stats.recordData(start, end, entry);
} else {
// trim something
- stats.removeBucketsBefore(r.nextLong());
+ stats.removeBucketsStartingBefore(r.nextLong());
}
}
assertConsistent(stats);
@@ -533,40 +532,6 @@
assertEquals(512L + 4096L, stats.getTotalBytes());
}
- @Test
- public void testBuilder() {
- final NetworkStatsHistory.Entry entry1 = new NetworkStatsHistory.Entry(10, 30, 40,
- 4, 50, 5, 60);
- final NetworkStatsHistory.Entry entry2 = new NetworkStatsHistory.Entry(30, 15, 3,
- 41, 7, 1, 0);
- final NetworkStatsHistory.Entry entry3 = new NetworkStatsHistory.Entry(7, 301, 11,
- 14, 31, 2, 80);
-
- final NetworkStatsHistory statsEmpty = new NetworkStatsHistory
- .Builder(HOUR_IN_MILLIS, 10).build();
- assertEquals(0, statsEmpty.getEntries().size());
- assertEquals(HOUR_IN_MILLIS, statsEmpty.getBucketDuration());
-
- NetworkStatsHistory statsSingle = new NetworkStatsHistory
- .Builder(HOUR_IN_MILLIS, 8)
- .addEntry(entry1)
- .build();
- assertEquals(1, statsSingle.getEntries().size());
- assertEquals(HOUR_IN_MILLIS, statsSingle.getBucketDuration());
- assertEquals(entry1, statsSingle.getEntries().get(0));
-
- NetworkStatsHistory statsMultiple = new NetworkStatsHistory
- .Builder(SECOND_IN_MILLIS, 0)
- .addEntry(entry1).addEntry(entry2).addEntry(entry3)
- .build();
- final List<NetworkStatsHistory.Entry> entries = statsMultiple.getEntries();
- assertEquals(3, entries.size());
- assertEquals(SECOND_IN_MILLIS, statsMultiple.getBucketDuration());
- assertEquals(entry1, entries.get(0));
- assertEquals(entry2, entries.get(1));
- assertEquals(entry3, entries.get(2));
- }
-
private static void assertIndexBeforeAfter(
NetworkStatsHistory stats, int before, int after, long time) {
assertEquals("unexpected before", before, stats.getIndexBefore(time));
diff --git a/tests/unit/java/android/net/NetworkStatsTest.java b/tests/unit/java/android/net/NetworkStatsTest.java
index b0cc16c..6d79869 100644
--- a/tests/unit/java/android/net/NetworkStatsTest.java
+++ b/tests/unit/java/android/net/NetworkStatsTest.java
@@ -61,7 +61,7 @@
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class NetworkStatsTest {
private static final String TEST_IFACE = "test0";
diff --git a/tests/unit/java/android/net/NetworkTemplateTest.kt b/tests/unit/java/android/net/NetworkTemplateTest.kt
index 453612f..3e9662d 100644
--- a/tests/unit/java/android/net/NetworkTemplateTest.kt
+++ b/tests/unit/java/android/net/NetworkTemplateTest.kt
@@ -29,12 +29,8 @@
import android.net.NetworkStats.METERED_NO
import android.net.NetworkStats.METERED_YES
import android.net.NetworkStats.ROAMING_ALL
-import android.net.NetworkTemplate.MATCH_BLUETOOTH
-import android.net.NetworkTemplate.MATCH_CARRIER
-import android.net.NetworkTemplate.MATCH_ETHERNET
import android.net.NetworkTemplate.MATCH_MOBILE
import android.net.NetworkTemplate.MATCH_MOBILE_WILDCARD
-import android.net.NetworkTemplate.MATCH_PROXY
import android.net.NetworkTemplate.MATCH_WIFI
import android.net.NetworkTemplate.MATCH_WIFI_WILDCARD
import android.net.NetworkTemplate.NETWORK_TYPE_ALL
@@ -52,11 +48,9 @@
import android.net.wifi.WifiInfo
import android.os.Build
import android.telephony.TelephonyManager
-import com.android.net.module.util.NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_ALL
import com.android.net.module.util.NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRunner
-import com.android.testutils.SC_V2
import com.android.testutils.assertParcelSane
import org.junit.Before
import org.junit.Test
@@ -65,7 +59,6 @@
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
import kotlin.test.assertEquals
-import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.assertNotEquals
import kotlin.test.assertTrue
@@ -77,7 +70,7 @@
private const val TEST_WIFI_KEY2 = "wifiKey2"
@RunWith(DevSdkIgnoreRunner::class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
class NetworkTemplateTest {
private val mockContext = mock(Context::class.java)
private val mockWifiInfo = mock(WifiInfo::class.java)
@@ -555,140 +548,4 @@
it.assertMatches(identMobileImsi3)
}
}
-
- @DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
- @Test
- fun testBuilderMatchRules() {
- // Verify unknown match rules cannot construct templates.
- listOf(Integer.MIN_VALUE, -1, Integer.MAX_VALUE).forEach {
- assertFailsWith<IllegalArgumentException> {
- NetworkTemplate.Builder(it).build()
- }
- }
-
- // Verify hidden match rules cannot construct templates.
- listOf(MATCH_WIFI_WILDCARD, MATCH_MOBILE_WILDCARD, MATCH_PROXY).forEach {
- assertFailsWith<IllegalArgumentException> {
- NetworkTemplate.Builder(it).build()
- }
- }
-
- // Verify template which matches metered cellular and carrier networks with
- // the given IMSI. See buildTemplateMobileAll and buildTemplateCarrierMetered.
- listOf(MATCH_MOBILE, MATCH_CARRIER).forEach { matchRule ->
- NetworkTemplate.Builder(matchRule).setSubscriberIds(setOf(TEST_IMSI1))
- .setMeteredness(METERED_YES).build().let {
- val expectedTemplate = NetworkTemplate(matchRule, TEST_IMSI1,
- arrayOf(TEST_IMSI1), arrayOf<String>(), METERED_YES,
- ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
- OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_EXACT)
- assertEquals(expectedTemplate, it)
- }
- }
-
- // Verify carrier template cannot be created without IMSI.
- assertFailsWith<IllegalArgumentException> {
- NetworkTemplate.Builder(MATCH_CARRIER).build()
- }
-
- // Verify template which matches metered cellular networks,
- // regardless of IMSI. See buildTemplateMobileWildcard.
- NetworkTemplate.Builder(MATCH_MOBILE).setMeteredness(METERED_YES).build().let {
- val expectedTemplate = NetworkTemplate(MATCH_MOBILE_WILDCARD, null /*subscriberId*/,
- null /*subscriberIds*/, arrayOf<String>(),
- METERED_YES, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
- OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_ALL)
- assertEquals(expectedTemplate, it)
- }
-
- // Verify template which matches metered cellular networks and ratType.
- // See NetworkTemplate#buildTemplateMobileWithRatType.
- NetworkTemplate.Builder(MATCH_MOBILE).setSubscriberIds(setOf(TEST_IMSI1))
- .setMeteredness(METERED_YES).setRatType(TelephonyManager.NETWORK_TYPE_UMTS)
- .build().let {
- val expectedTemplate = NetworkTemplate(MATCH_MOBILE, TEST_IMSI1,
- arrayOf(TEST_IMSI1), arrayOf<String>(), METERED_YES,
- ROAMING_ALL, DEFAULT_NETWORK_ALL, TelephonyManager.NETWORK_TYPE_UMTS,
- OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_EXACT)
- assertEquals(expectedTemplate, it)
- }
-
- // Verify template which matches all wifi networks,
- // regardless of Wifi Network Key. See buildTemplateWifiWildcard and buildTemplateWifi.
- NetworkTemplate.Builder(MATCH_WIFI).build().let {
- val expectedTemplate = NetworkTemplate(MATCH_WIFI_WILDCARD, null /*subscriberId*/,
- null /*subscriberIds*/, arrayOf<String>(),
- METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
- OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_ALL)
- assertEquals(expectedTemplate, it)
- }
-
- // Verify template which matches wifi networks with the given Wifi Network Key.
- // See buildTemplateWifi(wifiNetworkKey).
- NetworkTemplate.Builder(MATCH_WIFI).setWifiNetworkKeys(setOf(TEST_WIFI_KEY1)).build().let {
- val expectedTemplate = NetworkTemplate(MATCH_WIFI, null /*subscriberId*/,
- null /*subscriberIds*/, arrayOf(TEST_WIFI_KEY1),
- METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
- OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_ALL)
- assertEquals(expectedTemplate, it)
- }
-
- // Verify template which matches all wifi networks with the
- // given Wifi Network Key, and IMSI. See buildTemplateWifi(wifiNetworkKey, subscriberId).
- NetworkTemplate.Builder(MATCH_WIFI).setSubscriberIds(setOf(TEST_IMSI1))
- .setWifiNetworkKeys(setOf(TEST_WIFI_KEY1)).build().let {
- val expectedTemplate = NetworkTemplate(MATCH_WIFI, TEST_IMSI1,
- arrayOf(TEST_IMSI1), arrayOf(TEST_WIFI_KEY1),
- METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
- OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_EXACT)
- assertEquals(expectedTemplate, it)
- }
-
- // Verify template which matches ethernet and bluetooth networks.
- // See buildTemplateEthernet and buildTemplateBluetooth.
- listOf(MATCH_ETHERNET, MATCH_BLUETOOTH).forEach { matchRule ->
- NetworkTemplate.Builder(matchRule).build().let {
- val expectedTemplate = NetworkTemplate(matchRule, null /*subscriberId*/,
- null /*subscriberIds*/, arrayOf<String>(),
- METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
- OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_ALL)
- assertEquals(expectedTemplate, it)
- }
- }
- }
-
- @DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
- @Test
- fun testBuilderWifiNetworkKeys() {
- // Verify template builder which generates same template with the given different
- // sequence keys.
- NetworkTemplate.Builder(MATCH_WIFI).setWifiNetworkKeys(
- setOf(TEST_WIFI_KEY1, TEST_WIFI_KEY2)).build().let {
- val expectedTemplate = NetworkTemplate.Builder(MATCH_WIFI).setWifiNetworkKeys(
- setOf(TEST_WIFI_KEY2, TEST_WIFI_KEY1)).build()
- assertEquals(expectedTemplate, it)
- }
-
- // Verify template which matches non-wifi networks with the given key is invalid.
- listOf(MATCH_MOBILE, MATCH_CARRIER, MATCH_ETHERNET, MATCH_BLUETOOTH, -1,
- Integer.MAX_VALUE).forEach { matchRule ->
- assertFailsWith<IllegalArgumentException> {
- NetworkTemplate.Builder(matchRule).setWifiNetworkKeys(setOf(TEST_WIFI_KEY1)).build()
- }
- }
-
- // Verify template which matches wifi networks with the given null key is invalid.
- assertFailsWith<IllegalArgumentException> {
- NetworkTemplate.Builder(MATCH_WIFI).setWifiNetworkKeys(setOf(null)).build()
- }
-
- // Verify template which matches wifi wildcard with the given empty key set.
- NetworkTemplate.Builder(MATCH_WIFI).setWifiNetworkKeys(setOf<String>()).build().let {
- val expectedTemplate = NetworkTemplate(MATCH_WIFI_WILDCARD, null /*subscriberId*/,
- arrayOf<String>() /*subscriberIds*/, arrayOf<String>(),
- METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
- OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_ALL)
- assertEquals(expectedTemplate, it)
- }
- }
}
diff --git a/tests/unit/java/android/net/QosSocketFilterTest.java b/tests/unit/java/android/net/QosSocketFilterTest.java
index 91f2cdd..6820b40 100644
--- a/tests/unit/java/android/net/QosSocketFilterTest.java
+++ b/tests/unit/java/android/net/QosSocketFilterTest.java
@@ -16,8 +16,17 @@
package android.net;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
+import static android.net.QosCallbackException.EX_TYPE_FILTER_NONE;
+import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED;
+import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_NOT_BOUND;
+import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_NOT_CONNECTED;
+import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED;
+import static android.system.OsConstants.IPPROTO_TCP;
+import static android.system.OsConstants.IPPROTO_UDP;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import android.os.Build;
@@ -29,6 +38,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
@@ -36,14 +46,14 @@
@SmallTest
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
public class QosSocketFilterTest {
-
+ private static final int TEST_NET_ID = 1777;
+ private final Network mNetwork = new Network(TEST_NET_ID);
@Test
public void testPortExactMatch() {
final InetAddress addressA = InetAddresses.parseNumericAddress("1.2.3.4");
final InetAddress addressB = InetAddresses.parseNumericAddress("1.2.3.4");
assertTrue(QosSocketFilter.matchesAddress(
new InetSocketAddress(addressA, 10), addressB, 10, 10));
-
}
@Test
@@ -77,5 +87,90 @@
assertFalse(QosSocketFilter.matchesAddress(
new InetSocketAddress(addressA, 10), addressB, 10, 10));
}
+
+ @Test
+ public void testAddressMatchWithAnyLocalAddresses() {
+ final InetAddress addressA = InetAddresses.parseNumericAddress("1.2.3.4");
+ final InetAddress addressB = InetAddresses.parseNumericAddress("0.0.0.0");
+ assertTrue(QosSocketFilter.matchesAddress(
+ new InetSocketAddress(addressA, 10), addressB, 10, 10));
+ assertFalse(QosSocketFilter.matchesAddress(
+ new InetSocketAddress(addressB, 10), addressA, 10, 10));
+ }
+
+ @Test
+ public void testProtocolMatch() throws Exception {
+ DatagramSocket socket = new DatagramSocket(new InetSocketAddress("127.0.0.1", 0));
+ socket.connect(new InetSocketAddress("127.0.0.1", socket.getLocalPort() + 10));
+ DatagramSocket socketV6 = new DatagramSocket(new InetSocketAddress("::1", 0));
+ socketV6.connect(new InetSocketAddress("::1", socketV6.getLocalPort() + 10));
+ QosSocketInfo socketInfo = new QosSocketInfo(mNetwork, socket);
+ QosSocketFilter socketFilter = new QosSocketFilter(socketInfo);
+ QosSocketInfo socketInfo6 = new QosSocketInfo(mNetwork, socketV6);
+ QosSocketFilter socketFilter6 = new QosSocketFilter(socketInfo6);
+ assertTrue(socketFilter.matchesProtocol(IPPROTO_UDP));
+ assertTrue(socketFilter6.matchesProtocol(IPPROTO_UDP));
+ assertFalse(socketFilter.matchesProtocol(IPPROTO_TCP));
+ assertFalse(socketFilter6.matchesProtocol(IPPROTO_TCP));
+ socket.close();
+ socketV6.close();
+ }
+
+ @Test
+ public void testValidate() throws Exception {
+ DatagramSocket socket = new DatagramSocket(new InetSocketAddress("127.0.0.1", 0));
+ socket.connect(new InetSocketAddress("127.0.0.1", socket.getLocalPort() + 7));
+ DatagramSocket socketV6 = new DatagramSocket(new InetSocketAddress("::1", 0));
+
+ QosSocketInfo socketInfo = new QosSocketInfo(mNetwork, socket);
+ QosSocketFilter socketFilter = new QosSocketFilter(socketInfo);
+ QosSocketInfo socketInfo6 = new QosSocketInfo(mNetwork, socketV6);
+ QosSocketFilter socketFilter6 = new QosSocketFilter(socketInfo6);
+ assertEquals(EX_TYPE_FILTER_NONE, socketFilter.validate());
+ assertEquals(EX_TYPE_FILTER_NONE, socketFilter6.validate());
+ socket.close();
+ socketV6.close();
+ }
+
+ @Test
+ public void testValidateUnbind() throws Exception {
+ DatagramSocket socket;
+ socket = new DatagramSocket(null);
+ QosSocketInfo socketInfo = new QosSocketInfo(mNetwork, socket);
+ QosSocketFilter socketFilter = new QosSocketFilter(socketInfo);
+ assertEquals(EX_TYPE_FILTER_SOCKET_NOT_BOUND, socketFilter.validate());
+ socket.close();
+ }
+
+ @Test
+ public void testValidateLocalAddressChanged() throws Exception {
+ DatagramSocket socket = new DatagramSocket(null);
+ DatagramSocket socket6 = new DatagramSocket(null);
+ QosSocketInfo socketInfo = new QosSocketInfo(mNetwork, socket);
+ QosSocketFilter socketFilter = new QosSocketFilter(socketInfo);
+ QosSocketInfo socketInfo6 = new QosSocketInfo(mNetwork, socket6);
+ QosSocketFilter socketFilter6 = new QosSocketFilter(socketInfo6);
+ socket.bind(new InetSocketAddress("127.0.0.1", 0));
+ socket6.bind(new InetSocketAddress("::1", 0));
+ assertEquals(EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED, socketFilter.validate());
+ assertEquals(EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED, socketFilter6.validate());
+ socket.close();
+ socket6.close();
+ }
+
+ @Test
+ public void testValidateRemoteAddressChanged() throws Exception {
+ DatagramSocket socket;
+ socket = new DatagramSocket(new InetSocketAddress("127.0.0.1", 53137));
+ socket.connect(new InetSocketAddress("127.0.0.1", socket.getLocalPort() + 11));
+ QosSocketInfo socketInfo = new QosSocketInfo(mNetwork, socket);
+ QosSocketFilter socketFilter = new QosSocketFilter(socketInfo);
+ assertEquals(EX_TYPE_FILTER_NONE, socketFilter.validate());
+ socket.disconnect();
+ assertEquals(EX_TYPE_FILTER_SOCKET_NOT_CONNECTED, socketFilter.validate());
+ socket.connect(new InetSocketAddress("127.0.0.1", socket.getLocalPort() + 13));
+ assertEquals(EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED, socketFilter.validate());
+ socket.close();
+ }
}
diff --git a/tests/unit/java/android/net/QosSocketInfoTest.java b/tests/unit/java/android/net/QosSocketInfoTest.java
new file mode 100644
index 0000000..749c182
--- /dev/null
+++ b/tests/unit/java/android/net/QosSocketInfoTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static android.system.OsConstants.SOCK_DGRAM;
+import static android.system.OsConstants.SOCK_STREAM;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import android.os.Build;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.net.DatagramSocket;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+@RunWith(DevSdkIgnoreRunner.class)
+@SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+public class QosSocketInfoTest {
+ @Mock
+ private Network mMockNetwork = mock(Network.class);
+
+ @Test
+ public void testConstructWithSock() throws Exception {
+ ServerSocket server = new ServerSocket();
+ ServerSocket server6 = new ServerSocket();
+
+ InetSocketAddress clientAddr = new InetSocketAddress("127.0.0.1", 0);
+ InetSocketAddress serverAddr = new InetSocketAddress("127.0.0.1", 0);
+ InetSocketAddress clientAddr6 = new InetSocketAddress("::1", 0);
+ InetSocketAddress serverAddr6 = new InetSocketAddress("::1", 0);
+ server.bind(serverAddr);
+ server6.bind(serverAddr6);
+ Socket socket = new Socket(serverAddr.getAddress(), server.getLocalPort(),
+ clientAddr.getAddress(), clientAddr.getPort());
+ Socket socket6 = new Socket(serverAddr6.getAddress(), server6.getLocalPort(),
+ clientAddr6.getAddress(), clientAddr6.getPort());
+ QosSocketInfo sockInfo = new QosSocketInfo(mMockNetwork, socket);
+ QosSocketInfo sockInfo6 = new QosSocketInfo(mMockNetwork, socket6);
+ assertTrue(sockInfo.getLocalSocketAddress()
+ .equals(new InetSocketAddress(socket.getLocalAddress(), socket.getLocalPort())));
+ assertTrue(sockInfo.getRemoteSocketAddress()
+ .equals((InetSocketAddress) socket.getRemoteSocketAddress()));
+ assertEquals(SOCK_STREAM, sockInfo.getSocketType());
+ assertTrue(sockInfo6.getLocalSocketAddress()
+ .equals(new InetSocketAddress(socket6.getLocalAddress(), socket6.getLocalPort())));
+ assertTrue(sockInfo6.getRemoteSocketAddress()
+ .equals((InetSocketAddress) socket6.getRemoteSocketAddress()));
+ assertEquals(SOCK_STREAM, sockInfo6.getSocketType());
+ socket.close();
+ socket6.close();
+ server.close();
+ server6.close();
+ }
+
+ @Test
+ public void testConstructWithDatagramSock() throws Exception {
+ InetSocketAddress clientAddr = new InetSocketAddress("127.0.0.1", 0);
+ InetSocketAddress serverAddr = new InetSocketAddress("127.0.0.1", 0);
+ InetSocketAddress clientAddr6 = new InetSocketAddress("::1", 0);
+ InetSocketAddress serverAddr6 = new InetSocketAddress("::1", 0);
+ DatagramSocket socket = new DatagramSocket(null);
+ socket.setReuseAddress(true);
+ socket.bind(clientAddr);
+ socket.connect(serverAddr);
+ DatagramSocket socket6 = new DatagramSocket(null);
+ socket6.setReuseAddress(true);
+ socket6.bind(clientAddr);
+ socket6.connect(serverAddr);
+ QosSocketInfo sockInfo = new QosSocketInfo(mMockNetwork, socket);
+ QosSocketInfo sockInfo6 = new QosSocketInfo(mMockNetwork, socket6);
+ assertTrue(sockInfo.getLocalSocketAddress()
+ .equals((InetSocketAddress) socket.getLocalSocketAddress()));
+ assertTrue(sockInfo.getRemoteSocketAddress()
+ .equals((InetSocketAddress) socket.getRemoteSocketAddress()));
+ assertEquals(SOCK_DGRAM, sockInfo.getSocketType());
+ assertTrue(sockInfo6.getLocalSocketAddress()
+ .equals((InetSocketAddress) socket6.getLocalSocketAddress()));
+ assertTrue(sockInfo6.getRemoteSocketAddress()
+ .equals((InetSocketAddress) socket6.getRemoteSocketAddress()));
+ assertEquals(SOCK_DGRAM, sockInfo6.getSocketType());
+ socket.close();
+ }
+}
diff --git a/tests/unit/java/android/net/netstats/NetworkStatsDataMigrationUtilsTest.kt b/tests/unit/java/android/net/netstats/NetworkStatsDataMigrationUtilsTest.kt
index 743d39e..aa5a246 100644
--- a/tests/unit/java/android/net/netstats/NetworkStatsDataMigrationUtilsTest.kt
+++ b/tests/unit/java/android/net/netstats/NetworkStatsDataMigrationUtilsTest.kt
@@ -61,14 +61,6 @@
assertValues(builder.build(), 55, 1814302L, 21050L, 31001636L, 26152L)
}
- @Test
- fun testMaybeReadLegacyUid() {
- val builder = NetworkStatsCollection.Builder(BUCKET_DURATION_MS)
- NetworkStatsDataMigrationUtils.readLegacyUid(builder,
- getInputStreamForResource(R.raw.netstats_uid_v4), false /* taggedData */)
- assertValues(builder.build(), 223, 106245210L, 710722L, 1130647496L, 1103989L)
- }
-
private fun assertValues(
collection: NetworkStatsCollection,
expectedSize: Int,
diff --git a/tests/unit/java/android/net/nsd/NsdManagerTest.java b/tests/unit/java/android/net/nsd/NsdManagerTest.java
index 30b8fcd..32274bc 100644
--- a/tests/unit/java/android/net/nsd/NsdManagerTest.java
+++ b/tests/unit/java/android/net/nsd/NsdManagerTest.java
@@ -51,7 +51,7 @@
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class NsdManagerTest {
static final int PROTOCOL = NsdManager.PROTOCOL_DNS_SD;
diff --git a/tests/unit/java/android/net/nsd/NsdServiceInfoTest.java b/tests/unit/java/android/net/nsd/NsdServiceInfoTest.java
index e5e7ebc..64355ed 100644
--- a/tests/unit/java/android/net/nsd/NsdServiceInfoTest.java
+++ b/tests/unit/java/android/net/nsd/NsdServiceInfoTest.java
@@ -42,7 +42,7 @@
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class NsdServiceInfoTest {
public final static InetAddress LOCALHOST;
@@ -125,6 +125,7 @@
fullInfo.setPort(4242);
fullInfo.setHost(LOCALHOST);
fullInfo.setNetwork(new Network(123));
+ fullInfo.setInterfaceIndex(456);
checkParcelable(fullInfo);
NsdServiceInfo noHostInfo = new NsdServiceInfo();
@@ -175,6 +176,7 @@
assertEquals(original.getHost(), result.getHost());
assertTrue(original.getPort() == result.getPort());
assertEquals(original.getNetwork(), result.getNetwork());
+ assertEquals(original.getInterfaceIndex(), result.getInterfaceIndex());
// Assert equality of attribute map.
Map<String, byte[]> originalMap = original.getAttributes();
diff --git a/tests/unit/java/com/android/internal/net/VpnProfileTest.java b/tests/unit/java/com/android/internal/net/VpnProfileTest.java
index 943a559..0a6d2f2 100644
--- a/tests/unit/java/com/android/internal/net/VpnProfileTest.java
+++ b/tests/unit/java/com/android/internal/net/VpnProfileTest.java
@@ -16,6 +16,9 @@
package com.android.internal.net;
+import static android.net.cts.util.IkeSessionTestUtils.CHILD_PARAMS;
+import static android.net.cts.util.IkeSessionTestUtils.IKE_PARAMS_V4;
+
import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
import static com.android.testutils.ParcelUtils.assertParcelSane;
@@ -26,6 +29,7 @@
import static org.junit.Assert.assertTrue;
import android.net.IpSecAlgorithm;
+import android.net.ipsec.ike.IkeTunnelConnectionParams;
import android.os.Build;
import androidx.test.filters.SmallTest;
@@ -85,7 +89,8 @@
private VpnProfile getSampleIkev2Profile(String key) {
final VpnProfile p = new VpnProfile(key, true /* isRestrictedToTestNetworks */,
- false /* excludesLocalRoutes */, true /* requiresPlatformValidation */);
+ false /* excludesLocalRoutes */, true /* requiresPlatformValidation */,
+ null /* ikeTunConnParams */);
p.name = "foo";
p.type = VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS;
@@ -120,6 +125,35 @@
return p;
}
+ private VpnProfile getSampleIkev2ProfileWithIkeTunConnParams(String key) {
+ final VpnProfile p = new VpnProfile(key, true /* isRestrictedToTestNetworks */,
+ false /* excludesLocalRoutes */, true /* requiresPlatformValidation */,
+ new IkeTunnelConnectionParams(IKE_PARAMS_V4, CHILD_PARAMS));
+
+ p.name = "foo";
+ p.server = "bar";
+ p.dnsServers = "8.8.8.8";
+ p.searchDomains = "";
+ p.routes = "0.0.0.0/0";
+ p.mppe = false;
+ p.proxy = null;
+ p.setAllowedAlgorithms(
+ Arrays.asList(
+ IpSecAlgorithm.AUTH_CRYPT_AES_GCM,
+ IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305,
+ IpSecAlgorithm.AUTH_HMAC_SHA512,
+ IpSecAlgorithm.CRYPT_AES_CBC));
+ p.isBypassable = true;
+ p.isMetered = true;
+ p.maxMtu = 1350;
+ p.areAuthParamsInline = true;
+
+ // Not saved, but also not compared.
+ p.saveLogin = true;
+
+ return p;
+ }
+
@Test
public void testEquals() {
assertEquals(
@@ -134,13 +168,21 @@
public void testParcelUnparcel() {
if (isAtLeastT()) {
// excludeLocalRoutes, requiresPlatformValidation were added in T.
- assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 25);
+ assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 26);
+ assertParcelSane(getSampleIkev2ProfileWithIkeTunConnParams(DUMMY_PROFILE_KEY), 26);
} else {
assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 23);
}
}
@Test
+ public void testEncodeDecodeWithIkeTunConnParams() {
+ final VpnProfile profile = getSampleIkev2ProfileWithIkeTunConnParams(DUMMY_PROFILE_KEY);
+ final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, profile.encode());
+ assertEquals(profile, decoded);
+ }
+
+ @Test
public void testEncodeDecode() {
final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, profile.encode());
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 025b28c..0c5420d 100644
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -51,6 +51,17 @@
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.EXTRA_NETWORK_INFO;
import static android.net.ConnectivityManager.EXTRA_NETWORK_TYPE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOCKDOWN_VPN;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_1;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_2;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY;
+import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
+import static android.net.ConnectivityManager.FIREWALL_RULE_DEFAULT;
+import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK;
@@ -104,6 +115,10 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P;
import static android.net.NetworkCapabilities.NET_CAPABILITY_XCAP;
import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_2;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_3;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_4;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_5;
import static android.net.NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION;
import static android.net.NetworkCapabilities.REDACT_FOR_LOCAL_MAC_ADDRESS;
import static android.net.NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS;
@@ -136,6 +151,8 @@
import static com.android.server.ConnectivityService.PREFERENCE_ORDER_PROFILE;
import static com.android.server.ConnectivityService.PREFERENCE_ORDER_VPN;
import static com.android.server.ConnectivityServiceTestUtils.transportToLegacyType;
+import static com.android.server.NetworkAgentWrapper.CallbackType.OnQosCallbackRegister;
+import static com.android.server.NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister;
import static com.android.testutils.ConcurrentUtils.await;
import static com.android.testutils.ConcurrentUtils.durationOf;
import static com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
@@ -159,8 +176,8 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.AdditionalMatchers.aryEq;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyLong;
@@ -194,6 +211,7 @@
import android.app.AppOpsManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.app.admin.DevicePolicyManager;
import android.app.usage.NetworkStatsManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -337,10 +355,13 @@
import com.android.net.module.util.ArrayTrackRecord;
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.LocationPermissionChecker;
+import com.android.networkstack.apishim.NetworkAgentConfigShimImpl;
+import com.android.networkstack.apishim.api29.ConstantsShim;
import com.android.server.ConnectivityService.ConnectivityDiagnosticsCallbackInfo;
import com.android.server.ConnectivityService.NetworkRequestInfo;
import com.android.server.ConnectivityServiceTest.ConnectivityServiceDependencies.ReportedInterfaces;
import com.android.server.connectivity.CarrierPrivilegeAuthenticator;
+import com.android.server.connectivity.ClatCoordinator;
import com.android.server.connectivity.ConnectivityFlags;
import com.android.server.connectivity.MockableSystemProperties;
import com.android.server.connectivity.Nat464Xlat;
@@ -463,6 +484,9 @@
private static final int TEST_APP_ID_2 = 104;
private static final int TEST_WORK_PROFILE_APP_UID_2 =
UserHandle.getUid(TEST_WORK_PROFILE_USER_ID, TEST_APP_ID_2);
+ private static final int TEST_APP_ID_3 = 105;
+ private static final int TEST_APP_ID_4 = 106;
+ private static final int TEST_APP_ID_5 = 107;
private static final String CLAT_PREFIX = "v4-";
private static final String MOBILE_IFNAME = "test_rmnet_data0";
@@ -538,7 +562,9 @@
@Mock NetworkPolicyManager mNetworkPolicyManager;
@Mock VpnProfileStore mVpnProfileStore;
@Mock SystemConfigManager mSystemConfigManager;
+ @Mock DevicePolicyManager mDevicePolicyManager;
@Mock Resources mResources;
+ @Mock ClatCoordinator mClatCoordinator;
@Mock PacProxyManager mPacProxyManager;
@Mock BpfNetMaps mBpfNetMaps;
@Mock CarrierPrivilegeAuthenticator mCarrierPrivilegeAuthenticator;
@@ -641,7 +667,8 @@
@Override
public ComponentName startService(Intent service) {
final String action = service.getAction();
- if (!VpnConfig.SERVICE_INTERFACE.equals(action)) {
+ if (!VpnConfig.SERVICE_INTERFACE.equals(action)
+ && !ConstantsShim.ACTION_VPN_MANAGER_EVENT.equals(action)) {
fail("Attempt to start unknown service, action=" + action);
}
return new ComponentName(service.getPackage(), "com.android.test.Service");
@@ -658,6 +685,7 @@
if (Context.TELEPHONY_SERVICE.equals(name)) return mTelephonyManager;
if (Context.ETHERNET_SERVICE.equals(name)) return mEthernetManager;
if (Context.NETWORK_POLICY_SERVICE.equals(name)) return mNetworkPolicyManager;
+ if (Context.DEVICE_POLICY_SERVICE.equals(name)) return mDevicePolicyManager;
if (Context.SYSTEM_CONFIG_SERVICE.equals(name)) return mSystemConfigManager;
if (Context.NETWORK_STATS_SERVICE.equals(name)) return mStatsManager;
if (Context.BATTERY_STATS_SERVICE.equals(name)) return mBatteryStatsManager;
@@ -686,6 +714,14 @@
doReturn(value).when(mUserManager).isManagedProfile(eq(userHandle.getIdentifier()));
}
+ public void setDeviceOwner(@NonNull final UserHandle userHandle, String value) {
+ // This relies on all contexts for a given user returning the same UM mock
+ final DevicePolicyManager dpmMock = createContextAsUser(userHandle, 0 /* flags */)
+ .getSystemService(DevicePolicyManager.class);
+ doReturn(value).when(dpmMock).getDeviceOwner();
+ doReturn(value).when(mDevicePolicyManager).getDeviceOwner();
+ }
+
@Override
public ContentResolver getContentResolver() {
return mContentResolver;
@@ -780,6 +816,32 @@
}
}
+ // This was only added in the T SDK, but this test needs to build against the R+S SDKs, too.
+ private static int toSdkSandboxUid(int appUid) {
+ final int firstSdkSandboxUid = 20000;
+ return appUid + (firstSdkSandboxUid - Process.FIRST_APPLICATION_UID);
+ }
+
+ // This function assumes the UID range for user 0 ([1, 99999])
+ private static UidRangeParcel[] uidRangeParcelsExcludingUids(Integer... excludedUids) {
+ int start = 1;
+ Arrays.sort(excludedUids);
+ List<UidRangeParcel> parcels = new ArrayList<UidRangeParcel>();
+ for (int excludedUid : excludedUids) {
+ if (excludedUid == start) {
+ start++;
+ } else {
+ parcels.add(new UidRangeParcel(start, excludedUid - 1));
+ start = excludedUid + 1;
+ }
+ }
+ if (start <= 99999) {
+ parcels.add(new UidRangeParcel(start, 99999));
+ }
+
+ return parcels.toArray(new UidRangeParcel[0]);
+ }
+
private void waitForIdle() {
HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
waitForIdle(mCellNetworkAgent, TIMEOUT_MS);
@@ -1373,6 +1435,10 @@
return (mMockNetworkAgent == null) ? null : mMockNetworkAgent.getNetwork();
}
+ public NetworkAgentConfig getNetworkAgentConfig() {
+ return null == mMockNetworkAgent ? null : mMockNetworkAgent.getNetworkAgentConfig();
+ }
+
@Override
public int getActiveVpnType() {
return mVpnType;
@@ -1999,6 +2065,11 @@
return mBpfNetMaps;
}
+ @Override
+ public ClatCoordinator getClatCoordinator(INetd netd) {
+ return mClatCoordinator;
+ }
+
final ArrayTrackRecord<Pair<String, Long>> mRateLimitHistory = new ArrayTrackRecord<>();
final Map<String, Long> mActiveRateLimit = new HashMap<>();
@@ -2936,6 +3007,7 @@
@Test
public void testRequiresValidation() {
assertTrue(NetworkMonitorUtils.isValidationRequired(
+ NetworkAgentConfigShimImpl.newInstance(null),
mCm.getDefaultRequest().networkCapabilities));
}
@@ -5845,7 +5917,7 @@
}
/**
- * Validate the callback flow CBS request without carrier privilege.
+ * Validate the service throws if request with CBS but without carrier privilege.
*/
@Test
public void testCBSRequestWithoutCarrierPrivilege() throws Exception {
@@ -5854,10 +5926,8 @@
final TestNetworkCallback networkCallback = new TestNetworkCallback();
mServiceContext.setPermission(CONNECTIVITY_USE_RESTRICTED_NETWORKS, PERMISSION_DENIED);
- // Now file the test request and expect it.
- mCm.requestNetwork(nr, networkCallback);
- networkCallback.expectCallback(CallbackEntry.UNAVAILABLE, (Network) null);
- mCm.unregisterNetworkCallback(networkCallback);
+ // Now file the test request and expect the service throws.
+ assertThrows(SecurityException.class, () -> mCm.requestNetwork(nr, networkCallback));
}
private static class TestKeepaliveCallback extends PacketKeepaliveCallback {
@@ -7933,6 +8003,7 @@
// VPN networks do not satisfy the default request and are automatically validated
// by NetworkMonitor
assertFalse(NetworkMonitorUtils.isValidationRequired(
+ NetworkAgentConfigShimImpl.newInstance(mMockVpn.getNetworkAgentConfig()),
mMockVpn.getAgent().getNetworkCapabilities()));
mMockVpn.getAgent().setNetworkValid(false /* isStrictMode */);
@@ -8083,6 +8154,7 @@
assertTrue(nc.hasCapability(NET_CAPABILITY_INTERNET));
assertFalse(NetworkMonitorUtils.isValidationRequired(
+ NetworkAgentConfigShimImpl.newInstance(mMockVpn.getNetworkAgentConfig()),
mMockVpn.getAgent().getNetworkCapabilities()));
assertTrue(NetworkMonitorUtils.isPrivateDnsValidationRequired(
mMockVpn.getAgent().getNetworkCapabilities()));
@@ -8981,10 +9053,16 @@
allowList);
waitForIdle();
- UidRangeParcel firstHalf = new UidRangeParcel(1, VPN_UID - 1);
- UidRangeParcel secondHalf = new UidRangeParcel(VPN_UID + 1, 99999);
+ final Set<Integer> excludedUids = new ArraySet<Integer>();
+ excludedUids.add(VPN_UID);
+ if (SdkLevel.isAtLeastT()) {
+ // On T onwards, the corresponding SDK sandbox UID should also be excluded
+ excludedUids.add(toSdkSandboxUid(VPN_UID));
+ }
+ final UidRangeParcel[] uidRangeParcels = uidRangeParcelsExcludingUids(
+ excludedUids.toArray(new Integer[0]));
InOrder inOrder = inOrder(mMockNetd);
- expectNetworkRejectNonSecureVpn(inOrder, true, firstHalf, secondHalf);
+ expectNetworkRejectNonSecureVpn(inOrder, true, uidRangeParcels);
// Connect a network when lockdown is active, expect to see it blocked.
mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
@@ -9008,7 +9086,7 @@
vpnUidCallback.assertNoCallback();
vpnUidDefaultCallback.assertNoCallback();
vpnDefaultCallbackAsUid.assertNoCallback();
- expectNetworkRejectNonSecureVpn(inOrder, false, firstHalf, secondHalf);
+ expectNetworkRejectNonSecureVpn(inOrder, false, uidRangeParcels);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
@@ -9025,13 +9103,14 @@
vpnUidDefaultCallback.assertNoCallback();
vpnDefaultCallbackAsUid.assertNoCallback();
- // The following requires that the UID of this test package is greater than VPN_UID. This
- // is always true in practice because a plain AOSP build with no apps installed has almost
- // 200 packages installed.
- final UidRangeParcel piece1 = new UidRangeParcel(1, VPN_UID - 1);
- final UidRangeParcel piece2 = new UidRangeParcel(VPN_UID + 1, uid - 1);
- final UidRangeParcel piece3 = new UidRangeParcel(uid + 1, 99999);
- expectNetworkRejectNonSecureVpn(inOrder, true, piece1, piece2, piece3);
+ excludedUids.add(uid);
+ if (SdkLevel.isAtLeastT()) {
+ // On T onwards, the corresponding SDK sandbox UID should also be excluded
+ excludedUids.add(toSdkSandboxUid(uid));
+ }
+ final UidRangeParcel[] uidRangeParcelsAlsoExcludingUs = uidRangeParcelsExcludingUids(
+ excludedUids.toArray(new Integer[0]));
+ expectNetworkRejectNonSecureVpn(inOrder, true, uidRangeParcelsAlsoExcludingUs);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
@@ -9057,12 +9136,12 @@
// Everything should now be blocked.
mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
waitForIdle();
- expectNetworkRejectNonSecureVpn(inOrder, false, piece1, piece2, piece3);
+ expectNetworkRejectNonSecureVpn(inOrder, false, uidRangeParcelsAlsoExcludingUs);
allowList.clear();
mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */,
allowList);
waitForIdle();
- expectNetworkRejectNonSecureVpn(inOrder, true, firstHalf, secondHalf);
+ expectNetworkRejectNonSecureVpn(inOrder, true, uidRangeParcels);
defaultCallback.expectBlockedStatusCallback(true, mWiFiNetworkAgent);
assertBlockedCallbackInAnyOrder(callback, true, mWiFiNetworkAgent, mCellNetworkAgent);
vpnUidCallback.assertNoCallback();
@@ -9434,6 +9513,135 @@
b2.expectBroadcast();
}
+ @Test @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testLockdownSetFirewallUidRule() throws Exception {
+ // For ConnectivityService#setAlwaysOnVpnPackage.
+ mServiceContext.setPermission(
+ Manifest.permission.CONTROL_ALWAYS_ON_VPN, PERMISSION_GRANTED);
+ // Needed to call Vpn#setAlwaysOnPackage.
+ mServiceContext.setPermission(Manifest.permission.CONTROL_VPN, PERMISSION_GRANTED);
+ // Needed to call Vpn#isAlwaysOnPackageSupported.
+ mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED);
+
+ // Enable Lockdown
+ final ArrayList<String> allowList = new ArrayList<>();
+ mVpnManagerService.setAlwaysOnVpnPackage(PRIMARY_USER, ALWAYS_ON_PACKAGE,
+ true /* lockdown */, allowList);
+ waitForIdle();
+
+ // Lockdown rule is set to apps uids
+ verify(mBpfNetMaps).setUidRule(
+ eq(FIREWALL_CHAIN_LOCKDOWN_VPN), eq(APP1_UID), eq(FIREWALL_RULE_DENY));
+ verify(mBpfNetMaps).setUidRule(
+ eq(FIREWALL_CHAIN_LOCKDOWN_VPN), eq(APP2_UID), eq(FIREWALL_RULE_DENY));
+
+ reset(mBpfNetMaps);
+
+ // Disable lockdown
+ mVpnManagerService.setAlwaysOnVpnPackage(PRIMARY_USER, null, false /* lockdown */,
+ allowList);
+ waitForIdle();
+
+ // Lockdown rule is removed from apps uids
+ verify(mBpfNetMaps).setUidRule(
+ eq(FIREWALL_CHAIN_LOCKDOWN_VPN), eq(APP1_UID), eq(FIREWALL_RULE_ALLOW));
+ verify(mBpfNetMaps).setUidRule(
+ eq(FIREWALL_CHAIN_LOCKDOWN_VPN), eq(APP2_UID), eq(FIREWALL_RULE_ALLOW));
+
+ // Interface rules are not changed by Lockdown mode enable/disable
+ verify(mBpfNetMaps, never()).addUidInterfaceRules(any(), any());
+ verify(mBpfNetMaps, never()).removeUidInterfaceRules(any());
+ }
+
+ private void doTestSetUidFirewallRule(final int chain, final int defaultRule) {
+ final int uid = 1001;
+ mCm.setUidFirewallRule(chain, uid, FIREWALL_RULE_ALLOW);
+ verify(mBpfNetMaps).setUidRule(chain, uid, FIREWALL_RULE_ALLOW);
+ reset(mBpfNetMaps);
+
+ mCm.setUidFirewallRule(chain, uid, FIREWALL_RULE_DENY);
+ verify(mBpfNetMaps).setUidRule(chain, uid, FIREWALL_RULE_DENY);
+ reset(mBpfNetMaps);
+
+ mCm.setUidFirewallRule(chain, uid, FIREWALL_RULE_DEFAULT);
+ verify(mBpfNetMaps).setUidRule(chain, uid, defaultRule);
+ reset(mBpfNetMaps);
+ }
+
+ @Test @IgnoreUpTo(SC_V2)
+ public void testSetUidFirewallRule() throws Exception {
+ doTestSetUidFirewallRule(FIREWALL_CHAIN_DOZABLE, FIREWALL_RULE_DENY);
+ doTestSetUidFirewallRule(FIREWALL_CHAIN_STANDBY, FIREWALL_RULE_ALLOW);
+ doTestSetUidFirewallRule(FIREWALL_CHAIN_POWERSAVE, FIREWALL_RULE_DENY);
+ doTestSetUidFirewallRule(FIREWALL_CHAIN_RESTRICTED, FIREWALL_RULE_DENY);
+ doTestSetUidFirewallRule(FIREWALL_CHAIN_LOW_POWER_STANDBY, FIREWALL_RULE_DENY);
+ doTestSetUidFirewallRule(FIREWALL_CHAIN_OEM_DENY_1, FIREWALL_RULE_ALLOW);
+ doTestSetUidFirewallRule(FIREWALL_CHAIN_OEM_DENY_2, FIREWALL_RULE_ALLOW);
+ }
+
+ @Test @IgnoreUpTo(SC_V2)
+ public void testSetFirewallChainEnabled() throws Exception {
+ final List<Integer> firewallChains = Arrays.asList(
+ FIREWALL_CHAIN_DOZABLE,
+ FIREWALL_CHAIN_STANDBY,
+ FIREWALL_CHAIN_POWERSAVE,
+ FIREWALL_CHAIN_RESTRICTED,
+ FIREWALL_CHAIN_LOW_POWER_STANDBY,
+ FIREWALL_CHAIN_OEM_DENY_1,
+ FIREWALL_CHAIN_OEM_DENY_2);
+ for (final int chain: firewallChains) {
+ mCm.setFirewallChainEnabled(chain, true /* enabled */);
+ verify(mBpfNetMaps).setChildChain(chain, true /* enable */);
+ reset(mBpfNetMaps);
+
+ mCm.setFirewallChainEnabled(chain, false /* enabled */);
+ verify(mBpfNetMaps).setChildChain(chain, false /* enable */);
+ reset(mBpfNetMaps);
+ }
+ }
+
+ private void doTestReplaceFirewallChain(final int chain, final String chainName,
+ final boolean allowList) {
+ final int[] uids = new int[] {1001, 1002};
+ mCm.replaceFirewallChain(chain, uids);
+ verify(mBpfNetMaps).replaceUidChain(chainName, allowList, uids);
+ reset(mBpfNetMaps);
+ }
+
+ @Test @IgnoreUpTo(SC_V2)
+ public void testReplaceFirewallChain() {
+ doTestReplaceFirewallChain(FIREWALL_CHAIN_DOZABLE, "fw_dozable", true);
+ doTestReplaceFirewallChain(FIREWALL_CHAIN_STANDBY, "fw_standby", false);
+ doTestReplaceFirewallChain(FIREWALL_CHAIN_POWERSAVE, "fw_powersave", true);
+ doTestReplaceFirewallChain(FIREWALL_CHAIN_RESTRICTED, "fw_restricted", true);
+ doTestReplaceFirewallChain(FIREWALL_CHAIN_LOW_POWER_STANDBY, "fw_low_power_standby", true);
+ doTestReplaceFirewallChain(FIREWALL_CHAIN_OEM_DENY_1, "fw_oem_deny_1", false);
+ doTestReplaceFirewallChain(FIREWALL_CHAIN_OEM_DENY_2, "fw_oem_deny_2", false);
+ }
+
+ @Test @IgnoreUpTo(SC_V2)
+ public void testInvalidFirewallChain() throws Exception {
+ final int uid = 1001;
+ final Class<IllegalArgumentException> expected = IllegalArgumentException.class;
+ assertThrows(expected,
+ () -> mCm.setUidFirewallRule(-1 /* chain */, uid, FIREWALL_RULE_ALLOW));
+ assertThrows(expected,
+ () -> mCm.setUidFirewallRule(100 /* chain */, uid, FIREWALL_RULE_ALLOW));
+ assertThrows(expected, () -> mCm.replaceFirewallChain(-1 /* chain */, new int[]{uid}));
+ assertThrows(expected, () -> mCm.replaceFirewallChain(100 /* chain */, new int[]{uid}));
+ }
+
+ @Test @IgnoreUpTo(SC_V2)
+ public void testInvalidFirewallRule() throws Exception {
+ final Class<IllegalArgumentException> expected = IllegalArgumentException.class;
+ assertThrows(expected,
+ () -> mCm.setUidFirewallRule(FIREWALL_CHAIN_DOZABLE,
+ 1001 /* uid */, -1 /* rule */));
+ assertThrows(expected,
+ () -> mCm.setUidFirewallRule(FIREWALL_CHAIN_DOZABLE,
+ 1001 /* uid */, 100 /* rule */));
+ }
+
/**
* Test mutable and requestable network capabilities such as
* {@link NetworkCapabilities#NET_CAPABILITY_TRUSTED} and
@@ -9561,6 +9769,59 @@
return event;
}
+ private <T> T verifyWithOrder(@Nullable InOrder inOrder, @NonNull T t) {
+ if (inOrder != null) {
+ return inOrder.verify(t);
+ } else {
+ return verify(t);
+ }
+ }
+
+ private <T> T verifyNeverWithOrder(@Nullable InOrder inOrder, @NonNull T t) {
+ if (inOrder != null) {
+ return inOrder.verify(t, never());
+ } else {
+ return verify(t, never());
+ }
+ }
+
+ private void verifyClatdStart(@Nullable InOrder inOrder, @NonNull String iface, int netId,
+ @NonNull String nat64Prefix) throws Exception {
+ if (SdkLevel.isAtLeastT()) {
+ verifyWithOrder(inOrder, mClatCoordinator)
+ .clatStart(eq(iface), eq(netId), eq(new IpPrefix(nat64Prefix)));
+ } else {
+ verifyWithOrder(inOrder, mMockNetd).clatdStart(eq(iface), eq(nat64Prefix));
+ }
+ }
+
+ private void verifyNeverClatdStart(@Nullable InOrder inOrder, @NonNull String iface)
+ throws Exception {
+ if (SdkLevel.isAtLeastT()) {
+ verifyNeverWithOrder(inOrder, mClatCoordinator).clatStart(eq(iface), anyInt(), any());
+ } else {
+ verifyNeverWithOrder(inOrder, mMockNetd).clatdStart(eq(iface), anyString());
+ }
+ }
+
+ private void verifyClatdStop(@Nullable InOrder inOrder, @NonNull String iface)
+ throws Exception {
+ if (SdkLevel.isAtLeastT()) {
+ verifyWithOrder(inOrder, mClatCoordinator).clatStop();
+ } else {
+ verifyWithOrder(inOrder, mMockNetd).clatdStop(eq(iface));
+ }
+ }
+
+ private void verifyNeverClatdStop(@Nullable InOrder inOrder, @NonNull String iface)
+ throws Exception {
+ if (SdkLevel.isAtLeastT()) {
+ verifyNeverWithOrder(inOrder, mClatCoordinator).clatStop();
+ } else {
+ verifyNeverWithOrder(inOrder, mMockNetd).clatdStop(eq(iface));
+ }
+ }
+
@Test
public void testStackedLinkProperties() throws Exception {
final LinkAddress myIpv4 = new LinkAddress("1.2.3.4/24");
@@ -9593,6 +9854,7 @@
mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
reset(mMockDnsResolver);
reset(mMockNetd);
+ reset(mClatCoordinator);
// Connect with ipv6 link properties. Expect prefix discovery to be started.
mCellNetworkAgent.connect(true);
@@ -9631,8 +9893,10 @@
&& ri.iface != null && ri.iface.startsWith("v4-")));
verifyNoMoreInteractions(mMockNetd);
+ verifyNoMoreInteractions(mClatCoordinator);
verifyNoMoreInteractions(mMockDnsResolver);
reset(mMockNetd);
+ reset(mClatCoordinator);
reset(mMockDnsResolver);
doReturn(getClatInterfaceConfigParcel(myIpv4)).when(mMockNetd)
.interfaceGetCfg(CLAT_MOBILE_IFNAME);
@@ -9653,7 +9917,7 @@
CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent).getLp();
assertEquals(0, lpBeforeClat.getStackedLinks().size());
assertEquals(kNat64Prefix, lpBeforeClat.getNat64Prefix());
- verify(mMockNetd, times(1)).clatdStart(MOBILE_IFNAME, kNat64Prefix.toString());
+ verifyClatdStart(null /* inOrder */, MOBILE_IFNAME, cellNetId, kNat64Prefix.toString());
// Clat iface comes up. Expect stacked link to be added.
clat.interfaceLinkStateChanged(CLAT_MOBILE_IFNAME, true);
@@ -9685,6 +9949,7 @@
new int[] { TRANSPORT_CELLULAR })));
}
reset(mMockNetd);
+ reset(mClatCoordinator);
doReturn(getClatInterfaceConfigParcel(myIpv4)).when(mMockNetd)
.interfaceGetCfg(CLAT_MOBILE_IFNAME);
// Change the NAT64 prefix without first removing it.
@@ -9693,11 +9958,12 @@
cellNetId, PREFIX_OPERATION_ADDED, kOtherNat64PrefixString, 96));
networkCallback.expectLinkPropertiesThat(mCellNetworkAgent,
(lp) -> lp.getStackedLinks().size() == 0);
- verify(mMockNetd, times(1)).clatdStop(MOBILE_IFNAME);
+ verifyClatdStop(null /* inOrder */, MOBILE_IFNAME);
assertRoutesRemoved(cellNetId, stackedDefault);
verify(mMockNetd, times(1)).networkRemoveInterface(cellNetId, CLAT_MOBILE_IFNAME);
- verify(mMockNetd, times(1)).clatdStart(MOBILE_IFNAME, kOtherNat64Prefix.toString());
+ verifyClatdStart(null /* inOrder */, MOBILE_IFNAME, cellNetId,
+ kOtherNat64Prefix.toString());
networkCallback.expectLinkPropertiesThat(mCellNetworkAgent,
(lp) -> lp.getNat64Prefix().equals(kOtherNat64Prefix));
clat.interfaceLinkStateChanged(CLAT_MOBILE_IFNAME, true);
@@ -9706,6 +9972,7 @@
assertRoutesAdded(cellNetId, stackedDefault);
verify(mMockNetd, times(1)).networkAddInterface(cellNetId, CLAT_MOBILE_IFNAME);
reset(mMockNetd);
+ reset(mClatCoordinator);
// Add ipv4 address, expect that clatd and prefix discovery are stopped and stacked
// linkproperties are cleaned up.
@@ -9714,7 +9981,7 @@
mCellNetworkAgent.sendLinkProperties(cellLp);
networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
assertRoutesAdded(cellNetId, ipv4Subnet);
- verify(mMockNetd, times(1)).clatdStop(MOBILE_IFNAME);
+ verifyClatdStop(null /* inOrder */, MOBILE_IFNAME);
verify(mMockDnsResolver, times(1)).stopPrefix64Discovery(cellNetId);
// As soon as stop is called, the linkproperties lose the stacked interface.
@@ -9731,8 +9998,10 @@
networkCallback.assertNoCallback();
verify(mMockNetd, times(1)).networkRemoveInterface(cellNetId, CLAT_MOBILE_IFNAME);
verifyNoMoreInteractions(mMockNetd);
+ verifyNoMoreInteractions(mClatCoordinator);
verifyNoMoreInteractions(mMockDnsResolver);
reset(mMockNetd);
+ reset(mClatCoordinator);
reset(mMockDnsResolver);
doReturn(getClatInterfaceConfigParcel(myIpv4)).when(mMockNetd)
.interfaceGetCfg(CLAT_MOBILE_IFNAME);
@@ -9754,7 +10023,7 @@
mService.mResolverUnsolEventCallback.onNat64PrefixEvent(makeNat64PrefixEvent(
cellNetId, PREFIX_OPERATION_ADDED, kNat64PrefixString, 96));
networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
- verify(mMockNetd, times(1)).clatdStart(MOBILE_IFNAME, kNat64Prefix.toString());
+ verifyClatdStart(null /* inOrder */, MOBILE_IFNAME, cellNetId, kNat64Prefix.toString());
// Clat iface comes up. Expect stacked link to be added.
clat.interfaceLinkStateChanged(CLAT_MOBILE_IFNAME, true);
@@ -9771,7 +10040,7 @@
assertRoutesRemoved(cellNetId, ipv4Subnet, stackedDefault);
// Stop has no effect because clat is already stopped.
- verify(mMockNetd, times(1)).clatdStop(MOBILE_IFNAME);
+ verifyClatdStop(null /* inOrder */, MOBILE_IFNAME);
networkCallback.expectLinkPropertiesThat(mCellNetworkAgent,
(lp) -> lp.getStackedLinks().size() == 0);
verify(mMockNetd, times(1)).networkRemoveInterface(cellNetId, CLAT_MOBILE_IFNAME);
@@ -9784,7 +10053,9 @@
eq(Integer.toString(TRANSPORT_CELLULAR)));
verify(mMockNetd).networkDestroy(cellNetId);
verifyNoMoreInteractions(mMockNetd);
+ verifyNoMoreInteractions(mClatCoordinator);
reset(mMockNetd);
+ reset(mClatCoordinator);
// Test disconnecting a network that is running 464xlat.
@@ -9801,7 +10072,7 @@
assertRoutesAdded(cellNetId, ipv6Subnet, ipv6Default);
// Clatd is started and clat iface comes up. Expect stacked link to be added.
- verify(mMockNetd).clatdStart(MOBILE_IFNAME, kNat64Prefix.toString());
+ verifyClatdStart(null /* inOrder */, MOBILE_IFNAME, cellNetId, kNat64Prefix.toString());
clat = getNat464Xlat(mCellNetworkAgent);
clat.interfaceLinkStateChanged(CLAT_MOBILE_IFNAME, true /* up */);
networkCallback.expectLinkPropertiesThat(mCellNetworkAgent,
@@ -9811,16 +10082,18 @@
// assertRoutesAdded sees all calls since last mMockNetd reset, so expect IPv6 routes again.
assertRoutesAdded(cellNetId, ipv6Subnet, ipv6Default, stackedDefault);
reset(mMockNetd);
+ reset(mClatCoordinator);
// Disconnect the network. clat is stopped and the network is destroyed.
mCellNetworkAgent.disconnect();
networkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
networkCallback.assertNoCallback();
- verify(mMockNetd).clatdStop(MOBILE_IFNAME);
+ verifyClatdStop(null /* inOrder */, MOBILE_IFNAME);
verify(mMockNetd).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(),
eq(Integer.toString(TRANSPORT_CELLULAR)));
verify(mMockNetd).networkDestroy(cellNetId);
verifyNoMoreInteractions(mMockNetd);
+ verifyNoMoreInteractions(mClatCoordinator);
mCm.unregisterNetworkCallback(networkCallback);
}
@@ -9851,7 +10124,7 @@
baseLp.addDnsServer(InetAddress.getByName("2001:4860:4860::6464"));
reset(mMockNetd, mMockDnsResolver);
- InOrder inOrder = inOrder(mMockNetd, mMockDnsResolver);
+ InOrder inOrder = inOrder(mMockNetd, mMockDnsResolver, mClatCoordinator);
// If a network already has a NAT64 prefix on connect, clatd is started immediately and
// prefix discovery is never started.
@@ -9862,7 +10135,7 @@
final Network network = mWiFiNetworkAgent.getNetwork();
int netId = network.getNetId();
callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
- inOrder.verify(mMockNetd).clatdStart(iface, pref64FromRa.toString());
+ verifyClatdStart(inOrder, iface, netId, pref64FromRa.toString());
inOrder.verify(mMockDnsResolver).setPrefix64(netId, pref64FromRa.toString());
inOrder.verify(mMockDnsResolver, never()).startPrefix64Discovery(netId);
callback.assertNoCallback();
@@ -9872,7 +10145,7 @@
lp.setNat64Prefix(null);
mWiFiNetworkAgent.sendLinkProperties(lp);
expectNat64PrefixChange(callback, mWiFiNetworkAgent, null);
- inOrder.verify(mMockNetd).clatdStop(iface);
+ verifyClatdStop(inOrder, iface);
inOrder.verify(mMockDnsResolver).setPrefix64(netId, "");
inOrder.verify(mMockDnsResolver).startPrefix64Discovery(netId);
@@ -9881,7 +10154,7 @@
lp.setNat64Prefix(pref64FromRa);
mWiFiNetworkAgent.sendLinkProperties(lp);
expectNat64PrefixChange(callback, mWiFiNetworkAgent, pref64FromRa);
- inOrder.verify(mMockNetd).clatdStart(iface, pref64FromRa.toString());
+ verifyClatdStart(inOrder, iface, netId, pref64FromRa.toString());
inOrder.verify(mMockDnsResolver).stopPrefix64Discovery(netId);
inOrder.verify(mMockDnsResolver).setPrefix64(netId, pref64FromRa.toString());
@@ -9890,22 +10163,22 @@
lp.setNat64Prefix(null);
mWiFiNetworkAgent.sendLinkProperties(lp);
expectNat64PrefixChange(callback, mWiFiNetworkAgent, null);
- inOrder.verify(mMockNetd).clatdStop(iface);
+ verifyClatdStop(inOrder, iface);
inOrder.verify(mMockDnsResolver).setPrefix64(netId, "");
inOrder.verify(mMockDnsResolver).startPrefix64Discovery(netId);
mService.mResolverUnsolEventCallback.onNat64PrefixEvent(
makeNat64PrefixEvent(netId, PREFIX_OPERATION_ADDED, pref64FromDnsStr, 96));
expectNat64PrefixChange(callback, mWiFiNetworkAgent, pref64FromDns);
- inOrder.verify(mMockNetd).clatdStart(iface, pref64FromDns.toString());
+ verifyClatdStart(inOrder, iface, netId, pref64FromDns.toString());
// If an RA advertises the same prefix that was discovered by DNS, nothing happens: prefix
// discovery is not stopped, and there are no callbacks.
lp.setNat64Prefix(pref64FromDns);
mWiFiNetworkAgent.sendLinkProperties(lp);
callback.assertNoCallback();
- inOrder.verify(mMockNetd, never()).clatdStop(iface);
- inOrder.verify(mMockNetd, never()).clatdStart(eq(iface), anyString());
+ verifyNeverClatdStop(inOrder, iface);
+ verifyNeverClatdStart(inOrder, iface);
inOrder.verify(mMockDnsResolver, never()).stopPrefix64Discovery(netId);
inOrder.verify(mMockDnsResolver, never()).startPrefix64Discovery(netId);
inOrder.verify(mMockDnsResolver, never()).setPrefix64(eq(netId), anyString());
@@ -9914,8 +10187,8 @@
lp.setNat64Prefix(null);
mWiFiNetworkAgent.sendLinkProperties(lp);
callback.assertNoCallback();
- inOrder.verify(mMockNetd, never()).clatdStop(iface);
- inOrder.verify(mMockNetd, never()).clatdStart(eq(iface), anyString());
+ verifyNeverClatdStop(inOrder, iface);
+ verifyNeverClatdStart(inOrder, iface);
inOrder.verify(mMockDnsResolver, never()).stopPrefix64Discovery(netId);
inOrder.verify(mMockDnsResolver, never()).startPrefix64Discovery(netId);
inOrder.verify(mMockDnsResolver, never()).setPrefix64(eq(netId), anyString());
@@ -9924,14 +10197,14 @@
lp.setNat64Prefix(pref64FromRa);
mWiFiNetworkAgent.sendLinkProperties(lp);
expectNat64PrefixChange(callback, mWiFiNetworkAgent, pref64FromRa);
- inOrder.verify(mMockNetd).clatdStop(iface);
+ verifyClatdStop(inOrder, iface);
inOrder.verify(mMockDnsResolver).stopPrefix64Discovery(netId);
// Stopping prefix discovery results in a prefix removed notification.
mService.mResolverUnsolEventCallback.onNat64PrefixEvent(
makeNat64PrefixEvent(netId, PREFIX_OPERATION_REMOVED, pref64FromDnsStr, 96));
- inOrder.verify(mMockNetd).clatdStart(iface, pref64FromRa.toString());
+ verifyClatdStart(inOrder, iface, netId, pref64FromRa.toString());
inOrder.verify(mMockDnsResolver).setPrefix64(netId, pref64FromRa.toString());
inOrder.verify(mMockDnsResolver, never()).startPrefix64Discovery(netId);
@@ -9939,9 +10212,9 @@
lp.setNat64Prefix(newPref64FromRa);
mWiFiNetworkAgent.sendLinkProperties(lp);
expectNat64PrefixChange(callback, mWiFiNetworkAgent, newPref64FromRa);
- inOrder.verify(mMockNetd).clatdStop(iface);
+ verifyClatdStop(inOrder, iface);
inOrder.verify(mMockDnsResolver).setPrefix64(netId, "");
- inOrder.verify(mMockNetd).clatdStart(iface, newPref64FromRa.toString());
+ verifyClatdStart(inOrder, iface, netId, newPref64FromRa.toString());
inOrder.verify(mMockDnsResolver).setPrefix64(netId, newPref64FromRa.toString());
inOrder.verify(mMockDnsResolver, never()).stopPrefix64Discovery(netId);
inOrder.verify(mMockDnsResolver, never()).startPrefix64Discovery(netId);
@@ -9951,8 +10224,8 @@
mWiFiNetworkAgent.sendLinkProperties(lp);
callback.assertNoCallback();
assertEquals(newPref64FromRa, mCm.getLinkProperties(network).getNat64Prefix());
- inOrder.verify(mMockNetd, never()).clatdStop(iface);
- inOrder.verify(mMockNetd, never()).clatdStart(eq(iface), anyString());
+ verifyNeverClatdStop(inOrder, iface);
+ verifyNeverClatdStart(inOrder, iface);
inOrder.verify(mMockDnsResolver, never()).stopPrefix64Discovery(netId);
inOrder.verify(mMockDnsResolver, never()).startPrefix64Discovery(netId);
inOrder.verify(mMockDnsResolver, never()).setPrefix64(eq(netId), anyString());
@@ -9964,20 +10237,20 @@
lp.setNat64Prefix(null);
mWiFiNetworkAgent.sendLinkProperties(lp);
expectNat64PrefixChange(callback, mWiFiNetworkAgent, null);
- inOrder.verify(mMockNetd).clatdStop(iface);
+ verifyClatdStop(inOrder, iface);
inOrder.verify(mMockDnsResolver).setPrefix64(netId, "");
inOrder.verify(mMockDnsResolver).startPrefix64Discovery(netId);
mService.mResolverUnsolEventCallback.onNat64PrefixEvent(
makeNat64PrefixEvent(netId, PREFIX_OPERATION_ADDED, pref64FromDnsStr, 96));
expectNat64PrefixChange(callback, mWiFiNetworkAgent, pref64FromDns);
- inOrder.verify(mMockNetd).clatdStart(iface, pref64FromDns.toString());
+ verifyClatdStart(inOrder, iface, netId, pref64FromDns.toString());
inOrder.verify(mMockDnsResolver, never()).setPrefix64(eq(netId), any());
lp.setNat64Prefix(pref64FromDns);
mWiFiNetworkAgent.sendLinkProperties(lp);
callback.assertNoCallback();
- inOrder.verify(mMockNetd, never()).clatdStop(iface);
- inOrder.verify(mMockNetd, never()).clatdStart(eq(iface), anyString());
+ verifyNeverClatdStop(inOrder, iface);
+ verifyNeverClatdStart(inOrder, iface);
inOrder.verify(mMockDnsResolver, never()).stopPrefix64Discovery(netId);
inOrder.verify(mMockDnsResolver, never()).startPrefix64Discovery(netId);
inOrder.verify(mMockDnsResolver, never()).setPrefix64(eq(netId), anyString());
@@ -9990,7 +10263,7 @@
callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
b.expectBroadcast();
- inOrder.verify(mMockNetd).clatdStop(iface);
+ verifyClatdStop(inOrder, iface);
inOrder.verify(mMockDnsResolver).stopPrefix64Discovery(netId);
inOrder.verify(mMockDnsResolver, never()).setPrefix64(eq(netId), anyString());
@@ -10240,7 +10513,7 @@
verify(mBpfNetMaps, times(2)).addUidInterfaceRules(eq("tun0"), uidCaptor.capture());
assertContainsExactly(uidCaptor.getAllValues().get(0), APP1_UID, APP2_UID);
assertContainsExactly(uidCaptor.getAllValues().get(1), APP1_UID, APP2_UID);
- assertTrue(mService.mPermissionMonitor.getVpnUidRanges("tun0").equals(vpnRange));
+ assertTrue(mService.mPermissionMonitor.getVpnInterfaceUidRanges("tun0").equals(vpnRange));
mMockVpn.disconnect();
waitForIdle();
@@ -10248,11 +10521,11 @@
// Disconnected VPN should have interface rules removed
verify(mBpfNetMaps).removeUidInterfaceRules(uidCaptor.capture());
assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
- assertNull(mService.mPermissionMonitor.getVpnUidRanges("tun0"));
+ assertNull(mService.mPermissionMonitor.getVpnInterfaceUidRanges("tun0"));
}
@Test
- public void testLegacyVpnDoesNotResultInInterfaceFilteringRule() throws Exception {
+ public void testLegacyVpnInterfaceFilteringRule() throws Exception {
LinkProperties lp = new LinkProperties();
lp.setInterfaceName("tun0");
lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null));
@@ -10262,13 +10535,34 @@
mMockVpn.establish(lp, Process.SYSTEM_UID, vpnRange);
assertVpnUidRangesUpdated(true, vpnRange, Process.SYSTEM_UID);
- // Legacy VPN should not have interface rules set up
- verify(mBpfNetMaps, never()).addUidInterfaceRules(any(), any());
+ if (SdkLevel.isAtLeastT()) {
+ // On T and above, A connected Legacy VPN should have interface rules with null
+ // interface. Null Interface is a wildcard and this accepts traffic from all the
+ // interfaces. There are two expected invocations, one during the VPN initial
+ // connection, one during the VPN LinkProperties update.
+ ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class);
+ verify(mBpfNetMaps, times(2)).addUidInterfaceRules(
+ eq(null) /* iface */, uidCaptor.capture());
+ assertContainsExactly(uidCaptor.getAllValues().get(0), APP1_UID, APP2_UID, VPN_UID);
+ assertContainsExactly(uidCaptor.getAllValues().get(1), APP1_UID, APP2_UID, VPN_UID);
+ assertEquals(mService.mPermissionMonitor.getVpnInterfaceUidRanges(null /* iface */),
+ vpnRange);
+
+ mMockVpn.disconnect();
+ waitForIdle();
+
+ // Disconnected VPN should have interface rules removed
+ verify(mBpfNetMaps).removeUidInterfaceRules(uidCaptor.capture());
+ assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID, VPN_UID);
+ assertNull(mService.mPermissionMonitor.getVpnInterfaceUidRanges(null /* iface */));
+ } else {
+ // Before T, Legacy VPN should not have interface rules.
+ verify(mBpfNetMaps, never()).addUidInterfaceRules(any(), any());
+ }
}
@Test
- public void testLocalIpv4OnlyVpnDoesNotResultInInterfaceFilteringRule()
- throws Exception {
+ public void testLocalIpv4OnlyVpnInterfaceFilteringRule() throws Exception {
LinkProperties lp = new LinkProperties();
lp.setInterfaceName("tun0");
lp.addRoute(new RouteInfo(new IpPrefix("192.0.2.0/24"), null, "tun0"));
@@ -10278,8 +10572,31 @@
mMockVpn.establish(lp, Process.SYSTEM_UID, vpnRange);
assertVpnUidRangesUpdated(true, vpnRange, Process.SYSTEM_UID);
- // IPv6 unreachable route should not be misinterpreted as a default route
- verify(mBpfNetMaps, never()).addUidInterfaceRules(any(), any());
+ if (SdkLevel.isAtLeastT()) {
+ // IPv6 unreachable route should not be misinterpreted as a default route
+ // On T and above, A connected VPN that does not provide a default route should have
+ // interface rules with null interface. Null Interface is a wildcard and this accepts
+ // traffic from all the interfaces. There are two expected invocations, one during the
+ // VPN initial connection, one during the VPN LinkProperties update.
+ ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class);
+ verify(mBpfNetMaps, times(2)).addUidInterfaceRules(
+ eq(null) /* iface */, uidCaptor.capture());
+ assertContainsExactly(uidCaptor.getAllValues().get(0), APP1_UID, APP2_UID, VPN_UID);
+ assertContainsExactly(uidCaptor.getAllValues().get(1), APP1_UID, APP2_UID, VPN_UID);
+ assertEquals(mService.mPermissionMonitor.getVpnInterfaceUidRanges(null /* iface */),
+ vpnRange);
+
+ mMockVpn.disconnect();
+ waitForIdle();
+
+ // Disconnected VPN should have interface rules removed
+ verify(mBpfNetMaps).removeUidInterfaceRules(uidCaptor.capture());
+ assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID, VPN_UID);
+ assertNull(mService.mPermissionMonitor.getVpnInterfaceUidRanges(null /* iface */));
+ } else {
+ // Before T, VPN with IPv6 unreachable route should not have interface rules.
+ verify(mBpfNetMaps, never()).addUidInterfaceRules(any(), any());
+ }
}
@Test
@@ -11484,6 +11801,12 @@
mCm.unregisterNetworkCallback(networkCallback);
}
+ private void verifyDump(String[] args) {
+ final StringWriter stringWriter = new StringWriter();
+ mService.dump(new FileDescriptor(), new PrintWriter(stringWriter), args);
+ assertFalse(stringWriter.toString().isEmpty());
+ }
+
@Test
public void testDumpDoesNotCrash() {
mServiceContext.setPermission(DUMP, PERMISSION_GRANTED);
@@ -11496,11 +11819,26 @@
.addTransportType(TRANSPORT_WIFI).build();
mCm.registerNetworkCallback(genericRequest, genericNetworkCallback);
mCm.registerNetworkCallback(wifiRequest, wifiNetworkCallback);
- final StringWriter stringWriter = new StringWriter();
- mService.dump(new FileDescriptor(), new PrintWriter(stringWriter), new String[0]);
+ verifyDump(new String[0]);
- assertFalse(stringWriter.toString().isEmpty());
+ // Verify dump with arguments.
+ final String dumpPrio = "--dump-priority";
+ final String[] dumpArgs = {dumpPrio};
+ verifyDump(dumpArgs);
+
+ final String[] highDumpArgs = {dumpPrio, "HIGH"};
+ verifyDump(highDumpArgs);
+
+ final String[] normalDumpArgs = {dumpPrio, "NORMAL"};
+ verifyDump(normalDumpArgs);
+
+ // Invalid args should do dumpNormal w/o exception
+ final String[] unknownDumpArgs = {dumpPrio, "UNKNOWN"};
+ verifyDump(unknownDumpArgs);
+
+ final String[] invalidDumpArgs = {"UNKNOWN"};
+ verifyDump(invalidDumpArgs);
}
@Test
@@ -11844,16 +12182,14 @@
mQosCallbackMockHelper.registerQosCallback(
mQosCallbackMockHelper.mFilter, mQosCallbackMockHelper.mCallback);
- final NetworkAgentWrapper.CallbackType.OnQosCallbackRegister cbRegister1 =
- (NetworkAgentWrapper.CallbackType.OnQosCallbackRegister)
- wrapper.getCallbackHistory().poll(1000, x -> true);
+ final OnQosCallbackRegister cbRegister1 =
+ (OnQosCallbackRegister) wrapper.getCallbackHistory().poll(1000, x -> true);
assertNotNull(cbRegister1);
final int registerCallbackId = cbRegister1.mQosCallbackId;
mService.unregisterQosCallback(mQosCallbackMockHelper.mCallback);
- final NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister cbUnregister;
- cbUnregister = (NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister)
- wrapper.getCallbackHistory().poll(1000, x -> true);
+ final OnQosCallbackUnregister cbUnregister =
+ (OnQosCallbackUnregister) wrapper.getCallbackHistory().poll(1000, x -> true);
assertNotNull(cbUnregister);
assertEquals(registerCallbackId, cbUnregister.mQosCallbackId);
assertNull(wrapper.getCallbackHistory().poll(200, x -> true));
@@ -11932,6 +12268,86 @@
&& session.getSessionType() == QosSession.TYPE_NR_BEARER));
}
+ @Test @IgnoreUpTo(SC_V2)
+ public void testQosCallbackAvailableOnValidationError() throws Exception {
+ mQosCallbackMockHelper = new QosCallbackMockHelper();
+ final NetworkAgentWrapper wrapper = mQosCallbackMockHelper.mAgentWrapper;
+ final int sessionId = 10;
+ final int qosCallbackId = 1;
+
+ doReturn(QosCallbackException.EX_TYPE_FILTER_NONE)
+ .when(mQosCallbackMockHelper.mFilter).validate();
+ mQosCallbackMockHelper.registerQosCallback(
+ mQosCallbackMockHelper.mFilter, mQosCallbackMockHelper.mCallback);
+ OnQosCallbackRegister cbRegister1 =
+ (OnQosCallbackRegister) wrapper.getCallbackHistory().poll(1000, x -> true);
+ assertNotNull(cbRegister1);
+ final int registerCallbackId = cbRegister1.mQosCallbackId;
+
+ waitForIdle();
+
+ doReturn(QosCallbackException.EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED)
+ .when(mQosCallbackMockHelper.mFilter).validate();
+ final EpsBearerQosSessionAttributes attributes = new EpsBearerQosSessionAttributes(
+ 1, 2, 3, 4, 5, new ArrayList<>());
+ mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent()
+ .sendQosSessionAvailable(qosCallbackId, sessionId, attributes);
+ waitForIdle();
+
+ final NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister cbUnregister;
+ cbUnregister = (NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister)
+ wrapper.getCallbackHistory().poll(1000, x -> true);
+ assertNotNull(cbUnregister);
+ assertEquals(registerCallbackId, cbUnregister.mQosCallbackId);
+ waitForIdle();
+ verify(mQosCallbackMockHelper.mCallback)
+ .onError(eq(QosCallbackException.EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED));
+ }
+
+ @Test @IgnoreUpTo(SC_V2)
+ public void testQosCallbackLostOnValidationError() throws Exception {
+ mQosCallbackMockHelper = new QosCallbackMockHelper();
+ final int sessionId = 10;
+ final int qosCallbackId = 1;
+
+ doReturn(QosCallbackException.EX_TYPE_FILTER_NONE)
+ .when(mQosCallbackMockHelper.mFilter).validate();
+ mQosCallbackMockHelper.registerQosCallback(
+ mQosCallbackMockHelper.mFilter, mQosCallbackMockHelper.mCallback);
+ waitForIdle();
+ EpsBearerQosSessionAttributes attributes =
+ sendQosSessionEvent(qosCallbackId, sessionId, true);
+ waitForIdle();
+
+ verify(mQosCallbackMockHelper.mCallback).onQosEpsBearerSessionAvailable(argThat(session ->
+ session.getSessionId() == sessionId
+ && session.getSessionType() == QosSession.TYPE_EPS_BEARER), eq(attributes));
+
+ doReturn(QosCallbackException.EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED)
+ .when(mQosCallbackMockHelper.mFilter).validate();
+
+ sendQosSessionEvent(qosCallbackId, sessionId, false);
+ waitForIdle();
+ verify(mQosCallbackMockHelper.mCallback)
+ .onError(eq(QosCallbackException.EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED));
+ }
+
+ private EpsBearerQosSessionAttributes sendQosSessionEvent(
+ int qosCallbackId, int sessionId, boolean available) {
+ if (available) {
+ final EpsBearerQosSessionAttributes attributes = new EpsBearerQosSessionAttributes(
+ 1, 2, 3, 4, 5, new ArrayList<>());
+ mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent()
+ .sendQosSessionAvailable(qosCallbackId, sessionId, attributes);
+ return attributes;
+ } else {
+ mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent()
+ .sendQosSessionLost(qosCallbackId, sessionId, QosSession.TYPE_EPS_BEARER);
+ return null;
+ }
+
+ }
+
@Test
public void testQosCallbackTooManyRequests() throws Exception {
mQosCallbackMockHelper = new QosCallbackMockHelper();
@@ -14455,7 +14871,7 @@
profileNetworkPreferenceBuilder.setPreference(
PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK);
profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(
- NetworkCapabilities.NET_ENTERPRISE_ID_2);
+ NET_ENTERPRISE_ID_2);
registerDefaultNetworkCallbacks();
testPreferenceForUserNetworkUpDownForGivenPreference(
profileNetworkPreferenceBuilder.build(), true,
@@ -14480,6 +14896,393 @@
}
/**
+ * Make sure per-profile networking preference throws exception when default preference
+ * is set along with enterprise preference.
+ */
+ @Test
+ public void testPreferenceWithInvalidPreferenceDefaultAndEnterpriseTogether()
+ throws Exception {
+ final UserHandle testHandle = setupEnterpriseNetwork();
+ mServiceContext.setWorkProfile(testHandle, true);
+
+ final int testWorkProfileAppUid1 =
+ UserHandle.getUid(testHandle.getIdentifier(), TEST_APP_ID);
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder1 =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder1.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ profileNetworkPreferenceBuilder1.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
+ profileNetworkPreferenceBuilder1.setIncludedUids(new int[]{testWorkProfileAppUid1});
+
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder2 =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder2.setPreference(PROFILE_NETWORK_PREFERENCE_DEFAULT);
+ final TestOnCompleteListener listener = new TestOnCompleteListener();
+ Assert.assertThrows(IllegalArgumentException.class,
+ () -> mCm.setProfileNetworkPreferences(
+ testHandle, List.of(profileNetworkPreferenceBuilder1.build(),
+ profileNetworkPreferenceBuilder2.build()),
+ r -> r.run(), listener));
+ Assert.assertThrows(IllegalArgumentException.class,
+ () -> mCm.setProfileNetworkPreferences(
+ testHandle, List.of(profileNetworkPreferenceBuilder2.build(),
+ profileNetworkPreferenceBuilder1.build()),
+ r -> r.run(), listener));
+ }
+
+ /**
+ * Make sure per profile network preferences behave as expected when two slices with
+ * two different apps within same user profile is configured
+ * Make sure per profile network preferences overrides with latest preference when
+ * same user preference is set twice
+ */
+ @Test
+ public void testSetPreferenceWithOverridingPreference()
+ throws Exception {
+ final InOrder inOrder = inOrder(mMockNetd);
+ final UserHandle testHandle = setupEnterpriseNetwork();
+ mServiceContext.setWorkProfile(testHandle, true);
+ registerDefaultNetworkCallbacks();
+
+ final TestNetworkCallback appCb1 = new TestNetworkCallback();
+ final TestNetworkCallback appCb2 = new TestNetworkCallback();
+ final TestNetworkCallback appCb3 = new TestNetworkCallback();
+
+ final int testWorkProfileAppUid1 =
+ UserHandle.getUid(testHandle.getIdentifier(), TEST_APP_ID);
+ final int testWorkProfileAppUid2 =
+ UserHandle.getUid(testHandle.getIdentifier(), TEST_APP_ID_2);
+ final int testWorkProfileAppUid3 =
+ UserHandle.getUid(testHandle.getIdentifier(), TEST_APP_ID_3);
+
+ registerDefaultNetworkCallbackAsUid(appCb1, testWorkProfileAppUid1);
+ registerDefaultNetworkCallbackAsUid(appCb2, testWorkProfileAppUid2);
+ registerDefaultNetworkCallbackAsUid(appCb3, testWorkProfileAppUid3);
+
+ // Connect both a regular cell agent and an enterprise network first.
+ mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(true);
+
+ final TestNetworkAgentWrapper workAgent1 = makeEnterpriseNetworkAgent(NET_ENTERPRISE_ID_1);
+ final TestNetworkAgentWrapper workAgent2 = makeEnterpriseNetworkAgent(NET_ENTERPRISE_ID_2);
+ workAgent1.connect(true);
+ workAgent2.connect(true);
+
+ mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+
+ appCb1.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ appCb2.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ appCb3.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+
+ verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
+ mCellNetworkAgent.getNetwork().netId, INetd.PERMISSION_NONE));
+ verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
+ workAgent1.getNetwork().netId, INetd.PERMISSION_SYSTEM));
+ verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
+ workAgent2.getNetwork().netId, INetd.PERMISSION_SYSTEM));
+
+ final TestOnCompleteListener listener = new TestOnCompleteListener();
+
+ // Set preferences for testHandle to map testWorkProfileAppUid1 to
+ // NET_ENTERPRISE_ID_1 and testWorkProfileAppUid2 to NET_ENTERPRISE_ID_2.
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder1 =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder1.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ profileNetworkPreferenceBuilder1.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
+ profileNetworkPreferenceBuilder1.setIncludedUids(new int[]{testWorkProfileAppUid1});
+
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder2 =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder2.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ profileNetworkPreferenceBuilder2.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_2);
+ profileNetworkPreferenceBuilder2.setIncludedUids(new int[]{testWorkProfileAppUid2});
+
+ mCm.setProfileNetworkPreferences(testHandle,
+ List.of(profileNetworkPreferenceBuilder1.build(),
+ profileNetworkPreferenceBuilder2.build()),
+ r -> r.run(), listener);
+ listener.expectOnComplete();
+ verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
+ workAgent2.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreferenceBuilder2.build()),
+ PREFERENCE_ORDER_PROFILE));
+ verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
+ workAgent1.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreferenceBuilder1.build()),
+ PREFERENCE_ORDER_PROFILE));
+
+ assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
+ appCb1.expectAvailableCallbacksValidated(workAgent1);
+ appCb2.expectAvailableCallbacksValidated(workAgent2);
+
+ // Set preferences for testHandle to map testWorkProfileAppUid3 to
+ // to NET_ENTERPRISE_ID_1.
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder3 =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder3.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ profileNetworkPreferenceBuilder3.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
+ profileNetworkPreferenceBuilder3.setIncludedUids(new int[]{testWorkProfileAppUid3});
+
+ mCm.setProfileNetworkPreferences(testHandle,
+ List.of(profileNetworkPreferenceBuilder3.build()),
+ r -> r.run(), listener);
+ listener.expectOnComplete();
+ verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
+ workAgent1.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreferenceBuilder3.build()),
+ PREFERENCE_ORDER_PROFILE));
+ verify(mMockNetd).networkRemoveUidRangesParcel(new NativeUidRangeConfig(
+ workAgent2.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreferenceBuilder2.build()),
+ PREFERENCE_ORDER_PROFILE));
+ verify(mMockNetd).networkRemoveUidRangesParcel(new NativeUidRangeConfig(
+ workAgent1.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreferenceBuilder1.build()),
+ PREFERENCE_ORDER_PROFILE));
+
+ assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
+ appCb3.expectAvailableCallbacksValidated(workAgent1);
+ appCb2.expectAvailableCallbacksValidated(mCellNetworkAgent);
+ appCb1.expectAvailableCallbacksValidated(mCellNetworkAgent);
+
+ // Set the preferences for testHandle to default.
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_DEFAULT);
+
+ mCm.setProfileNetworkPreferences(testHandle,
+ List.of(profileNetworkPreferenceBuilder.build()),
+ r -> r.run(), listener);
+ listener.expectOnComplete();
+ verify(mMockNetd).networkRemoveUidRangesParcel(new NativeUidRangeConfig(
+ workAgent1.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreferenceBuilder3.build()),
+ PREFERENCE_ORDER_PROFILE));
+
+ assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback, appCb1, appCb2);
+ appCb3.expectAvailableCallbacksValidated(mCellNetworkAgent);
+ workAgent2.disconnect();
+ mCellNetworkAgent.disconnect();
+
+ mCm.unregisterNetworkCallback(appCb1);
+ mCm.unregisterNetworkCallback(appCb2);
+ mCm.unregisterNetworkCallback(appCb3);
+ // Other callbacks will be unregistered by tearDown()
+ }
+
+ /**
+ * Make sure per profile network preferences behave as expected when multiple slices with
+ * multiple different apps within same user profile is configured.
+ */
+ @Test
+ public void testSetPreferenceWithMultiplePreferences()
+ throws Exception {
+ final InOrder inOrder = inOrder(mMockNetd);
+
+ final UserHandle testHandle = setupEnterpriseNetwork();
+ mServiceContext.setWorkProfile(testHandle, true);
+ registerDefaultNetworkCallbacks();
+
+ final TestNetworkCallback appCb1 = new TestNetworkCallback();
+ final TestNetworkCallback appCb2 = new TestNetworkCallback();
+ final TestNetworkCallback appCb3 = new TestNetworkCallback();
+ final TestNetworkCallback appCb4 = new TestNetworkCallback();
+ final TestNetworkCallback appCb5 = new TestNetworkCallback();
+
+ final int testWorkProfileAppUid1 =
+ UserHandle.getUid(testHandle.getIdentifier(), TEST_APP_ID);
+ final int testWorkProfileAppUid2 =
+ UserHandle.getUid(testHandle.getIdentifier(), TEST_APP_ID_2);
+ final int testWorkProfileAppUid3 =
+ UserHandle.getUid(testHandle.getIdentifier(), TEST_APP_ID_3);
+ final int testWorkProfileAppUid4 =
+ UserHandle.getUid(testHandle.getIdentifier(), TEST_APP_ID_4);
+ final int testWorkProfileAppUid5 =
+ UserHandle.getUid(testHandle.getIdentifier(), TEST_APP_ID_5);
+
+ registerDefaultNetworkCallbackAsUid(appCb1, testWorkProfileAppUid1);
+ registerDefaultNetworkCallbackAsUid(appCb2, testWorkProfileAppUid2);
+ registerDefaultNetworkCallbackAsUid(appCb3, testWorkProfileAppUid3);
+ registerDefaultNetworkCallbackAsUid(appCb4, testWorkProfileAppUid4);
+ registerDefaultNetworkCallbackAsUid(appCb5, testWorkProfileAppUid5);
+
+ // Connect both a regular cell agent and an enterprise network first.
+ mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(true);
+
+ final TestNetworkAgentWrapper workAgent1 = makeEnterpriseNetworkAgent(NET_ENTERPRISE_ID_1);
+ final TestNetworkAgentWrapper workAgent2 = makeEnterpriseNetworkAgent(NET_ENTERPRISE_ID_2);
+ final TestNetworkAgentWrapper workAgent3 = makeEnterpriseNetworkAgent(NET_ENTERPRISE_ID_3);
+ final TestNetworkAgentWrapper workAgent4 = makeEnterpriseNetworkAgent(NET_ENTERPRISE_ID_4);
+ final TestNetworkAgentWrapper workAgent5 = makeEnterpriseNetworkAgent(NET_ENTERPRISE_ID_5);
+
+ workAgent1.connect(true);
+ workAgent2.connect(true);
+ workAgent3.connect(true);
+ workAgent4.connect(true);
+ workAgent5.connect(true);
+
+ mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ appCb1.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ appCb2.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ appCb3.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ appCb4.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ appCb5.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+
+ verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
+ mCellNetworkAgent.getNetwork().netId, INetd.PERMISSION_NONE));
+ verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
+ workAgent1.getNetwork().netId, INetd.PERMISSION_SYSTEM));
+ verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
+ workAgent2.getNetwork().netId, INetd.PERMISSION_SYSTEM));
+ verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
+ workAgent3.getNetwork().netId, INetd.PERMISSION_SYSTEM));
+ verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
+ workAgent4.getNetwork().netId, INetd.PERMISSION_SYSTEM));
+ verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
+ workAgent5.getNetwork().netId, INetd.PERMISSION_SYSTEM));
+
+ final TestOnCompleteListener listener = new TestOnCompleteListener();
+
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder1 =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder1.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ profileNetworkPreferenceBuilder1.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
+ profileNetworkPreferenceBuilder1.setIncludedUids(new int[]{testWorkProfileAppUid1});
+
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder2 =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder2.setPreference(
+ PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK);
+ profileNetworkPreferenceBuilder2.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_2);
+ profileNetworkPreferenceBuilder2.setIncludedUids(new int[]{testWorkProfileAppUid2});
+
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder3 =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder3.setPreference(
+ PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ profileNetworkPreferenceBuilder3.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_3);
+ profileNetworkPreferenceBuilder3.setIncludedUids(new int[]{testWorkProfileAppUid3});
+
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder4 =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder4.setPreference(
+ PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK);
+ profileNetworkPreferenceBuilder4.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_4);
+ profileNetworkPreferenceBuilder4.setIncludedUids(new int[]{testWorkProfileAppUid4});
+
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder5 =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder5.setPreference(
+ PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ profileNetworkPreferenceBuilder5.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_5);
+ profileNetworkPreferenceBuilder5.setIncludedUids(new int[]{testWorkProfileAppUid5});
+
+ mCm.setProfileNetworkPreferences(testHandle,
+ List.of(profileNetworkPreferenceBuilder1.build(),
+ profileNetworkPreferenceBuilder2.build(),
+ profileNetworkPreferenceBuilder3.build(),
+ profileNetworkPreferenceBuilder4.build(),
+ profileNetworkPreferenceBuilder5.build()),
+ r -> r.run(), listener);
+
+ listener.expectOnComplete();
+
+ verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
+ workAgent1.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreferenceBuilder1.build()),
+ PREFERENCE_ORDER_PROFILE));
+ verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
+ workAgent2.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreferenceBuilder2.build()),
+ PREFERENCE_ORDER_PROFILE));
+ verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
+ workAgent3.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreferenceBuilder3.build()),
+ PREFERENCE_ORDER_PROFILE));
+ verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
+ workAgent4.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreferenceBuilder4.build()),
+ PREFERENCE_ORDER_PROFILE));
+ verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
+ workAgent5.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreferenceBuilder5.build()),
+ PREFERENCE_ORDER_PROFILE));
+
+ assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
+ appCb1.expectAvailableCallbacksValidated(workAgent1);
+ appCb2.expectAvailableCallbacksValidated(workAgent2);
+ appCb3.expectAvailableCallbacksValidated(workAgent3);
+ appCb4.expectAvailableCallbacksValidated(workAgent4);
+ appCb5.expectAvailableCallbacksValidated(workAgent5);
+
+ workAgent1.disconnect();
+ workAgent2.disconnect();
+ workAgent3.disconnect();
+ workAgent4.disconnect();
+ workAgent5.disconnect();
+
+ appCb1.expectCallback(CallbackEntry.LOST, workAgent1);
+ appCb2.expectCallback(CallbackEntry.LOST, workAgent2);
+ appCb3.expectCallback(CallbackEntry.LOST, workAgent3);
+ appCb4.expectCallback(CallbackEntry.LOST, workAgent4);
+ appCb5.expectCallback(CallbackEntry.LOST, workAgent5);
+
+ appCb1.expectAvailableCallbacksValidated(mCellNetworkAgent);
+ appCb2.assertNoCallback();
+ appCb3.expectAvailableCallbacksValidated(mCellNetworkAgent);
+ appCb4.assertNoCallback();
+ appCb5.expectAvailableCallbacksValidated(mCellNetworkAgent);
+
+ verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
+ mCellNetworkAgent.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreferenceBuilder1.build()),
+ PREFERENCE_ORDER_PROFILE));
+ verify(mMockNetd, never()).networkAddUidRangesParcel(new NativeUidRangeConfig(
+ mCellNetworkAgent.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreferenceBuilder2.build()),
+ PREFERENCE_ORDER_PROFILE));
+ verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
+ mCellNetworkAgent.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreferenceBuilder3.build()),
+ PREFERENCE_ORDER_PROFILE));
+ verify(mMockNetd, never()).networkAddUidRangesParcel(new NativeUidRangeConfig(
+ mCellNetworkAgent.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreferenceBuilder4.build()),
+ PREFERENCE_ORDER_PROFILE));
+ verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
+ mCellNetworkAgent.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreferenceBuilder5.build()),
+ PREFERENCE_ORDER_PROFILE));
+
+ mSystemDefaultNetworkCallback.assertNoCallback();
+ mDefaultNetworkCallback.assertNoCallback();
+
+ // Set the preferences for testHandle to default.
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_DEFAULT);
+
+ mCm.setProfileNetworkPreferences(testHandle,
+ List.of(profileNetworkPreferenceBuilder.build()),
+ r -> r.run(), listener);
+ listener.expectOnComplete();
+ assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback, appCb1, appCb3,
+ appCb5);
+ appCb2.expectAvailableCallbacksValidated(mCellNetworkAgent);
+ appCb4.expectAvailableCallbacksValidated(mCellNetworkAgent);
+ mCellNetworkAgent.disconnect();
+
+ mCm.unregisterNetworkCallback(appCb1);
+ mCm.unregisterNetworkCallback(appCb2);
+ mCm.unregisterNetworkCallback(appCb3);
+ mCm.unregisterNetworkCallback(appCb4);
+ mCm.unregisterNetworkCallback(appCb5);
+ // Other callbacks will be unregistered by tearDown()
+ }
+
+ /**
* Test that, in a given networking context, calling setPreferenceForUser to set per-profile
* defaults on then off works as expected.
*/
@@ -14649,12 +15452,42 @@
public void testProfileNetworkPrefWrongProfile() throws Exception {
final UserHandle testHandle = UserHandle.of(TEST_WORK_PROFILE_USER_ID);
mServiceContext.setWorkProfile(testHandle, false);
- assertThrows("Should not be able to set a user pref for a non-work profile",
+ mServiceContext.setDeviceOwner(testHandle, null);
+ assertThrows("Should not be able to set a user pref for a non-work profile "
+ + "and non device owner",
IllegalArgumentException.class , () ->
mCm.setProfileNetworkPreference(testHandle,
PROFILE_NETWORK_PREFERENCE_ENTERPRISE, null, null));
}
+ /**
+ * Make sure requests for per-profile default networking for a device owner is
+ * accepted on T and not accepted on S
+ */
+ @Test
+ public void testProfileNetworkDeviceOwner() throws Exception {
+ final UserHandle testHandle = UserHandle.of(TEST_WORK_PROFILE_USER_ID);
+ mServiceContext.setWorkProfile(testHandle, false);
+ mServiceContext.setDeviceOwner(testHandle, "deviceOwnerPackage");
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
+ final TestOnCompleteListener listener = new TestOnCompleteListener();
+ if (SdkLevel.isAtLeastT()) {
+ mCm.setProfileNetworkPreferences(testHandle,
+ List.of(profileNetworkPreferenceBuilder.build()),
+ r -> r.run(), listener);
+ } else {
+ // S should not allow setting preference on device owner
+ assertThrows("Should not be able to set a user pref for a non-work profile on S",
+ IllegalArgumentException.class , () ->
+ mCm.setProfileNetworkPreferences(testHandle,
+ List.of(profileNetworkPreferenceBuilder.build()),
+ r -> r.run(), listener));
+ }
+ }
+
@Test
public void testSubIdsClearedWithoutNetworkFactoryPermission() throws Exception {
mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_DENIED);
@@ -14814,6 +15647,27 @@
}
@Test
+ public void testAutomotiveEthernetAllowedUids() throws Exception {
+ mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_GRANTED);
+ mServiceContext.setPermission(MANAGE_TEST_NETWORKS, PERMISSION_GRANTED);
+
+ // In this test the automotive feature will be enabled.
+ mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, true);
+
+ // Simulate a restricted ethernet network.
+ final NetworkCapabilities.Builder agentNetCaps = new NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_ETHERNET)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_NOT_SUSPENDED)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ .removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
+
+ mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET,
+ new LinkProperties(), agentNetCaps.build());
+ validateAllowedUids(mEthernetNetworkAgent, TRANSPORT_ETHERNET, agentNetCaps, true);
+ }
+
+ @Test
public void testCbsAllowedUids() throws Exception {
mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_GRANTED);
mServiceContext.setPermission(MANAGE_TEST_NETWORKS, PERMISSION_GRANTED);
@@ -14822,6 +15676,24 @@
doReturn(true).when(mCarrierPrivilegeAuthenticator)
.hasCarrierPrivilegeForNetworkCapabilities(eq(TEST_PACKAGE_UID), any());
+ // Simulate a restricted telephony network. The telephony factory is entitled to set
+ // the access UID to the service package on any of its restricted networks.
+ final NetworkCapabilities.Builder agentNetCaps = new NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_NOT_SUSPENDED)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ .setNetworkSpecifier(new TelephonyNetworkSpecifier(1 /* subid */));
+
+ mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR,
+ new LinkProperties(), agentNetCaps.build());
+ validateAllowedUids(mCellNetworkAgent, TRANSPORT_CELLULAR, agentNetCaps, false);
+ }
+
+ private void validateAllowedUids(final TestNetworkAgentWrapper testAgent,
+ @NetworkCapabilities.Transport final int transportUnderTest,
+ final NetworkCapabilities.Builder ncb, final boolean forAutomotive) throws Exception {
final ArraySet<Integer> serviceUidSet = new ArraySet<>();
serviceUidSet.add(TEST_PACKAGE_UID);
final ArraySet<Integer> nonServiceUidSet = new ArraySet<>();
@@ -14832,40 +15704,34 @@
final TestNetworkCallback cb = new TestNetworkCallback();
- // Simulate a restricted telephony network. The telephony factory is entitled to set
- // the access UID to the service package on any of its restricted networks.
- final NetworkCapabilities.Builder ncb = new NetworkCapabilities.Builder()
- .addTransportType(TRANSPORT_CELLULAR)
- .addCapability(NET_CAPABILITY_INTERNET)
- .addCapability(NET_CAPABILITY_NOT_SUSPENDED)
- .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
- .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
- .setNetworkSpecifier(new TelephonyNetworkSpecifier(1 /* subid */));
-
+ /* Test setting UIDs */
// Cell gets to set the service UID as access UID
mCm.requestNetwork(new NetworkRequest.Builder()
- .addTransportType(TRANSPORT_CELLULAR)
+ .addTransportType(transportUnderTest)
.removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
.build(), cb);
- mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR,
- new LinkProperties(), ncb.build());
- mCellNetworkAgent.connect(true);
- cb.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ testAgent.connect(true);
+ cb.expectAvailableThenValidatedCallbacks(testAgent);
ncb.setAllowedUids(serviceUidSet);
- mCellNetworkAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
+ testAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
if (SdkLevel.isAtLeastT()) {
- cb.expectCapabilitiesThat(mCellNetworkAgent,
+ cb.expectCapabilitiesThat(testAgent,
caps -> caps.getAllowedUids().equals(serviceUidSet));
} else {
// S must ignore access UIDs.
cb.assertNoCallback(TEST_CALLBACK_TIMEOUT_MS);
}
+ /* Test setting UIDs is rejected when expected */
+ if (forAutomotive) {
+ mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, false);
+ }
+
// ...but not to some other UID. Rejection sets UIDs to the empty set
ncb.setAllowedUids(nonServiceUidSet);
- mCellNetworkAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
+ testAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
if (SdkLevel.isAtLeastT()) {
- cb.expectCapabilitiesThat(mCellNetworkAgent,
+ cb.expectCapabilitiesThat(testAgent,
caps -> caps.getAllowedUids().isEmpty());
} else {
// S must ignore access UIDs.
@@ -14874,18 +15740,18 @@
// ...and also not to multiple UIDs even including the service UID
ncb.setAllowedUids(serviceUidSetPlus);
- mCellNetworkAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
+ testAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
cb.assertNoCallback(TEST_CALLBACK_TIMEOUT_MS);
- mCellNetworkAgent.disconnect();
- cb.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+ testAgent.disconnect();
+ cb.expectCallback(CallbackEntry.LOST, testAgent);
mCm.unregisterNetworkCallback(cb);
// Must be unset before touching the transports, because remove and add transport types
// check the specifier on the builder immediately, contradicting normal builder semantics
// TODO : fix the builder
ncb.setNetworkSpecifier(null);
- ncb.removeTransportType(TRANSPORT_CELLULAR);
+ ncb.removeTransportType(transportUnderTest);
ncb.addTransportType(TRANSPORT_WIFI);
// Wifi does not get to set access UID, even to the correct UID
mCm.requestNetwork(new NetworkRequest.Builder()
diff --git a/tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java
index 45f3d3c..9401d47 100644
--- a/tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java
+++ b/tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java
@@ -89,7 +89,7 @@
public class IpSecServiceParameterizedTest {
@Rule
public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule(
- Build.VERSION_CODES.R /* ignoreClassUpTo */);
+ Build.VERSION_CODES.S_V2 /* ignoreClassUpTo */);
private static final int TEST_SPI = 0xD1201D;
@@ -783,6 +783,23 @@
}
@Test
+ public void testSetNetworkForTunnelInterfaceFailsForNullLp() throws Exception {
+ final IpSecTunnelInterfaceResponse createTunnelResp =
+ createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
+ final Network newFakeNetwork = new Network(1000);
+ final int tunnelIfaceResourceId = createTunnelResp.resourceId;
+
+ try {
+ mIpSecService.setNetworkForTunnelInterface(
+ tunnelIfaceResourceId, newFakeNetwork, BLESSED_PACKAGE);
+ fail(
+ "Expected an IllegalArgumentException for underlying network with null"
+ + " LinkProperties");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
public void testSetNetworkForTunnelInterfaceFailsForInvalidResourceId() throws Exception {
final IpSecTunnelInterfaceResponse createTunnelResp =
createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
diff --git a/tests/unit/java/com/android/server/IpSecServiceRefcountedResourceTest.java b/tests/unit/java/com/android/server/IpSecServiceRefcountedResourceTest.java
index 5c7ca6f..8595ab9 100644
--- a/tests/unit/java/com/android/server/IpSecServiceRefcountedResourceTest.java
+++ b/tests/unit/java/com/android/server/IpSecServiceRefcountedResourceTest.java
@@ -54,7 +54,7 @@
/** Unit tests for {@link IpSecService.RefcountedResource}. */
@SmallTest
@RunWith(DevSdkIgnoreRunner.class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class IpSecServiceRefcountedResourceTest {
Context mMockContext;
IpSecService.Dependencies mMockDeps;
diff --git a/tests/unit/java/com/android/server/IpSecServiceTest.java b/tests/unit/java/com/android/server/IpSecServiceTest.java
index 7e6b157..6955620 100644
--- a/tests/unit/java/com/android/server/IpSecServiceTest.java
+++ b/tests/unit/java/com/android/server/IpSecServiceTest.java
@@ -75,7 +75,7 @@
/** Unit tests for {@link IpSecService}. */
@SmallTest
@RunWith(DevSdkIgnoreRunner.class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class IpSecServiceTest {
private static final int DROID_SPI = 0xD1201D;
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index 5086943..9365bee 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -21,8 +21,13 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -34,8 +39,15 @@
import android.compat.testing.PlatformCompatChangeRule;
import android.content.ContentResolver;
import android.content.Context;
+import android.net.INetd;
+import android.net.InetAddresses;
+import android.net.mdns.aidl.DiscoveryInfo;
+import android.net.mdns.aidl.GetAddressInfo;
+import android.net.mdns.aidl.IMDnsEventListener;
+import android.net.mdns.aidl.ResolutionInfo;
import android.net.nsd.INsdManagerCallback;
import android.net.nsd.INsdServiceConnector;
+import android.net.nsd.MDnsManager;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
import android.os.Binder;
@@ -49,9 +61,6 @@
import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
-import com.android.server.NsdService.DaemonConnection;
-import com.android.server.NsdService.DaemonConnectionSupplier;
-import com.android.server.NsdService.NativeCallbackReceiver;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
import com.android.testutils.HandlerUtils;
@@ -66,7 +75,6 @@
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
import java.util.LinkedList;
import java.util.Queue;
@@ -76,7 +84,7 @@
// - test NSD_ON ENABLE/DISABLED listening
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class NsdServiceTest {
static final int PROTOCOL = NsdManager.PROTOCOL_DNS_SD;
@@ -92,8 +100,7 @@
public TestRule compatChangeRule = new PlatformCompatChangeRule();
@Mock Context mContext;
@Mock ContentResolver mResolver;
- NativeCallbackReceiver mDaemonCallback;
- @Spy DaemonConnection mDaemon = new DaemonConnection(mDaemonCallback);
+ @Mock MDnsManager mMockMDnsM;
HandlerThread mThread;
TestHandler mHandler;
@@ -112,9 +119,21 @@
MockitoAnnotations.initMocks(this);
mThread = new HandlerThread("mock-service-handler");
mThread.start();
- doReturn(true).when(mDaemon).execute(any());
mHandler = new TestHandler(mThread.getLooper());
when(mContext.getContentResolver()).thenReturn(mResolver);
+ doReturn(MDnsManager.MDNS_SERVICE).when(mContext)
+ .getSystemServiceName(MDnsManager.class);
+ doReturn(mMockMDnsM).when(mContext).getSystemService(MDnsManager.MDNS_SERVICE);
+ if (mContext.getSystemService(MDnsManager.class) == null) {
+ // Test is using mockito-extended
+ doCallRealMethod().when(mContext).getSystemService(MDnsManager.class);
+ }
+ doReturn(true).when(mMockMDnsM).registerService(
+ anyInt(), anyString(), anyString(), anyInt(), any(), anyInt());
+ doReturn(true).when(mMockMDnsM).stopOperation(anyInt());
+ doReturn(true).when(mMockMDnsM).discover(anyInt(), anyString(), anyInt());
+ doReturn(true).when(mMockMDnsM).resolve(
+ anyInt(), anyString(), anyString(), anyString(), anyInt());
}
@After
@@ -135,24 +154,25 @@
waitForIdle();
final INsdManagerCallback cb1 = getCallback();
final IBinder.DeathRecipient deathRecipient1 = verifyLinkToDeath(cb1);
- verify(mDaemon, times(1)).maybeStart();
- verifyDaemonCommands("start-service");
+ verify(mMockMDnsM, times(1)).registerEventListener(any());
+ verify(mMockMDnsM, times(1)).startDaemon();
connectClient(service);
waitForIdle();
final INsdManagerCallback cb2 = getCallback();
final IBinder.DeathRecipient deathRecipient2 = verifyLinkToDeath(cb2);
- verify(mDaemon, times(1)).maybeStart();
+ // Daemon has been started, it should not try to start it again.
+ verify(mMockMDnsM, times(1)).registerEventListener(any());
+ verify(mMockMDnsM, times(1)).startDaemon();
deathRecipient1.binderDied();
// Still 1 client remains, daemon shouldn't be stopped.
waitForIdle();
- verify(mDaemon, never()).maybeStop();
+ verify(mMockMDnsM, never()).stopDaemon();
deathRecipient2.binderDied();
// All clients are disconnected, the daemon should be stopped.
verifyDelayMaybeStopDaemon(CLEANUP_DELAY_MS);
- verifyDaemonCommands("stop-service");
}
@Test
@@ -160,28 +180,30 @@
public void testNoDaemonStartedWhenClientsConnect() throws Exception {
final NsdService service = makeService();
- // Creating an NsdManager will not cause any cmds executed, which means
- // no daemon is started.
+ // Creating an NsdManager will not cause daemon startup.
connectClient(service);
waitForIdle();
- verify(mDaemon, never()).execute(any());
+ verify(mMockMDnsM, never()).registerEventListener(any());
+ verify(mMockMDnsM, never()).startDaemon();
final INsdManagerCallback cb1 = getCallback();
final IBinder.DeathRecipient deathRecipient1 = verifyLinkToDeath(cb1);
- // Creating another NsdManager will not cause any cmds executed.
+ // Creating another NsdManager will not cause daemon startup either.
connectClient(service);
waitForIdle();
- verify(mDaemon, never()).execute(any());
+ verify(mMockMDnsM, never()).registerEventListener(any());
+ verify(mMockMDnsM, never()).startDaemon();
final INsdManagerCallback cb2 = getCallback();
final IBinder.DeathRecipient deathRecipient2 = verifyLinkToDeath(cb2);
- // If there is no active request, try to clean up the daemon
- // every time the client disconnects.
+ // If there is no active request, try to clean up the daemon but should not do it because
+ // daemon has not been started.
deathRecipient1.binderDied();
- verifyDelayMaybeStopDaemon(CLEANUP_DELAY_MS);
- reset(mDaemon);
+ verify(mMockMDnsM, never()).unregisterEventListener(any());
+ verify(mMockMDnsM, never()).stopDaemon();
deathRecipient2.binderDied();
- verifyDelayMaybeStopDaemon(CLEANUP_DELAY_MS);
+ verify(mMockMDnsM, never()).unregisterEventListener(any());
+ verify(mMockMDnsM, never()).stopDaemon();
}
private IBinder.DeathRecipient verifyLinkToDeath(INsdManagerCallback cb)
@@ -200,8 +222,8 @@
waitForIdle();
final INsdManagerCallback cb1 = getCallback();
final IBinder.DeathRecipient deathRecipient = verifyLinkToDeath(cb1);
- verify(mDaemon, never()).maybeStart();
- verify(mDaemon, never()).execute(any());
+ verify(mMockMDnsM, never()).registerEventListener(any());
+ verify(mMockMDnsM, never()).startDaemon();
NsdServiceInfo request = new NsdServiceInfo("a_name", "a_type");
request.setPort(2201);
@@ -210,29 +232,31 @@
NsdManager.RegistrationListener listener1 = mock(NsdManager.RegistrationListener.class);
client.registerService(request, PROTOCOL, listener1);
waitForIdle();
- verify(mDaemon, times(1)).maybeStart();
- verifyDaemonCommands("start-service", "register 2 a_name a_type 2201");
+ verify(mMockMDnsM, times(1)).registerEventListener(any());
+ verify(mMockMDnsM, times(1)).startDaemon();
+ verify(mMockMDnsM, times(1)).registerService(
+ eq(2), eq("a_name"), eq("a_type"), eq(2201), any(), eq(0));
// Client discovery request
NsdManager.DiscoveryListener listener2 = mock(NsdManager.DiscoveryListener.class);
client.discoverServices("a_type", PROTOCOL, listener2);
waitForIdle();
- verify(mDaemon, times(1)).maybeStart();
- verifyDaemonCommand("discover 3 a_type 0");
+ verify(mMockMDnsM, times(1)).discover(eq(3), eq("a_type"), eq(0));
// Client resolve request
NsdManager.ResolveListener listener3 = mock(NsdManager.ResolveListener.class);
client.resolveService(request, listener3);
waitForIdle();
- verify(mDaemon, times(1)).maybeStart();
- verifyDaemonCommand("resolve 4 a_name a_type local. 0");
+ verify(mMockMDnsM, times(1)).resolve(
+ eq(4), eq("a_name"), eq("a_type"), eq("local."), eq(0));
// Client disconnects, stop the daemon after CLEANUP_DELAY_MS.
deathRecipient.binderDied();
verifyDelayMaybeStopDaemon(CLEANUP_DELAY_MS);
// checks that request are cleaned
- verifyDaemonCommands("stop-register 2", "stop-discover 3",
- "stop-resolve 4", "stop-service");
+ verify(mMockMDnsM, times(1)).stopOperation(eq(2));
+ verify(mMockMDnsM, times(1)).stopOperation(eq(3));
+ verify(mMockMDnsM, times(1)).stopOperation(eq(4));
}
@Test
@@ -246,20 +270,122 @@
NsdManager.RegistrationListener listener1 = mock(NsdManager.RegistrationListener.class);
client.registerService(request, PROTOCOL, listener1);
waitForIdle();
- verify(mDaemon, times(1)).maybeStart();
+ verify(mMockMDnsM, times(1)).registerEventListener(any());
+ verify(mMockMDnsM, times(1)).startDaemon();
final INsdManagerCallback cb1 = getCallback();
final IBinder.DeathRecipient deathRecipient = verifyLinkToDeath(cb1);
- verifyDaemonCommands("start-service", "register 2 a_name a_type 2201");
+ verify(mMockMDnsM, times(1)).registerService(
+ eq(2), eq("a_name"), eq("a_type"), eq(2201), any(), eq(0));
client.unregisterService(listener1);
- verifyDaemonCommand("stop-register 2");
+ waitForIdle();
+ verify(mMockMDnsM, times(1)).stopOperation(eq(2));
verifyDelayMaybeStopDaemon(CLEANUP_DELAY_MS);
- verifyDaemonCommand("stop-service");
- reset(mDaemon);
+ reset(mMockMDnsM);
deathRecipient.binderDied();
- // Client disconnects, after CLEANUP_DELAY_MS, maybeStop the daemon.
- verifyDelayMaybeStopDaemon(CLEANUP_DELAY_MS);
+ // Client disconnects, daemon should not be stopped after CLEANUP_DELAY_MS.
+ verify(mMockMDnsM, never()).unregisterEventListener(any());
+ verify(mMockMDnsM, never()).stopDaemon();
+ }
+
+ @Test
+ public void testDiscoverOnTetheringDownstream() throws Exception {
+ NsdService service = makeService();
+ NsdManager client = connectClient(service);
+
+ final String serviceType = "a_type";
+ final String serviceName = "a_name";
+ final String domainName = "mytestdevice.local";
+ final int interfaceIdx = 123;
+ final NsdManager.DiscoveryListener discListener = mock(NsdManager.DiscoveryListener.class);
+ client.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, discListener);
+ waitForIdle();
+
+ final ArgumentCaptor<IMDnsEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(IMDnsEventListener.class);
+ verify(mMockMDnsM).registerEventListener(listenerCaptor.capture());
+ final ArgumentCaptor<Integer> discIdCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mMockMDnsM).discover(discIdCaptor.capture(), eq(serviceType),
+ eq(0) /* interfaceIdx */);
+ // NsdManager uses a separate HandlerThread to dispatch callbacks (on ServiceHandler), so
+ // this needs to use a timeout
+ verify(discListener, timeout(TIMEOUT_MS)).onDiscoveryStarted(serviceType);
+
+ final DiscoveryInfo discoveryInfo = new DiscoveryInfo(
+ discIdCaptor.getValue(),
+ IMDnsEventListener.SERVICE_FOUND,
+ serviceName,
+ serviceType,
+ domainName,
+ interfaceIdx,
+ INetd.LOCAL_NET_ID); // LOCAL_NET_ID (99) used on tethering downstreams
+ final IMDnsEventListener eventListener = listenerCaptor.getValue();
+ eventListener.onServiceDiscoveryStatus(discoveryInfo);
+ waitForIdle();
+
+ final ArgumentCaptor<NsdServiceInfo> discoveredInfoCaptor =
+ ArgumentCaptor.forClass(NsdServiceInfo.class);
+ verify(discListener, timeout(TIMEOUT_MS)).onServiceFound(discoveredInfoCaptor.capture());
+ final NsdServiceInfo foundInfo = discoveredInfoCaptor.getValue();
+ assertEquals(serviceName, foundInfo.getServiceName());
+ assertEquals(serviceType, foundInfo.getServiceType());
+ assertNull(foundInfo.getHost());
+ assertNull(foundInfo.getNetwork());
+ assertEquals(interfaceIdx, foundInfo.getInterfaceIndex());
+
+ // After discovering the service, verify resolving it
+ final NsdManager.ResolveListener resolveListener = mock(NsdManager.ResolveListener.class);
+ client.resolveService(foundInfo, resolveListener);
+ waitForIdle();
+
+ final ArgumentCaptor<Integer> resolvIdCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mMockMDnsM).resolve(resolvIdCaptor.capture(), eq(serviceName), eq(serviceType),
+ eq("local.") /* domain */, eq(interfaceIdx));
+
+ final int servicePort = 10123;
+ final String serviceFullName = serviceName + "." + serviceType;
+ final ResolutionInfo resolutionInfo = new ResolutionInfo(
+ resolvIdCaptor.getValue(),
+ IMDnsEventListener.SERVICE_RESOLVED,
+ null /* serviceName */,
+ null /* serviceType */,
+ null /* domain */,
+ serviceFullName,
+ domainName,
+ servicePort,
+ new byte[0] /* txtRecord */,
+ interfaceIdx);
+
+ doReturn(true).when(mMockMDnsM).getServiceAddress(anyInt(), any(), anyInt());
+ eventListener.onServiceResolutionStatus(resolutionInfo);
+ waitForIdle();
+
+ final ArgumentCaptor<Integer> getAddrIdCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mMockMDnsM).getServiceAddress(getAddrIdCaptor.capture(), eq(domainName),
+ eq(interfaceIdx));
+
+ final String serviceAddress = "192.0.2.123";
+ final GetAddressInfo addressInfo = new GetAddressInfo(
+ getAddrIdCaptor.getValue(),
+ IMDnsEventListener.SERVICE_GET_ADDR_SUCCESS,
+ serviceFullName,
+ serviceAddress,
+ interfaceIdx,
+ INetd.LOCAL_NET_ID);
+ eventListener.onGettingServiceAddressStatus(addressInfo);
+ waitForIdle();
+
+ final ArgumentCaptor<NsdServiceInfo> resInfoCaptor =
+ ArgumentCaptor.forClass(NsdServiceInfo.class);
+ verify(resolveListener, timeout(TIMEOUT_MS)).onServiceResolved(resInfoCaptor.capture());
+ final NsdServiceInfo resolvedService = resInfoCaptor.getValue();
+ assertEquals(serviceName, resolvedService.getServiceName());
+ assertEquals("." + serviceType, resolvedService.getServiceType());
+ assertEquals(InetAddresses.parseNumericAddress(serviceAddress), resolvedService.getHost());
+ assertEquals(servicePort, resolvedService.getPort());
+ assertNull(resolvedService.getNetwork());
+ assertEquals(interfaceIdx, resolvedService.getInterfaceIndex());
}
private void waitForIdle() {
@@ -267,11 +393,7 @@
}
NsdService makeService() {
- DaemonConnectionSupplier supplier = (callback) -> {
- mDaemonCallback = callback;
- return mDaemon;
- };
- final NsdService service = new NsdService(mContext, mHandler, supplier, CLEANUP_DELAY_MS) {
+ final NsdService service = new NsdService(mContext, mHandler, CLEANUP_DELAY_MS) {
@Override
public INsdServiceConnector connect(INsdManagerCallback baseCb) {
// Wrap the callback in a transparent mock, to mock asBinder returning a
@@ -285,7 +407,6 @@
return super.connect(cb);
}
};
- verify(mDaemon, never()).execute(any(String.class));
return service;
}
@@ -297,34 +418,15 @@
return new NsdManager(mContext, service);
}
- void verifyDelayMaybeStopDaemon(long cleanupDelayMs) {
+ void verifyDelayMaybeStopDaemon(long cleanupDelayMs) throws Exception {
waitForIdle();
// Stop daemon shouldn't be called immediately.
- verify(mDaemon, never()).maybeStop();
+ verify(mMockMDnsM, never()).unregisterEventListener(any());
+ verify(mMockMDnsM, never()).stopDaemon();
+
// Clean up the daemon after CLEANUP_DELAY_MS.
- verify(mDaemon, timeout(cleanupDelayMs + TIMEOUT_MS)).maybeStop();
- }
-
- void verifyDaemonCommands(String... wants) {
- verifyDaemonCommand(String.join(" ", wants), wants.length);
- }
-
- void verifyDaemonCommand(String want) {
- verifyDaemonCommand(want, 1);
- }
-
- void verifyDaemonCommand(String want, int n) {
- waitForIdle();
- final ArgumentCaptor<Object> argumentsCaptor = ArgumentCaptor.forClass(Object.class);
- verify(mDaemon, times(n)).execute(argumentsCaptor.capture());
- String got = "";
- for (Object o : argumentsCaptor.getAllValues()) {
- got += o + " ";
- }
- assertEquals(want, got.trim());
- // rearm deamon for next command verification
- reset(mDaemon);
- doReturn(true).when(mDaemon).execute(any());
+ verify(mMockMDnsM, timeout(cleanupDelayMs + TIMEOUT_MS)).unregisterEventListener(any());
+ verify(mMockMDnsM, timeout(cleanupDelayMs + TIMEOUT_MS)).stopDaemon();
}
public static class TestHandler extends Handler {
diff --git a/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java b/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java
index 553cb83..157507b 100644
--- a/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java
@@ -69,8 +69,6 @@
@RunWith(DevSdkIgnoreRunner.class)
@IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
public class CarrierPrivilegeAuthenticatorTest {
- // TODO : use ConstantsShim.RECEIVER_NOT_EXPORTED when it's available in tests.
- private static final int RECEIVER_NOT_EXPORTED = 4;
private static final int SUBSCRIPTION_COUNT = 2;
private static final int TEST_SUBSCRIPTION_ID = 1;
@@ -117,7 +115,7 @@
private IntentFilter getIntentFilter() {
final ArgumentCaptor<IntentFilter> captor = ArgumentCaptor.forClass(IntentFilter.class);
- verify(mContext).registerReceiver(any(), captor.capture(), any(), any(), anyInt());
+ verify(mContext).registerReceiver(any(), captor.capture(), any(), any());
return captor.getValue();
}
@@ -140,11 +138,10 @@
@Test
public void testConstructor() throws Exception {
verify(mContext).registerReceiver(
- eq(mCarrierPrivilegeAuthenticator),
- any(IntentFilter.class),
- any(),
- any(),
- eq(RECEIVER_NOT_EXPORTED));
+ eq(mCarrierPrivilegeAuthenticator),
+ any(IntentFilter.class),
+ any(),
+ any());
final IntentFilter filter = getIntentFilter();
assertEquals(1, filter.countActions());
assertTrue(filter.hasAction(ACTION_MULTI_SIM_CONFIG_CHANGED));
diff --git a/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java b/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
index 8a2cfc2..3047a16 100644
--- a/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
@@ -17,30 +17,47 @@
package com.android.server.connectivity;
import static android.net.INetd.IF_STATE_UP;
+import static android.system.OsConstants.ETH_P_IP;
+import static android.system.OsConstants.ETH_P_IPV6;
import static com.android.net.module.util.NetworkStackConstants.ETHER_MTU;
import static com.android.server.connectivity.ClatCoordinator.CLAT_MAX_MTU;
+import static com.android.server.connectivity.ClatCoordinator.EGRESS;
+import static com.android.server.connectivity.ClatCoordinator.INGRESS;
import static com.android.server.connectivity.ClatCoordinator.INIT_V4ADDR_PREFIX_LEN;
import static com.android.server.connectivity.ClatCoordinator.INIT_V4ADDR_STRING;
+import static com.android.server.connectivity.ClatCoordinator.PRIO_CLAT;
import static com.android.testutils.MiscAsserts.assertThrows;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.argThat;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
import android.annotation.NonNull;
import android.net.INetd;
+import android.net.InetAddresses;
import android.net.IpPrefix;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import androidx.test.filters.SmallTest;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.net.module.util.IBpfMap;
+import com.android.net.module.util.bpf.ClatEgress4Key;
+import com.android.net.module.util.bpf.ClatEgress4Value;
+import com.android.net.module.util.bpf.ClatIngress6Key;
+import com.android.net.module.util.bpf.ClatIngress6Value;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
+import com.android.testutils.TestBpfMap;
import org.junit.Before;
import org.junit.Test;
@@ -52,6 +69,9 @@
import java.io.FileDescriptor;
import java.io.IOException;
+import java.io.StringWriter;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
import java.util.Objects;
@RunWith(DevSdkIgnoreRunner.class)
@@ -61,9 +81,12 @@
private static final String BASE_IFACE = "test0";
private static final String STACKED_IFACE = "v4-test0";
private static final int BASE_IFINDEX = 1000;
+ private static final int STACKED_IFINDEX = 1001;
private static final IpPrefix NAT64_IP_PREFIX = new IpPrefix("64:ff9b::/96");
private static final String NAT64_PREFIX_STRING = "64:ff9b::";
+ private static final Inet6Address INET6_PFX96 = (Inet6Address)
+ InetAddresses.parseNumericAddress(NAT64_PREFIX_STRING);
private static final int GOOGLE_DNS_4 = 0x08080808; // 8.8.8.8
private static final int NETID = 42;
@@ -74,18 +97,40 @@
private static final String XLAT_LOCAL_IPV4ADDR_STRING = "192.0.0.46";
private static final String XLAT_LOCAL_IPV6ADDR_STRING = "2001:db8:0:b11::464";
+ private static final Inet4Address INET4_LOCAL4 = (Inet4Address)
+ InetAddresses.parseNumericAddress(XLAT_LOCAL_IPV4ADDR_STRING);
+ private static final Inet6Address INET6_LOCAL6 = (Inet6Address)
+ InetAddresses.parseNumericAddress(XLAT_LOCAL_IPV6ADDR_STRING);
private static final int CLATD_PID = 10483;
private static final int TUN_FD = 534;
private static final int RAW_SOCK_FD = 535;
private static final int PACKET_SOCK_FD = 536;
private static final long RAW_SOCK_COOKIE = 27149;
- private static final ParcelFileDescriptor TUN_PFD = new ParcelFileDescriptor(
- new FileDescriptor());
- private static final ParcelFileDescriptor RAW_SOCK_PFD = new ParcelFileDescriptor(
- new FileDescriptor());
- private static final ParcelFileDescriptor PACKET_SOCK_PFD = new ParcelFileDescriptor(
- new FileDescriptor());
+ private static final ParcelFileDescriptor TUN_PFD = spy(new ParcelFileDescriptor(
+ new FileDescriptor()));
+ private static final ParcelFileDescriptor RAW_SOCK_PFD = spy(new ParcelFileDescriptor(
+ new FileDescriptor()));
+ private static final ParcelFileDescriptor PACKET_SOCK_PFD = spy(new ParcelFileDescriptor(
+ new FileDescriptor()));
+
+ private static final String EGRESS_PROG_PATH =
+ "/sys/fs/bpf/net_shared/prog_clatd_schedcls_egress4_clat_rawip";
+ private static final String INGRESS_PROG_PATH =
+ "/sys/fs/bpf/net_shared/prog_clatd_schedcls_ingress6_clat_ether";
+ private static final ClatEgress4Key EGRESS_KEY = new ClatEgress4Key(STACKED_IFINDEX,
+ INET4_LOCAL4);
+ private static final ClatEgress4Value EGRESS_VALUE = new ClatEgress4Value(BASE_IFINDEX,
+ INET6_LOCAL6, INET6_PFX96, (short) 1 /* oifIsEthernet, 1 = true */);
+ private static final ClatIngress6Key INGRESS_KEY = new ClatIngress6Key(BASE_IFINDEX,
+ INET6_PFX96, INET6_LOCAL6);
+ private static final ClatIngress6Value INGRESS_VALUE = new ClatIngress6Value(STACKED_IFINDEX,
+ INET4_LOCAL4);
+
+ private final TestBpfMap<ClatIngress6Key, ClatIngress6Value> mIngressMap =
+ spy(new TestBpfMap<>(ClatIngress6Key.class, ClatIngress6Value.class));
+ private final TestBpfMap<ClatEgress4Key, ClatEgress4Value> mEgressMap =
+ spy(new TestBpfMap<>(ClatEgress4Key.class, ClatEgress4Value.class));
@Mock private INetd mNetd;
@Spy private TestDependencies mDeps = new TestDependencies();
@@ -129,6 +174,8 @@
public int getInterfaceIndex(String ifName) {
if (BASE_IFACE.equals(ifName)) {
return BASE_IFINDEX;
+ } else if (STACKED_IFACE.equals(ifName)) {
+ return STACKED_IFINDEX;
}
fail("unsupported arg: " + ifName);
return -1;
@@ -285,6 +332,49 @@
fail("unsupported arg: " + cookie);
}
}
+
+ /** Get ingress6 BPF map. */
+ @Override
+ public IBpfMap<ClatIngress6Key, ClatIngress6Value> getBpfIngress6Map() {
+ return mIngressMap;
+ }
+
+ /** Get egress4 BPF map. */
+ @Override
+ public IBpfMap<ClatEgress4Key, ClatEgress4Value> getBpfEgress4Map() {
+ return mEgressMap;
+ }
+
+ /** Checks if the network interface uses an ethernet L2 header. */
+ public boolean isEthernet(String iface) throws IOException {
+ if (BASE_IFACE.equals(iface)) return true;
+
+ fail("unsupported arg: " + iface);
+ return false;
+ }
+
+ /** Add a clsact qdisc. */
+ @Override
+ public void tcQdiscAddDevClsact(int ifIndex) throws IOException {
+ // no-op
+ return;
+ }
+
+ /** Attach a tc bpf filter. */
+ @Override
+ public void tcFilterAddDevBpf(int ifIndex, boolean ingress, short prio, short proto,
+ String bpfProgPath) throws IOException {
+ // no-op
+ return;
+ }
+
+ /** Delete a tc filter. */
+ @Override
+ public void tcFilterDelDev(int ifIndex, boolean ingress, short prio, short proto)
+ throws IOException {
+ // no-op
+ return;
+ }
};
@NonNull
@@ -309,12 +399,17 @@
@Test
public void testStartStopClatd() throws Exception {
final ClatCoordinator coordinator = makeClatCoordinator();
- final InOrder inOrder = inOrder(mNetd, mDeps);
- clearInvocations(mNetd, mDeps);
+ final InOrder inOrder = inOrder(mNetd, mDeps, mIngressMap, mEgressMap);
+ clearInvocations(mNetd, mDeps, mIngressMap, mEgressMap);
// [1] Start clatd.
final String addr6For464xlat = coordinator.clatStart(BASE_IFACE, NETID, NAT64_IP_PREFIX);
assertEquals(XLAT_LOCAL_IPV6ADDR_STRING, addr6For464xlat);
+ final ClatCoordinator.ClatdTracker expected = new ClatCoordinator.ClatdTracker(
+ BASE_IFACE, BASE_IFINDEX, STACKED_IFACE, STACKED_IFINDEX,
+ INET4_LOCAL4, INET6_LOCAL6, INET6_PFX96, CLATD_PID, RAW_SOCK_COOKIE);
+ final ClatCoordinator.ClatdTracker actual = coordinator.getClatdTrackerForTesting();
+ assertEquals(expected, actual);
// Pick an IPv4 address.
inOrder.verify(mDeps).selectIpv4Address(eq(INIT_V4ADDR_STRING),
@@ -327,6 +422,7 @@
// Open, configure and bring up the tun interface.
inOrder.verify(mDeps).createTunInterface(eq(STACKED_IFACE));
inOrder.verify(mDeps).adoptFd(eq(TUN_FD));
+ inOrder.verify(mDeps).getInterfaceIndex(eq(STACKED_IFACE));
inOrder.verify(mNetd).interfaceSetEnableIPv6(eq(STACKED_IFACE), eq(false /* enable */));
inOrder.verify(mDeps).detectMtu(eq(NAT64_PREFIX_STRING), eq(GOOGLE_DNS_4), eq(MARK));
inOrder.verify(mNetd).interfaceSetMtu(eq(STACKED_IFACE),
@@ -360,6 +456,13 @@
argThat(fd -> Objects.equals(RAW_SOCK_PFD.getFileDescriptor(), fd)),
eq(BASE_IFACE), eq(NAT64_PREFIX_STRING),
eq(XLAT_LOCAL_IPV4ADDR_STRING), eq(XLAT_LOCAL_IPV6ADDR_STRING));
+ inOrder.verify(mEgressMap).insertEntry(eq(EGRESS_KEY), eq(EGRESS_VALUE));
+ inOrder.verify(mIngressMap).insertEntry(eq(INGRESS_KEY), eq(INGRESS_VALUE));
+ inOrder.verify(mDeps).tcQdiscAddDevClsact(eq(STACKED_IFINDEX));
+ inOrder.verify(mDeps).tcFilterAddDevBpf(eq(STACKED_IFINDEX), eq(EGRESS),
+ eq((short) PRIO_CLAT), eq((short) ETH_P_IP), eq(EGRESS_PROG_PATH));
+ inOrder.verify(mDeps).tcFilterAddDevBpf(eq(BASE_IFINDEX), eq(INGRESS),
+ eq((short) PRIO_CLAT), eq((short) ETH_P_IPV6), eq(INGRESS_PROG_PATH));
inOrder.verifyNoMoreInteractions();
// [2] Start clatd again failed.
@@ -369,9 +472,16 @@
// [3] Expect clatd to stop successfully.
coordinator.clatStop();
+ inOrder.verify(mDeps).tcFilterDelDev(eq(BASE_IFINDEX), eq(INGRESS),
+ eq((short) PRIO_CLAT), eq((short) ETH_P_IPV6));
+ inOrder.verify(mDeps).tcFilterDelDev(eq(STACKED_IFINDEX), eq(EGRESS),
+ eq((short) PRIO_CLAT), eq((short) ETH_P_IP));
+ inOrder.verify(mEgressMap).deleteEntry(eq(EGRESS_KEY));
+ inOrder.verify(mIngressMap).deleteEntry(eq(INGRESS_KEY));
inOrder.verify(mDeps).stopClatd(eq(BASE_IFACE), eq(NAT64_PREFIX_STRING),
eq(XLAT_LOCAL_IPV4ADDR_STRING), eq(XLAT_LOCAL_IPV6ADDR_STRING), eq(CLATD_PID));
inOrder.verify(mDeps).untagSocket(eq(RAW_SOCK_COOKIE));
+ assertNull(coordinator.getClatdTrackerForTesting());
inOrder.verifyNoMoreInteractions();
// [4] Expect an IO exception while stopping a clatd that doesn't exist.
@@ -404,4 +514,190 @@
// Expected mtu is that CLAT_MAX_MTU(65536) minus MTU_DELTA(28).
assertEquals(65508, ClatCoordinator.adjustMtu(CLAT_MAX_MTU + 1 /* over maximum mtu */));
}
+
+ @Test
+ public void testDump() throws Exception {
+ final ClatCoordinator coordinator = makeClatCoordinator();
+ final StringWriter stringWriter = new StringWriter();
+ final IndentingPrintWriter ipw = new IndentingPrintWriter(stringWriter, " ");
+ coordinator.clatStart(BASE_IFACE, NETID, NAT64_IP_PREFIX);
+ coordinator.dump(ipw);
+
+ final String[] dumpStrings = stringWriter.toString().split("\n");
+ assertEquals(5, dumpStrings.length);
+ assertEquals("Forwarding rules:", dumpStrings[0].trim());
+ assertEquals("BPF ingress map: iif nat64Prefix v6Addr -> v4Addr oif",
+ dumpStrings[1].trim());
+ assertEquals("1000 /64:ff9b::/96 /2001:db8:0:b11::464 -> /192.0.0.46 1001",
+ dumpStrings[2].trim());
+ assertEquals("BPF egress map: iif v4Addr -> v6Addr nat64Prefix oif",
+ dumpStrings[3].trim());
+ assertEquals("1001 /192.0.0.46 -> /2001:db8:0:b11::464 /64:ff9b::/96 1000 ether",
+ dumpStrings[4].trim());
+ }
+
+ @Test
+ public void testNotStartClatWithInvalidPrefix() throws Exception {
+ final ClatCoordinator coordinator = makeClatCoordinator();
+ final IpPrefix invalidPrefix = new IpPrefix("2001:db8::/64");
+ assertThrows(IOException.class,
+ () -> coordinator.clatStart(BASE_IFACE, NETID, invalidPrefix));
+ }
+
+ private void assertStartClat(final TestDependencies deps) throws Exception {
+ final ClatCoordinator coordinator = new ClatCoordinator(deps);
+ assertNotNull(coordinator.clatStart(BASE_IFACE, NETID, NAT64_IP_PREFIX));
+ }
+
+ private void assertNotStartClat(final TestDependencies deps) {
+ // Expect that the injection function of TestDependencies causes clatStart() failed.
+ final ClatCoordinator coordinator = new ClatCoordinator(deps);
+ assertThrows(IOException.class,
+ () -> coordinator.clatStart(BASE_IFACE, NETID, NAT64_IP_PREFIX));
+ }
+
+ private void checkNotStartClat(final TestDependencies deps, final boolean verifyTunFd,
+ final boolean verifyPacketSockFd, final boolean verifyRawSockFd) throws Exception {
+ // [1] Expect that modified TestDependencies can't start clatd.
+ clearInvocations(TUN_PFD, RAW_SOCK_PFD, PACKET_SOCK_PFD);
+ assertNotStartClat(deps);
+ if (verifyTunFd) verify(TUN_PFD).close();
+ if (verifyPacketSockFd) verify(PACKET_SOCK_PFD).close();
+ if (verifyRawSockFd) verify(RAW_SOCK_PFD).close();
+
+ // [2] Expect that unmodified TestDependencies can start clatd.
+ // Used to make sure that the above modified TestDependencies has really broken the
+ // clatd starting.
+ assertStartClat(new TestDependencies());
+ }
+
+ // The following testNotStartClat* tests verifies bunches of code for unwinding the
+ // failure if any.
+ @Test
+ public void testNotStartClatWithNativeFailureSelectIpv4Address() throws Exception {
+ class FailureDependencies extends TestDependencies {
+ @Override
+ public String selectIpv4Address(@NonNull String v4addr, int prefixlen)
+ throws IOException {
+ throw new IOException();
+ }
+ }
+ checkNotStartClat(new FailureDependencies(), false /* verifyTunFd */,
+ false /* verifyPacketSockFd */, false /* verifyRawSockFd */);
+ }
+
+ @Test
+ public void testNotStartClatWithNativeFailureGenerateIpv6Address() throws Exception {
+ class FailureDependencies extends TestDependencies {
+ @Override
+ public String generateIpv6Address(@NonNull String iface, @NonNull String v4,
+ @NonNull String prefix64) throws IOException {
+ throw new IOException();
+ }
+ }
+ checkNotStartClat(new FailureDependencies(), false /* verifyTunFd */,
+ false /* verifyPacketSockFd */, false /* verifyRawSockFd */);
+ }
+
+ @Test
+ public void testNotStartClatWithNativeFailureCreateTunInterface() throws Exception {
+ class FailureDependencies extends TestDependencies {
+ @Override
+ public int createTunInterface(@NonNull String tuniface) throws IOException {
+ throw new IOException();
+ }
+ }
+ checkNotStartClat(new FailureDependencies(), false /* verifyTunFd */,
+ false /* verifyPacketSockFd */, false /* verifyRawSockFd */);
+ }
+
+ @Test
+ public void testNotStartClatWithNativeFailureDetectMtu() throws Exception {
+ class FailureDependencies extends TestDependencies {
+ @Override
+ public int detectMtu(@NonNull String platSubnet, int platSuffix, int mark)
+ throws IOException {
+ throw new IOException();
+ }
+ }
+ checkNotStartClat(new FailureDependencies(), true /* verifyTunFd */,
+ false /* verifyPacketSockFd */, false /* verifyRawSockFd */);
+ }
+
+ @Test
+ public void testNotStartClatWithNativeFailureOpenPacketSocket() throws Exception {
+ class FailureDependencies extends TestDependencies {
+ @Override
+ public int openPacketSocket() throws IOException {
+ throw new IOException();
+ }
+ }
+ checkNotStartClat(new FailureDependencies(), true /* verifyTunFd */,
+ false /* verifyPacketSockFd */, false /* verifyRawSockFd */);
+ }
+
+ @Test
+ public void testNotStartClatWithNativeFailureOpenRawSocket6() throws Exception {
+ class FailureDependencies extends TestDependencies {
+ @Override
+ public int openRawSocket6(int mark) throws IOException {
+ throw new IOException();
+ }
+ }
+ checkNotStartClat(new FailureDependencies(), true /* verifyTunFd */,
+ true /* verifyPacketSockFd */, false /* verifyRawSockFd */);
+ }
+
+ @Test
+ public void testNotStartClatWithNativeFailureAddAnycastSetsockopt() throws Exception {
+ class FailureDependencies extends TestDependencies {
+ @Override
+ public void addAnycastSetsockopt(@NonNull FileDescriptor sock, String v6,
+ int ifindex) throws IOException {
+ throw new IOException();
+ }
+ }
+ checkNotStartClat(new FailureDependencies(), true /* verifyTunFd */,
+ true /* verifyPacketSockFd */, true /* verifyRawSockFd */);
+ }
+
+ @Test
+ public void testNotStartClatWithNativeFailureTagSocketAsClat() throws Exception {
+ class FailureDependencies extends TestDependencies {
+ @Override
+ public long tagSocketAsClat(@NonNull FileDescriptor sock) throws IOException {
+ throw new IOException();
+ }
+ }
+ checkNotStartClat(new FailureDependencies(), true /* verifyTunFd */,
+ true /* verifyPacketSockFd */, true /* verifyRawSockFd */);
+ }
+
+ @Test
+ public void testNotStartClatWithNativeFailureConfigurePacketSocket() throws Exception {
+ class FailureDependencies extends TestDependencies {
+ @Override
+ public void configurePacketSocket(@NonNull FileDescriptor sock, String v6,
+ int ifindex) throws IOException {
+ throw new IOException();
+ }
+ }
+ checkNotStartClat(new FailureDependencies(), true /* verifyTunFd */,
+ true /* verifyPacketSockFd */, true /* verifyRawSockFd */);
+ }
+
+ @Test
+ public void testNotStartClatWithNativeFailureStartClatd() throws Exception {
+ class FailureDependencies extends TestDependencies {
+ @Override
+ public int startClatd(@NonNull FileDescriptor tunfd, @NonNull FileDescriptor readsock6,
+ @NonNull FileDescriptor writesock6, @NonNull String iface,
+ @NonNull String pfx96, @NonNull String v4, @NonNull String v6)
+ throws IOException {
+ throw new IOException();
+ }
+ }
+ checkNotStartClat(new FailureDependencies(), true /* verifyTunFd */,
+ true /* verifyPacketSockFd */, true /* verifyRawSockFd */);
+ }
}
diff --git a/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt b/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt
index e7f6245..c03a9cd 100644
--- a/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt
@@ -22,6 +22,7 @@
import android.os.Build
import android.text.TextUtils
import android.util.ArraySet
+import android.util.Log
import androidx.test.filters.SmallTest
import com.android.server.connectivity.FullScore.MAX_CS_MANAGED_POLICY
import com.android.server.connectivity.FullScore.POLICY_ACCEPT_UNVALIDATED
@@ -32,11 +33,12 @@
import com.android.server.connectivity.FullScore.POLICY_IS_VPN
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRunner
+import org.junit.After
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import kotlin.reflect.full.staticProperties
import kotlin.test.assertEquals
-import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@@ -63,6 +65,23 @@
return mixInScore(nc, nac, validated, false /* yieldToBadWifi */, destroyed)
}
+ private val TAG = this::class.simpleName
+
+ private var wtfHandler: Log.TerribleFailureHandler? = null
+
+ @Before
+ fun setUp() {
+ // policyNameOf will call Log.wtf if passed an invalid policy.
+ wtfHandler = Log.setWtfHandler() { tagString, what, system ->
+ Log.d(TAG, "WTF captured, ignoring: $tagString $what")
+ }
+ }
+
+ @After
+ fun tearDown() {
+ Log.setWtfHandler(wtfHandler)
+ }
+
@Test
fun testGetLegacyInt() {
val ns = FullScore(50, 0L /* policy */, KEEP_CONNECTED_NONE)
@@ -101,10 +120,9 @@
assertFalse(foundNames.contains(name))
foundNames.add(name)
}
- assertFailsWith<IllegalArgumentException> {
- FullScore.policyNameOf(MAX_CS_MANAGED_POLICY + 1)
- }
assertEquals("IS_UNMETERED", FullScore.policyNameOf(POLICY_IS_UNMETERED))
+ val invalidPolicy = MAX_CS_MANAGED_POLICY + 1
+ assertEquals(Integer.toString(invalidPolicy), FullScore.policyNameOf(invalidPolicy))
}
fun getAllPolicies() = Regex("POLICY_.*").let { nameRegex ->
diff --git a/tests/unit/java/com/android/server/connectivity/Nat464XlatTest.java b/tests/unit/java/com/android/server/connectivity/Nat464XlatTest.java
index aa4c4e3..06e0d6d 100644
--- a/tests/unit/java/com/android/server/connectivity/Nat464XlatTest.java
+++ b/tests/unit/java/com/android/server/connectivity/Nat464XlatTest.java
@@ -21,6 +21,8 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.inOrder;
@@ -44,8 +46,11 @@
import android.os.Handler;
import android.os.test.TestLooper;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
+import com.android.modules.utils.build.SdkLevel;
import com.android.server.ConnectivityService;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
@@ -75,13 +80,20 @@
@Mock IDnsResolver mDnsResolver;
@Mock INetd mNetd;
@Mock NetworkAgentInfo mNai;
+ @Mock ClatCoordinator mClatCoordinator;
TestLooper mLooper;
Handler mHandler;
NetworkAgentConfig mAgentConfig = new NetworkAgentConfig();
Nat464Xlat makeNat464Xlat(boolean isCellular464XlatEnabled) {
- return new Nat464Xlat(mNai, mNetd, mDnsResolver, new ConnectivityService.Dependencies()) {
+ final ConnectivityService.Dependencies deps = new ConnectivityService.Dependencies() {
+ @Override public ClatCoordinator getClatCoordinator(INetd netd) {
+ return mClatCoordinator;
+ }
+ };
+
+ return new Nat464Xlat(mNai, mNetd, mDnsResolver, deps) {
@Override protected int getNetId() {
return NETID;
}
@@ -208,6 +220,39 @@
}
}
+ private <T> T verifyWithOrder(@Nullable InOrder inOrder, @NonNull T t) {
+ if (inOrder != null) {
+ return inOrder.verify(t);
+ } else {
+ return verify(t);
+ }
+ }
+
+ private void verifyClatdStart(@Nullable InOrder inOrder) throws Exception {
+ if (SdkLevel.isAtLeastT()) {
+ verifyWithOrder(inOrder, mClatCoordinator)
+ .clatStart(eq(BASE_IFACE), eq(NETID), eq(new IpPrefix(NAT64_PREFIX)));
+ } else {
+ verifyWithOrder(inOrder, mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX));
+ }
+ }
+
+ private void verifyNeverClatdStart() throws Exception {
+ if (SdkLevel.isAtLeastT()) {
+ verify(mClatCoordinator, never()).clatStart(anyString(), anyInt(), any());
+ } else {
+ verify(mNetd, never()).clatdStart(anyString(), anyString());
+ }
+ }
+
+ private void verifyClatdStop(@Nullable InOrder inOrder) throws Exception {
+ if (SdkLevel.isAtLeastT()) {
+ verifyWithOrder(inOrder, mClatCoordinator).clatStop();
+ } else {
+ verifyWithOrder(inOrder, mNetd).clatdStop(eq(BASE_IFACE));
+ }
+ }
+
private void checkNormalStartAndStop(boolean dueToDisconnect) throws Exception {
Nat464Xlat nat = makeNat464Xlat(true);
ArgumentCaptor<LinkProperties> c = ArgumentCaptor.forClass(LinkProperties.class);
@@ -219,7 +264,7 @@
// Start clat.
nat.start();
- verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX));
+ verifyClatdStart(null /* inOrder */);
// Stacked interface up notification arrives.
nat.interfaceLinkStateChanged(STACKED_IFACE, true);
@@ -235,7 +280,7 @@
makeClatUnnecessary(dueToDisconnect);
nat.stop();
- verify(mNetd).clatdStop(eq(BASE_IFACE));
+ verifyClatdStop(null /* inOrder */);
verify(mConnectivity, times(2)).handleUpdateLinkProperties(eq(mNai), c.capture());
assertTrue(c.getValue().getStackedLinks().isEmpty());
assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
@@ -262,7 +307,7 @@
private void checkStartStopStart(boolean interfaceRemovedFirst) throws Exception {
Nat464Xlat nat = makeNat464Xlat(true);
ArgumentCaptor<LinkProperties> c = ArgumentCaptor.forClass(LinkProperties.class);
- InOrder inOrder = inOrder(mNetd, mConnectivity);
+ InOrder inOrder = inOrder(mNetd, mConnectivity, mClatCoordinator);
mNai.linkProperties.addLinkAddress(V6ADDR);
@@ -270,7 +315,7 @@
nat.start();
- inOrder.verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX));
+ verifyClatdStart(inOrder);
// Stacked interface up notification arrives.
nat.interfaceLinkStateChanged(STACKED_IFACE, true);
@@ -284,7 +329,7 @@
// ConnectivityService stops clat (Network disconnects, IPv4 addr appears, ...).
nat.stop();
- inOrder.verify(mNetd).clatdStop(eq(BASE_IFACE));
+ verifyClatdStop(inOrder);
inOrder.verify(mConnectivity, times(1)).handleUpdateLinkProperties(eq(mNai), c.capture());
assertTrue(c.getValue().getStackedLinks().isEmpty());
@@ -306,7 +351,7 @@
nat.start();
- inOrder.verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX));
+ verifyClatdStart(inOrder);
if (!interfaceRemovedFirst) {
// Stacked interface removed notification arrives and is ignored.
@@ -328,7 +373,7 @@
// ConnectivityService stops clat again.
nat.stop();
- inOrder.verify(mNetd).clatdStop(eq(BASE_IFACE));
+ verifyClatdStop(inOrder);
inOrder.verify(mConnectivity, times(1)).handleUpdateLinkProperties(eq(mNai), c.capture());
assertTrue(c.getValue().getStackedLinks().isEmpty());
@@ -357,7 +402,7 @@
nat.start();
- verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX));
+ verifyClatdStart(null /* inOrder */);
// Stacked interface up notification arrives.
nat.interfaceLinkStateChanged(STACKED_IFACE, true);
@@ -373,7 +418,7 @@
nat.interfaceRemoved(STACKED_IFACE);
mLooper.dispatchNext();
- verify(mNetd).clatdStop(eq(BASE_IFACE));
+ verifyClatdStop(null /* inOrder */);
verify(mConnectivity, times(2)).handleUpdateLinkProperties(eq(mNai), c.capture());
verify(mDnsResolver).stopPrefix64Discovery(eq(NETID));
assertTrue(c.getValue().getStackedLinks().isEmpty());
@@ -395,13 +440,13 @@
nat.start();
- verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX));
+ verifyClatdStart(null /* inOrder */);
// ConnectivityService immediately stops clat (Network disconnects, IPv4 addr appears, ...)
makeClatUnnecessary(dueToDisconnect);
nat.stop();
- verify(mNetd).clatdStop(eq(BASE_IFACE));
+ verifyClatdStop(null /* inOrder */);
verify(mDnsResolver).stopPrefix64Discovery(eq(NETID));
assertIdle(nat);
@@ -437,13 +482,13 @@
nat.start();
- verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX));
+ verifyClatdStart(null /* inOrder */);
// ConnectivityService immediately stops clat (Network disconnects, IPv4 addr appears, ...)
makeClatUnnecessary(dueToDisconnect);
nat.stop();
- verify(mNetd).clatdStop(eq(BASE_IFACE));
+ verifyClatdStop(null /* inOrder */);
verify(mDnsResolver).stopPrefix64Discovery(eq(NETID));
assertIdle(nat);
@@ -518,7 +563,7 @@
mNai.linkProperties.setNat64Prefix(nat64Prefix);
nat.setNat64PrefixFromRa(nat64Prefix);
nat.update();
- verify(mNetd, never()).clatdStart(anyString(), anyString());
+ verifyNeverClatdStart();
assertIdle(nat);
} else {
// Prefix discovery is started.
@@ -529,7 +574,7 @@
mNai.linkProperties.setNat64Prefix(nat64Prefix);
nat.setNat64PrefixFromRa(nat64Prefix);
nat.update();
- verify(mNetd).clatdStart(BASE_IFACE, NAT64_PREFIX);
+ verifyClatdStart(null /* inOrder */);
assertStarting(nat);
}
}
diff --git a/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
index 6b379e8..ecd17ba 100644
--- a/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
@@ -30,6 +30,9 @@
import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_REQUIRED;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
import static android.content.pm.PackageManager.MATCH_ANY_USER;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOCKDOWN_VPN;
+import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
+import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
import static android.net.ConnectivitySettingsManager.UIDS_ALLOWED_ON_RESTRICTED_NETWORKS;
import static android.net.INetd.PERMISSION_INTERNET;
import static android.net.INetd.PERMISSION_NETWORK;
@@ -117,23 +120,32 @@
public class PermissionMonitorTest {
private static final int MOCK_USER_ID1 = 0;
private static final int MOCK_USER_ID2 = 1;
+ private static final int MOCK_USER_ID3 = 2;
private static final UserHandle MOCK_USER1 = UserHandle.of(MOCK_USER_ID1);
private static final UserHandle MOCK_USER2 = UserHandle.of(MOCK_USER_ID2);
+ private static final UserHandle MOCK_USER3 = UserHandle.of(MOCK_USER_ID3);
private static final int MOCK_APPID1 = 10001;
private static final int MOCK_APPID2 = 10086;
+ private static final int MOCK_APPID3 = 10110;
private static final int SYSTEM_APPID1 = 1100;
private static final int SYSTEM_APPID2 = 1108;
private static final int VPN_APPID = 10002;
private static final int MOCK_UID11 = MOCK_USER1.getUid(MOCK_APPID1);
private static final int MOCK_UID12 = MOCK_USER1.getUid(MOCK_APPID2);
+ private static final int MOCK_UID13 = MOCK_USER1.getUid(MOCK_APPID3);
private static final int SYSTEM_APP_UID11 = MOCK_USER1.getUid(SYSTEM_APPID1);
private static final int VPN_UID = MOCK_USER1.getUid(VPN_APPID);
private static final int MOCK_UID21 = MOCK_USER2.getUid(MOCK_APPID1);
private static final int MOCK_UID22 = MOCK_USER2.getUid(MOCK_APPID2);
+ private static final int MOCK_UID23 = MOCK_USER2.getUid(MOCK_APPID3);
private static final int SYSTEM_APP_UID21 = MOCK_USER2.getUid(SYSTEM_APPID1);
+ private static final int MOCK_UID31 = MOCK_USER3.getUid(MOCK_APPID1);
+ private static final int MOCK_UID32 = MOCK_USER3.getUid(MOCK_APPID2);
+ private static final int MOCK_UID33 = MOCK_USER3.getUid(MOCK_APPID3);
private static final String REAL_SYSTEM_PACKAGE_NAME = "android";
private static final String MOCK_PACKAGE1 = "appName1";
private static final String MOCK_PACKAGE2 = "appName2";
+ private static final String MOCK_PACKAGE3 = "appName3";
private static final String SYSTEM_PACKAGE1 = "sysName1";
private static final String SYSTEM_PACKAGE2 = "sysName2";
private static final String PARTITION_SYSTEM = "system";
@@ -191,6 +203,7 @@
mBpfMapMonitor = new BpfMapMonitor(mBpfNetMaps);
doReturn(List.of()).when(mPackageManager).getInstalledPackagesAsUser(anyInt(), anyInt());
+ mPermissionMonitor.onUserAdded(MOCK_USER1);
}
private boolean hasRestrictedNetworkPermission(String partition, int targetSdkVersion,
@@ -283,6 +296,18 @@
mPermissionMonitor.onPackageAdded(packageName, uid);
}
+ private void removePackage(String packageName, int uid) {
+ final String[] oldPackages = mPackageManager.getPackagesForUid(uid);
+ // If the package isn't existed, no need to remove it.
+ if (!CollectionUtils.contains(oldPackages, packageName)) return;
+
+ // Remove the package if this uid is shared with other packages.
+ final String[] newPackages = Arrays.stream(oldPackages).filter(e -> !e.equals(packageName))
+ .toArray(String[]::new);
+ doReturn(newPackages).when(mPackageManager).getPackagesForUid(eq(uid));
+ mPermissionMonitor.onPackageRemoved(packageName, uid);
+ }
+
@Test
public void testHasPermission() {
PackageInfo app = systemPackageInfoWithPermissions();
@@ -739,8 +764,8 @@
MOCK_APPID1);
}
- @Test
- public void testUidFilteringDuringVpnConnectDisconnectAndUidUpdates() throws Exception {
+ private void doTestuidFilteringDuringVpnConnectDisconnectAndUidUpdates(@Nullable String ifName)
+ throws Exception {
doReturn(List.of(buildPackageInfo(SYSTEM_PACKAGE1, SYSTEM_APP_UID11, CHANGE_NETWORK_STATE,
CONNECTIVITY_USE_RESTRICTED_NETWORKS),
buildPackageInfo(MOCK_PACKAGE1, MOCK_UID11),
@@ -756,8 +781,8 @@
final Set<UidRange> vpnRange2 = Set.of(new UidRange(MOCK_UID12, MOCK_UID12));
// When VPN is connected, expect a rule to be set up for user app MOCK_UID11
- mPermissionMonitor.onVpnUidRangesAdded("tun0", vpnRange1, VPN_UID);
- verify(mBpfNetMaps).addUidInterfaceRules(eq("tun0"), aryEq(new int[]{MOCK_UID11}));
+ mPermissionMonitor.onVpnUidRangesAdded(ifName, vpnRange1, VPN_UID);
+ verify(mBpfNetMaps).addUidInterfaceRules(eq(ifName), aryEq(new int[]{MOCK_UID11}));
reset(mBpfNetMaps);
@@ -765,42 +790,54 @@
mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID11);
verify(mBpfNetMaps).removeUidInterfaceRules(aryEq(new int[]{MOCK_UID11}));
mPermissionMonitor.onPackageAdded(MOCK_PACKAGE1, MOCK_UID11);
- verify(mBpfNetMaps).addUidInterfaceRules(eq("tun0"), aryEq(new int[]{MOCK_UID11}));
+ verify(mBpfNetMaps).addUidInterfaceRules(eq(ifName), aryEq(new int[]{MOCK_UID11}));
reset(mBpfNetMaps);
// During VPN uid update (vpnRange1 -> vpnRange2), ConnectivityService first deletes the
// old UID rules then adds the new ones. Expect netd to be updated
- mPermissionMonitor.onVpnUidRangesRemoved("tun0", vpnRange1, VPN_UID);
+ mPermissionMonitor.onVpnUidRangesRemoved(ifName, vpnRange1, VPN_UID);
verify(mBpfNetMaps).removeUidInterfaceRules(aryEq(new int[] {MOCK_UID11}));
- mPermissionMonitor.onVpnUidRangesAdded("tun0", vpnRange2, VPN_UID);
- verify(mBpfNetMaps).addUidInterfaceRules(eq("tun0"), aryEq(new int[]{MOCK_UID12}));
+ mPermissionMonitor.onVpnUidRangesAdded(ifName, vpnRange2, VPN_UID);
+ verify(mBpfNetMaps).addUidInterfaceRules(eq(ifName), aryEq(new int[]{MOCK_UID12}));
reset(mBpfNetMaps);
// When VPN is disconnected, expect rules to be torn down
- mPermissionMonitor.onVpnUidRangesRemoved("tun0", vpnRange2, VPN_UID);
+ mPermissionMonitor.onVpnUidRangesRemoved(ifName, vpnRange2, VPN_UID);
verify(mBpfNetMaps).removeUidInterfaceRules(aryEq(new int[] {MOCK_UID12}));
- assertNull(mPermissionMonitor.getVpnUidRanges("tun0"));
+ assertNull(mPermissionMonitor.getVpnInterfaceUidRanges(ifName));
}
@Test
- public void testUidFilteringDuringPackageInstallAndUninstall() throws Exception {
+ public void testUidFilteringDuringVpnConnectDisconnectAndUidUpdates() throws Exception {
+ doTestuidFilteringDuringVpnConnectDisconnectAndUidUpdates("tun0");
+ }
+
+ @Test
+ public void testUidFilteringDuringVpnConnectDisconnectAndUidUpdatesWithWildcard()
+ throws Exception {
+ doTestuidFilteringDuringVpnConnectDisconnectAndUidUpdates(null /* ifName */);
+ }
+
+ private void doTestUidFilteringDuringPackageInstallAndUninstall(@Nullable String ifName) throws
+ Exception {
doReturn(List.of(buildPackageInfo(SYSTEM_PACKAGE1, SYSTEM_APP_UID11, CHANGE_NETWORK_STATE,
NETWORK_STACK, CONNECTIVITY_USE_RESTRICTED_NETWORKS),
buildPackageInfo(SYSTEM_PACKAGE2, VPN_UID)))
.when(mPackageManager).getInstalledPackagesAsUser(eq(GET_PERMISSIONS), anyInt());
buildAndMockPackageInfoWithPermissions(MOCK_PACKAGE1, MOCK_UID11);
+ doReturn(List.of(MOCK_USER1, MOCK_USER2)).when(mUserManager).getUserHandles(eq(true));
mPermissionMonitor.startMonitoring();
final Set<UidRange> vpnRange = Set.of(UidRange.createForUser(MOCK_USER1),
UidRange.createForUser(MOCK_USER2));
- mPermissionMonitor.onVpnUidRangesAdded("tun0", vpnRange, VPN_UID);
+ mPermissionMonitor.onVpnUidRangesAdded(ifName, vpnRange, VPN_UID);
// Newly-installed package should have uid rules added
addPackageForUsers(new UserHandle[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_APPID1);
- verify(mBpfNetMaps).addUidInterfaceRules(eq("tun0"), aryEq(new int[]{MOCK_UID11}));
- verify(mBpfNetMaps).addUidInterfaceRules(eq("tun0"), aryEq(new int[]{MOCK_UID21}));
+ verify(mBpfNetMaps).addUidInterfaceRules(eq(ifName), aryEq(new int[]{MOCK_UID11}));
+ verify(mBpfNetMaps).addUidInterfaceRules(eq(ifName), aryEq(new int[]{MOCK_UID21}));
// Removed package should have its uid rules removed
mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID11);
@@ -808,6 +845,168 @@
verify(mBpfNetMaps, never()).removeUidInterfaceRules(aryEq(new int[]{MOCK_UID21}));
}
+ @Test
+ public void testUidFilteringDuringPackageInstallAndUninstall() throws Exception {
+ doTestUidFilteringDuringPackageInstallAndUninstall("tun0");
+ }
+
+ @Test
+ public void testUidFilteringDuringPackageInstallAndUninstallWithWildcard() throws Exception {
+ doTestUidFilteringDuringPackageInstallAndUninstall(null /* ifName */);
+ }
+
+ @Test
+ public void testLockdownUidFilteringWithLockdownEnableDisable() {
+ doReturn(List.of(buildPackageInfo(SYSTEM_PACKAGE1, SYSTEM_APP_UID11, CHANGE_NETWORK_STATE,
+ CONNECTIVITY_USE_RESTRICTED_NETWORKS),
+ buildPackageInfo(MOCK_PACKAGE1, MOCK_UID11),
+ buildPackageInfo(MOCK_PACKAGE2, MOCK_UID12),
+ buildPackageInfo(SYSTEM_PACKAGE2, VPN_UID)))
+ .when(mPackageManager).getInstalledPackagesAsUser(eq(GET_PERMISSIONS), anyInt());
+ mPermissionMonitor.startMonitoring();
+ // Every app on user 0 except MOCK_UID12 are under VPN.
+ final UidRange[] vpnRange1 = {
+ new UidRange(0, MOCK_UID12 - 1),
+ new UidRange(MOCK_UID12 + 1, UserHandle.PER_USER_RANGE - 1)
+ };
+
+ // Add Lockdown uid range, expect a rule to be set up for user app MOCK_UID11
+ mPermissionMonitor.updateVpnLockdownUidRanges(true /* add */, vpnRange1);
+ verify(mBpfNetMaps)
+ .setUidRule(
+ eq(FIREWALL_CHAIN_LOCKDOWN_VPN), eq(MOCK_UID11),
+ eq(FIREWALL_RULE_DENY));
+ assertEquals(mPermissionMonitor.getVpnLockdownUidRanges(), Set.of(vpnRange1));
+
+ reset(mBpfNetMaps);
+
+ // Remove Lockdown uid range, expect rules to be torn down
+ mPermissionMonitor.updateVpnLockdownUidRanges(false /* false */, vpnRange1);
+ verify(mBpfNetMaps)
+ .setUidRule(eq(FIREWALL_CHAIN_LOCKDOWN_VPN), eq(MOCK_UID11),
+ eq(FIREWALL_RULE_ALLOW));
+ assertTrue(mPermissionMonitor.getVpnLockdownUidRanges().isEmpty());
+ }
+
+ @Test
+ public void testLockdownUidFilteringWithLockdownEnableDisableWithMultiAdd() {
+ doReturn(List.of(buildPackageInfo(SYSTEM_PACKAGE1, SYSTEM_APP_UID11, CHANGE_NETWORK_STATE,
+ CONNECTIVITY_USE_RESTRICTED_NETWORKS),
+ buildPackageInfo(MOCK_PACKAGE1, MOCK_UID11),
+ buildPackageInfo(SYSTEM_PACKAGE2, VPN_UID)))
+ .when(mPackageManager).getInstalledPackagesAsUser(eq(GET_PERMISSIONS), anyInt());
+ mPermissionMonitor.startMonitoring();
+ // MOCK_UID11 is under VPN.
+ final UidRange range = new UidRange(MOCK_UID11, MOCK_UID11);
+ final UidRange[] vpnRange = {range};
+
+ // Add Lockdown uid range at 1st time, expect a rule to be set up
+ mPermissionMonitor.updateVpnLockdownUidRanges(true /* add */, vpnRange);
+ verify(mBpfNetMaps)
+ .setUidRule(eq(FIREWALL_CHAIN_LOCKDOWN_VPN), eq(MOCK_UID11),
+ eq(FIREWALL_RULE_DENY));
+ assertEquals(mPermissionMonitor.getVpnLockdownUidRanges(), Set.of(vpnRange));
+
+ reset(mBpfNetMaps);
+
+ // Add Lockdown uid range at 2nd time, expect a rule not to be set up because the uid
+ // already has the rule
+ mPermissionMonitor.updateVpnLockdownUidRanges(true /* add */, vpnRange);
+ verify(mBpfNetMaps, never()).setUidRule(anyInt(), anyInt(), anyInt());
+ assertEquals(mPermissionMonitor.getVpnLockdownUidRanges(), Set.of(vpnRange));
+
+ reset(mBpfNetMaps);
+
+ // Remove Lockdown uid range at 1st time, expect a rule not to be torn down because we added
+ // the range 2 times.
+ mPermissionMonitor.updateVpnLockdownUidRanges(false /* false */, vpnRange);
+ verify(mBpfNetMaps, never()).setUidRule(anyInt(), anyInt(), anyInt());
+ assertEquals(mPermissionMonitor.getVpnLockdownUidRanges(), Set.of(vpnRange));
+
+ reset(mBpfNetMaps);
+
+ // Remove Lockdown uid range at 2nd time, expect a rule to be torn down because we added
+ // twice and we removed twice.
+ mPermissionMonitor.updateVpnLockdownUidRanges(false /* false */, vpnRange);
+ verify(mBpfNetMaps)
+ .setUidRule(eq(FIREWALL_CHAIN_LOCKDOWN_VPN), eq(MOCK_UID11),
+ eq(FIREWALL_RULE_ALLOW));
+ assertTrue(mPermissionMonitor.getVpnLockdownUidRanges().isEmpty());
+ }
+
+ @Test
+ public void testLockdownUidFilteringWithLockdownEnableDisableWithDuplicates() {
+ doReturn(List.of(buildPackageInfo(SYSTEM_PACKAGE1, SYSTEM_APP_UID11, CHANGE_NETWORK_STATE,
+ CONNECTIVITY_USE_RESTRICTED_NETWORKS),
+ buildPackageInfo(MOCK_PACKAGE1, MOCK_UID11),
+ buildPackageInfo(SYSTEM_PACKAGE2, VPN_UID)))
+ .when(mPackageManager).getInstalledPackagesAsUser(eq(GET_PERMISSIONS), anyInt());
+ mPermissionMonitor.startMonitoring();
+ // MOCK_UID11 is under VPN.
+ final UidRange range = new UidRange(MOCK_UID11, MOCK_UID11);
+ final UidRange[] vpnRangeDuplicates = {range, range};
+ final UidRange[] vpnRange = {range};
+
+ // Add Lockdown uid ranges which contains duplicated uid ranges
+ mPermissionMonitor.updateVpnLockdownUidRanges(true /* add */, vpnRangeDuplicates);
+ verify(mBpfNetMaps)
+ .setUidRule(eq(FIREWALL_CHAIN_LOCKDOWN_VPN), eq(MOCK_UID11),
+ eq(FIREWALL_RULE_DENY));
+ assertEquals(mPermissionMonitor.getVpnLockdownUidRanges(), Set.of(vpnRange));
+
+ reset(mBpfNetMaps);
+
+ // Remove Lockdown uid range at 1st time, expect a rule not to be torn down because uid
+ // ranges we added contains duplicated uid ranges.
+ mPermissionMonitor.updateVpnLockdownUidRanges(false /* false */, vpnRange);
+ verify(mBpfNetMaps, never()).setUidRule(anyInt(), anyInt(), anyInt());
+ assertEquals(mPermissionMonitor.getVpnLockdownUidRanges(), Set.of(vpnRange));
+
+ reset(mBpfNetMaps);
+
+ // Remove Lockdown uid range at 2nd time, expect a rule to be torn down.
+ mPermissionMonitor.updateVpnLockdownUidRanges(false /* false */, vpnRange);
+ verify(mBpfNetMaps)
+ .setUidRule(eq(FIREWALL_CHAIN_LOCKDOWN_VPN), eq(MOCK_UID11),
+ eq(FIREWALL_RULE_ALLOW));
+ assertTrue(mPermissionMonitor.getVpnLockdownUidRanges().isEmpty());
+ }
+
+ @Test
+ public void testLockdownUidFilteringWithInstallAndUnInstall() {
+ doReturn(List.of(buildPackageInfo(SYSTEM_PACKAGE1, SYSTEM_APP_UID11, CHANGE_NETWORK_STATE,
+ NETWORK_STACK, CONNECTIVITY_USE_RESTRICTED_NETWORKS),
+ buildPackageInfo(SYSTEM_PACKAGE2, VPN_UID)))
+ .when(mPackageManager).getInstalledPackagesAsUser(eq(GET_PERMISSIONS), anyInt());
+ doReturn(List.of(MOCK_USER1, MOCK_USER2)).when(mUserManager).getUserHandles(eq(true));
+
+ mPermissionMonitor.startMonitoring();
+ final UidRange[] vpnRange = {
+ UidRange.createForUser(MOCK_USER1),
+ UidRange.createForUser(MOCK_USER2)
+ };
+ mPermissionMonitor.updateVpnLockdownUidRanges(true /* add */, vpnRange);
+
+ // Installing package should add Lockdown rules
+ addPackageForUsers(new UserHandle[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_APPID1);
+ verify(mBpfNetMaps)
+ .setUidRule(eq(FIREWALL_CHAIN_LOCKDOWN_VPN), eq(MOCK_UID11),
+ eq(FIREWALL_RULE_DENY));
+ verify(mBpfNetMaps)
+ .setUidRule(eq(FIREWALL_CHAIN_LOCKDOWN_VPN), eq(MOCK_UID21),
+ eq(FIREWALL_RULE_DENY));
+
+ reset(mBpfNetMaps);
+
+ // Uninstalling package should remove Lockdown rules
+ mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID11);
+ verify(mBpfNetMaps)
+ .setUidRule(eq(FIREWALL_CHAIN_LOCKDOWN_VPN), eq(MOCK_UID11),
+ eq(FIREWALL_RULE_ALLOW));
+ verify(mBpfNetMaps, never())
+ .setUidRule(eq(FIREWALL_CHAIN_LOCKDOWN_VPN), eq(MOCK_UID21),
+ eq(FIREWALL_RULE_ALLOW));
+ }
// Normal package add/remove operations will trigger multiple intent for uids corresponding to
// each user. To simulate generic package operations, the onPackageAdded/Removed will need to be
@@ -881,7 +1080,7 @@
addPackage(MOCK_PACKAGE1, MOCK_UID11, INTERNET, UPDATE_DEVICE_STATS);
mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
- // Install another package with the same uid and no permissions should not cause the app id
+ // Install another package with the same uid and no permissions should not cause the appId
// to lose permissions.
addPackage(MOCK_PACKAGE2, MOCK_UID11);
mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
@@ -1249,4 +1448,211 @@
assertTrue(isHigherNetworkPermission(PERMISSION_SYSTEM, PERMISSION_NETWORK));
assertFalse(isHigherNetworkPermission(PERMISSION_SYSTEM, PERMISSION_SYSTEM));
}
+
+ private void prepareMultiUserPackages() {
+ // MOCK_USER1 has installed 3 packages
+ // mockApp1 has no permission and share MOCK_APPID1.
+ // mockApp2 has INTERNET permission and share MOCK_APPID2.
+ // mockApp3 has UPDATE_DEVICE_STATS permission and share MOCK_APPID3.
+ final List<PackageInfo> pkgs1 = List.of(
+ buildPackageInfo("mockApp1", MOCK_UID11),
+ buildPackageInfo("mockApp2", MOCK_UID12, INTERNET),
+ buildPackageInfo("mockApp3", MOCK_UID13, UPDATE_DEVICE_STATS));
+
+ // MOCK_USER2 has installed 2 packages
+ // mockApp4 has UPDATE_DEVICE_STATS permission and share MOCK_APPID1.
+ // mockApp5 has INTERNET permission and share MOCK_APPID2.
+ final List<PackageInfo> pkgs2 = List.of(
+ buildPackageInfo("mockApp4", MOCK_UID21, UPDATE_DEVICE_STATS),
+ buildPackageInfo("mockApp5", MOCK_UID23, INTERNET));
+
+ // MOCK_USER3 has installed 1 packages
+ // mockApp6 has UPDATE_DEVICE_STATS permission and share MOCK_APPID2.
+ final List<PackageInfo> pkgs3 = List.of(
+ buildPackageInfo("mockApp6", MOCK_UID32, UPDATE_DEVICE_STATS));
+
+ doReturn(pkgs1).when(mPackageManager).getInstalledPackagesAsUser(eq(GET_PERMISSIONS),
+ eq(MOCK_USER_ID1));
+ doReturn(pkgs2).when(mPackageManager).getInstalledPackagesAsUser(eq(GET_PERMISSIONS),
+ eq(MOCK_USER_ID2));
+ doReturn(pkgs3).when(mPackageManager).getInstalledPackagesAsUser(eq(GET_PERMISSIONS),
+ eq(MOCK_USER_ID3));
+ }
+
+ private void addUserAndVerifyAppIdsPermissions(UserHandle user, int appId1Perm,
+ int appId2Perm, int appId3Perm) {
+ mPermissionMonitor.onUserAdded(user);
+ mBpfMapMonitor.expectTrafficPerm(appId1Perm, MOCK_APPID1);
+ mBpfMapMonitor.expectTrafficPerm(appId2Perm, MOCK_APPID2);
+ mBpfMapMonitor.expectTrafficPerm(appId3Perm, MOCK_APPID3);
+ }
+
+ private void removeUserAndVerifyAppIdsPermissions(UserHandle user, int appId1Perm,
+ int appId2Perm, int appId3Perm) {
+ mPermissionMonitor.onUserRemoved(user);
+ mBpfMapMonitor.expectTrafficPerm(appId1Perm, MOCK_APPID1);
+ mBpfMapMonitor.expectTrafficPerm(appId2Perm, MOCK_APPID2);
+ mBpfMapMonitor.expectTrafficPerm(appId3Perm, MOCK_APPID3);
+ }
+
+ @Test
+ public void testAppIdsTrafficPermission_UserAddedRemoved() {
+ prepareMultiUserPackages();
+
+ // Add MOCK_USER1 and verify the permissions with each appIds.
+ addUserAndVerifyAppIdsPermissions(MOCK_USER1, PERMISSION_NONE, PERMISSION_INTERNET,
+ PERMISSION_UPDATE_DEVICE_STATS);
+
+ // Add MOCK_USER2 and verify the permissions upgrade on MOCK_APPID1 & MOCK_APPID3.
+ addUserAndVerifyAppIdsPermissions(MOCK_USER2, PERMISSION_UPDATE_DEVICE_STATS,
+ PERMISSION_INTERNET, PERMISSION_TRAFFIC_ALL);
+
+ // Add MOCK_USER3 and verify the permissions upgrade on MOCK_APPID2.
+ addUserAndVerifyAppIdsPermissions(MOCK_USER3, PERMISSION_UPDATE_DEVICE_STATS,
+ PERMISSION_TRAFFIC_ALL, PERMISSION_TRAFFIC_ALL);
+
+ // Remove MOCK_USER2 and verify the permissions downgrade on MOCK_APPID1 & MOCK_APPID3.
+ removeUserAndVerifyAppIdsPermissions(MOCK_USER2, PERMISSION_NONE, PERMISSION_TRAFFIC_ALL,
+ PERMISSION_UPDATE_DEVICE_STATS);
+
+ // Remove MOCK_USER1 and verify the permissions downgrade on all appIds.
+ removeUserAndVerifyAppIdsPermissions(MOCK_USER1, PERMISSION_UNINSTALLED,
+ PERMISSION_UPDATE_DEVICE_STATS, PERMISSION_UNINSTALLED);
+
+ // Add MOCK_USER2 back and verify the permissions upgrade on MOCK_APPID1 & MOCK_APPID3.
+ addUserAndVerifyAppIdsPermissions(MOCK_USER2, PERMISSION_UPDATE_DEVICE_STATS,
+ PERMISSION_UPDATE_DEVICE_STATS, PERMISSION_INTERNET);
+
+ // Remove MOCK_USER3 and verify the permissions downgrade on MOCK_APPID2.
+ removeUserAndVerifyAppIdsPermissions(MOCK_USER3, PERMISSION_UPDATE_DEVICE_STATS,
+ PERMISSION_UNINSTALLED, PERMISSION_INTERNET);
+ }
+
+ @Test
+ public void testAppIdsTrafficPermission_Multiuser_PackageAdded() throws Exception {
+ // Add two users with empty package list.
+ mPermissionMonitor.onUserAdded(MOCK_USER1);
+ mPermissionMonitor.onUserAdded(MOCK_USER2);
+
+ final int[] netdPermissions = {PERMISSION_NONE, PERMISSION_INTERNET,
+ PERMISSION_UPDATE_DEVICE_STATS, PERMISSION_TRAFFIC_ALL};
+ final String[][] grantPermissions = {new String[]{}, new String[]{INTERNET},
+ new String[]{UPDATE_DEVICE_STATS}, new String[]{INTERNET, UPDATE_DEVICE_STATS}};
+
+ // Verify that the permission combination is expected when same appId package is installed
+ // on another user. List the expected permissions below.
+ // NONE + NONE = NONE
+ // NONE + INTERNET = INTERNET
+ // NONE + UPDATE_DEVICE_STATS = UPDATE_DEVICE_STATS
+ // NONE + ALL = ALL
+ // INTERNET + NONE = INTERNET
+ // INTERNET + INTERNET = INTERNET
+ // INTERNET + UPDATE_DEVICE_STATS = ALL
+ // INTERNET + ALL = ALL
+ // UPDATE_DEVICE_STATS + NONE = UPDATE_DEVICE_STATS
+ // UPDATE_DEVICE_STATS + INTERNET = ALL
+ // UPDATE_DEVICE_STATS + UPDATE_DEVICE_STATS = UPDATE_DEVICE_STATS
+ // UPDATE_DEVICE_STATS + ALL = ALL
+ // ALL + NONE = ALL
+ // ALL + INTERNET = ALL
+ // ALL + UPDATE_DEVICE_STATS = ALL
+ // ALL + ALL = ALL
+ for (int i = 0, num = 0; i < netdPermissions.length; i++) {
+ final int current = netdPermissions[i];
+ final String[] user1Perm = grantPermissions[i];
+ for (int j = 0; j < netdPermissions.length; j++) {
+ final int appId = MOCK_APPID1 + num;
+ final int added = netdPermissions[j];
+ final String[] user2Perm = grantPermissions[j];
+ // Add package on MOCK_USER1 and verify the permission is same as package granted.
+ addPackage(MOCK_PACKAGE1, MOCK_USER1.getUid(appId), user1Perm);
+ mBpfMapMonitor.expectTrafficPerm(current, appId);
+
+ // Add package which share the same appId on MOCK_USER2, and verify the permission
+ // has combined.
+ addPackage(MOCK_PACKAGE2, MOCK_USER2.getUid(appId), user2Perm);
+ mBpfMapMonitor.expectTrafficPerm((current | added), appId);
+ num++;
+ }
+ }
+ }
+
+ private void verifyAppIdPermissionsAfterPackageRemoved(int appId, int expectedPerm,
+ String[] user1Perm, String[] user2Perm) throws Exception {
+ // Add package on MOCK_USER1 and verify the permission is same as package granted.
+ addPackage(MOCK_PACKAGE1, MOCK_USER1.getUid(appId), user1Perm);
+ mBpfMapMonitor.expectTrafficPerm(expectedPerm, appId);
+
+ // Add two packages which share the same appId and don't declare permission on
+ // MOCK_USER2. Verify the permission has no change.
+ addPackage(MOCK_PACKAGE2, MOCK_USER2.getUid(appId));
+ addPackage(MOCK_PACKAGE3, MOCK_USER2.getUid(appId), user2Perm);
+ mBpfMapMonitor.expectTrafficPerm(expectedPerm, appId);
+
+ // Remove one packages from MOCK_USER2. Verify the permission has no change too.
+ removePackage(MOCK_PACKAGE2, MOCK_USER2.getUid(appId));
+ mBpfMapMonitor.expectTrafficPerm(expectedPerm, appId);
+
+ // Remove last packages from MOCK_USER2. Verify the permission has still no change.
+ removePackage(MOCK_PACKAGE3, MOCK_USER2.getUid(appId));
+ mBpfMapMonitor.expectTrafficPerm(expectedPerm, appId);
+ }
+
+ @Test
+ public void testAppIdsTrafficPermission_Multiuser_PackageRemoved() throws Exception {
+ // Add two users with empty package list.
+ mPermissionMonitor.onUserAdded(MOCK_USER1);
+ mPermissionMonitor.onUserAdded(MOCK_USER2);
+
+ int appId = MOCK_APPID1;
+ // Verify that the permission combination is expected when same appId package is removed on
+ // another user. List the expected permissions below.
+ /***** NONE *****/
+ // NONE + NONE = NONE
+ verifyAppIdPermissionsAfterPackageRemoved(
+ appId++, PERMISSION_NONE, new String[]{}, new String[]{});
+
+ /***** INTERNET *****/
+ // INTERNET + NONE = INTERNET
+ verifyAppIdPermissionsAfterPackageRemoved(
+ appId++, PERMISSION_INTERNET, new String[]{INTERNET}, new String[]{});
+
+ // INTERNET + INTERNET = INTERNET
+ verifyAppIdPermissionsAfterPackageRemoved(
+ appId++, PERMISSION_INTERNET, new String[]{INTERNET}, new String[]{INTERNET});
+
+ /***** UPDATE_DEVICE_STATS *****/
+ // UPDATE_DEVICE_STATS + NONE = UPDATE_DEVICE_STATS
+ verifyAppIdPermissionsAfterPackageRemoved(appId++, PERMISSION_UPDATE_DEVICE_STATS,
+ new String[]{UPDATE_DEVICE_STATS}, new String[]{});
+
+ // UPDATE_DEVICE_STATS + UPDATE_DEVICE_STATS = UPDATE_DEVICE_STATS
+ verifyAppIdPermissionsAfterPackageRemoved(appId++, PERMISSION_UPDATE_DEVICE_STATS,
+ new String[]{UPDATE_DEVICE_STATS}, new String[]{UPDATE_DEVICE_STATS});
+
+ /***** ALL *****/
+ // ALL + NONE = ALL
+ verifyAppIdPermissionsAfterPackageRemoved(appId++, PERMISSION_TRAFFIC_ALL,
+ new String[]{INTERNET, UPDATE_DEVICE_STATS}, new String[]{});
+
+ // ALL + INTERNET = ALL
+ verifyAppIdPermissionsAfterPackageRemoved(appId++, PERMISSION_TRAFFIC_ALL,
+ new String[]{INTERNET, UPDATE_DEVICE_STATS}, new String[]{INTERNET});
+
+ // ALL + UPDATE_DEVICE_STATS = ALL
+ verifyAppIdPermissionsAfterPackageRemoved(appId++, PERMISSION_TRAFFIC_ALL,
+ new String[]{INTERNET, UPDATE_DEVICE_STATS}, new String[]{UPDATE_DEVICE_STATS});
+
+ // ALL + ALL = ALL
+ verifyAppIdPermissionsAfterPackageRemoved(appId++, PERMISSION_TRAFFIC_ALL,
+ new String[]{INTERNET, UPDATE_DEVICE_STATS},
+ new String[]{INTERNET, UPDATE_DEVICE_STATS});
+
+ /***** UNINSTALL *****/
+ // UNINSTALL + UNINSTALL = UNINSTALL
+ verifyAppIdPermissionsAfterPackageRemoved(
+ appId, PERMISSION_NONE, new String[]{}, new String[]{});
+ removePackage(MOCK_PACKAGE1, MOCK_USER1.getUid(appId));
+ mBpfMapMonitor.expectTrafficPerm(PERMISSION_UNINSTALLED, appId);
+ }
}
diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index 33c0868..eb35469 100644
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java
@@ -27,9 +27,12 @@
import static android.net.ConnectivityManager.NetworkCallback;
import static android.net.INetd.IF_STATE_DOWN;
import static android.net.INetd.IF_STATE_UP;
+import static android.net.VpnManager.TYPE_VPN_PLATFORM;
+import static android.os.Build.VERSION_CODES.S_V2;
import static android.os.UserHandle.PER_USER_RANGE;
import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
+import static com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import static com.android.testutils.MiscAsserts.assertThrows;
import static org.junit.Assert.assertArrayEquals;
@@ -42,6 +45,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
@@ -54,6 +58,7 @@
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -65,6 +70,7 @@
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
@@ -83,20 +89,30 @@
import android.net.LinkProperties;
import android.net.LocalSocket;
import android.net.Network;
+import android.net.NetworkAgent;
+import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo.DetailedState;
+import android.net.NetworkProvider;
import android.net.RouteInfo;
import android.net.UidRangeParcel;
import android.net.VpnManager;
+import android.net.VpnProfileState;
import android.net.VpnService;
import android.net.VpnTransportInfo;
import android.net.ipsec.ike.IkeSessionCallback;
+import android.net.ipsec.ike.exceptions.IkeException;
+import android.net.ipsec.ike.exceptions.IkeNetworkLostException;
+import android.net.ipsec.ike.exceptions.IkeNonProtocolException;
import android.net.ipsec.ike.exceptions.IkeProtocolException;
+import android.net.ipsec.ike.exceptions.IkeTimeoutException;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.ConditionVariable;
import android.os.INetworkManagementService;
+import android.os.Looper;
import android.os.ParcelFileDescriptor;
+import android.os.PowerWhitelistManager;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
@@ -113,12 +129,17 @@
import com.android.internal.net.LegacyVpnInfo;
import com.android.internal.net.VpnConfig;
import com.android.internal.net.VpnProfile;
+import com.android.internal.util.HexDump;
import com.android.modules.utils.build.SdkLevel;
+import com.android.server.DeviceIdleInternal;
import com.android.server.IpSecService;
+import com.android.server.vcn.util.PersistableBundleUtils;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
+import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.AdditionalAnswers;
@@ -135,6 +156,7 @@
import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress;
+import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -154,10 +176,13 @@
*/
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
-@DevSdkIgnoreRule.IgnoreUpTo(VERSION_CODES.R)
+@IgnoreUpTo(VERSION_CODES.S_V2)
public class VpnTest {
private static final String TAG = "VpnTest";
+ @Rule
+ public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
+
// Mock users
static final UserInfo primaryUser = new UserInfo(27, "Primary", FLAG_ADMIN | FLAG_PRIMARY);
static final UserInfo secondaryUser = new UserInfo(15, "Secondary", FLAG_ADMIN);
@@ -181,14 +206,16 @@
private static final String TEST_IFACE_NAME = "TEST_IFACE";
private static final int TEST_TUNNEL_RESOURCE_ID = 0x2345;
private static final long TEST_TIMEOUT_MS = 500L;
-
+ private static final String PRIMARY_USER_APP_EXCLUDE_KEY =
+ "VPN_APP_EXCLUDED_27_com.testvpn.vpn";
/**
* Names and UIDs for some fake packages. Important points:
* - UID is ordered increasing.
* - One pair of packages have consecutive UIDs.
*/
static final String[] PKGS = {"com.example", "org.example", "net.example", "web.vpn"};
- static final int[] PKG_UIDS = {66, 77, 78, 400};
+ static final String PKGS_BYTES = getPackageByteString(List.of(PKGS));
+ static final int[] PKG_UIDS = {10066, 10077, 10078, 10400};
// Mock packages
static final Map<String, Integer> mPackages = new ArrayMap<>();
@@ -211,6 +238,7 @@
@Mock private ConnectivityManager mConnectivityManager;
@Mock private IpSecService mIpSecService;
@Mock private VpnProfileStore mVpnProfileStore;
+ @Mock DeviceIdleInternal mDeviceIdleInternal;
private final VpnProfile mVpnProfile;
private IpSecManager mIpSecManager;
@@ -271,6 +299,11 @@
doReturn(PERMISSION_DENIED).when(mContext).checkCallingOrSelfPermission(any());
}
+ @After
+ public void tearDown() throws Exception {
+ doReturn(PERMISSION_DENIED).when(mContext).checkCallingOrSelfPermission(CONTROL_VPN);
+ }
+
private <T> void mockService(Class<T> clazz, String name, T service) {
doReturn(service).when(mContext).getSystemService(name);
doReturn(name).when(mContext).getSystemServiceName(clazz);
@@ -296,6 +329,17 @@
return new Range<Integer>(start, stop);
}
+ private static String getPackageByteString(List<String> packages) {
+ try {
+ return HexDump.toHexString(
+ PersistableBundleUtils.toDiskStableBytes(PersistableBundleUtils.fromList(
+ packages, PersistableBundleUtils.STRING_SERIALIZER)),
+ true /* upperCase */);
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
@Test
public void testRestrictedProfilesAreAddedToVpn() {
setMockedUsers(primaryUser, secondaryUser, restrictedProfileA, restrictedProfileB);
@@ -345,7 +389,11 @@
Arrays.asList(packages), null /* disallowedApplications */);
assertEquals(rangeSet(
uidRange(userStart + PKG_UIDS[0], userStart + PKG_UIDS[0]),
- uidRange(userStart + PKG_UIDS[1], userStart + PKG_UIDS[2])),
+ uidRange(userStart + PKG_UIDS[1], userStart + PKG_UIDS[2]),
+ uidRange(Process.toSdkSandboxUid(userStart + PKG_UIDS[0]),
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[0])),
+ uidRange(Process.toSdkSandboxUid(userStart + PKG_UIDS[1]),
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[2]))),
allow);
// Denied list
@@ -356,10 +404,20 @@
uidRange(userStart, userStart + PKG_UIDS[0] - 1),
uidRange(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[1] - 1),
/* Empty range between UIDS[1] and UIDS[2], should be excluded, */
- uidRange(userStart + PKG_UIDS[2] + 1, userStop)),
+ uidRange(userStart + PKG_UIDS[2] + 1,
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
+ uidRange(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1),
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
+ uidRange(Process.toSdkSandboxUid(userStart + PKG_UIDS[2] + 1), userStop)),
disallow);
}
+ private void verifyPowerSaveTempWhitelistApp(String packageName) {
+ verify(mDeviceIdleInternal).addPowerSaveTempWhitelistApp(anyInt(), eq(packageName),
+ anyLong(), anyInt(), eq(false), eq(PowerWhitelistManager.REASON_VPN),
+ eq("VpnManager event"));
+ }
+
@Test
public void testGetAlwaysAndOnGetLockDown() throws Exception {
final Vpn vpn = createVpn(primaryUser.id);
@@ -397,18 +455,24 @@
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, null));
verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
new UidRangeParcel(userStart, userStart + PKG_UIDS[1] - 1),
- new UidRangeParcel(userStart + PKG_UIDS[1] + 1, userStop)
+ new UidRangeParcel(userStart + PKG_UIDS[1] + 1,
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
+ new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1), userStop)
}));
// Switch to another app.
assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null));
verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
new UidRangeParcel(userStart, userStart + PKG_UIDS[1] - 1),
- new UidRangeParcel(userStart + PKG_UIDS[1] + 1, userStop)
+ new UidRangeParcel(userStart + PKG_UIDS[1] + 1,
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
+ new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1), userStop)
}));
verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
new UidRangeParcel(userStart, userStart + PKG_UIDS[3] - 1),
- new UidRangeParcel(userStart + PKG_UIDS[3] + 1, userStop)
+ new UidRangeParcel(userStart + PKG_UIDS[3] + 1,
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[3] - 1)),
+ new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[3] + 1), userStop)
}));
}
@@ -423,17 +487,25 @@
PKGS[1], true, Collections.singletonList(PKGS[2])));
verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
new UidRangeParcel(userStart, userStart + PKG_UIDS[1] - 1),
- new UidRangeParcel(userStart + PKG_UIDS[2] + 1, userStop)
+ new UidRangeParcel(userStart + PKG_UIDS[2] + 1,
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[1]) - 1),
+ new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[2] + 1), userStop)
}));
// Change allowed app list to PKGS[3].
assertTrue(vpn.setAlwaysOnPackage(
PKGS[1], true, Collections.singletonList(PKGS[3])));
verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
- new UidRangeParcel(userStart + PKG_UIDS[2] + 1, userStop)
+ new UidRangeParcel(userStart + PKG_UIDS[2] + 1,
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
+ new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[2] + 1), userStop)
}));
verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
new UidRangeParcel(userStart + PKG_UIDS[1] + 1, userStart + PKG_UIDS[3] - 1),
- new UidRangeParcel(userStart + PKG_UIDS[3] + 1, userStop)
+ new UidRangeParcel(userStart + PKG_UIDS[3] + 1,
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
+ new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1),
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[3] - 1)),
+ new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[3] + 1), userStop)
}));
// Change the VPN app.
@@ -441,32 +513,52 @@
PKGS[0], true, Collections.singletonList(PKGS[3])));
verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
new UidRangeParcel(userStart, userStart + PKG_UIDS[1] - 1),
- new UidRangeParcel(userStart + PKG_UIDS[1] + 1, userStart + PKG_UIDS[3] - 1)
+ new UidRangeParcel(userStart + PKG_UIDS[1] + 1, userStart + PKG_UIDS[3] - 1),
+ new UidRangeParcel(userStart + PKG_UIDS[3] + 1,
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
+ new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1),
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[3] - 1))
}));
verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
new UidRangeParcel(userStart, userStart + PKG_UIDS[0] - 1),
- new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[3] - 1)
+ new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[3] - 1),
+ new UidRangeParcel(userStart + PKG_UIDS[3] + 1,
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
+ new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1),
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[3] - 1))
}));
// Remove the list of allowed packages.
assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null));
verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[3] - 1),
- new UidRangeParcel(userStart + PKG_UIDS[3] + 1, userStop)
+ new UidRangeParcel(userStart + PKG_UIDS[3] + 1,
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
+ new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1),
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[3] - 1)),
+ new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[3] + 1), userStop)
}));
verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
- new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStop),
+ new UidRangeParcel(userStart + PKG_UIDS[0] + 1,
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
+ new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1), userStop),
}));
// Add the list of allowed packages.
assertTrue(vpn.setAlwaysOnPackage(
PKGS[0], true, Collections.singletonList(PKGS[1])));
verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
- new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStop)
+ new UidRangeParcel(userStart + PKG_UIDS[0] + 1,
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
+ new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1), userStop),
}));
verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[1] - 1),
- new UidRangeParcel(userStart + PKG_UIDS[1] + 1, userStop)
+ new UidRangeParcel(userStart + PKG_UIDS[1] + 1,
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
+ new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1),
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
+ new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1), userStop)
}));
// Try allowing a package with a comma, should be rejected.
@@ -479,11 +571,19 @@
PKGS[0], true, Arrays.asList("com.foo.app", PKGS[2], "com.bar.app")));
verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[1] - 1),
- new UidRangeParcel(userStart + PKG_UIDS[1] + 1, userStop)
+ new UidRangeParcel(userStart + PKG_UIDS[1] + 1,
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
+ new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1),
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
+ new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1), userStop)
}));
verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[2] - 1),
- new UidRangeParcel(userStart + PKG_UIDS[2] + 1, userStop)
+ new UidRangeParcel(userStart + PKG_UIDS[2] + 1,
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
+ new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1),
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[2] - 1)),
+ new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[2] + 1), userStop)
}));
}
@@ -528,7 +628,10 @@
};
final UidRangeParcel[] exceptPkg0 = {
new UidRangeParcel(entireUser[0].start, entireUser[0].start + PKG_UIDS[0] - 1),
- new UidRangeParcel(entireUser[0].start + PKG_UIDS[0] + 1, entireUser[0].stop)
+ new UidRangeParcel(entireUser[0].start + PKG_UIDS[0] + 1,
+ Process.toSdkSandboxUid(entireUser[0].start + PKG_UIDS[0] - 1)),
+ new UidRangeParcel(Process.toSdkSandboxUid(entireUser[0].start + PKG_UIDS[0] + 1),
+ entireUser[0].stop),
};
final InOrder order = inOrder(mConnectivityManager);
@@ -696,6 +799,47 @@
}
}
+ private Vpn prepareVpnForVerifyAppExclusionList() throws Exception {
+ final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+ when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
+ .thenReturn(mVpnProfile.encode());
+ when(mVpnProfileStore.get(PRIMARY_USER_APP_EXCLUDE_KEY))
+ .thenReturn(HexDump.hexStringToByteArray(PKGS_BYTES));
+
+ vpn.startVpnProfile(TEST_VPN_PKG);
+ verify(mVpnProfileStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
+ vpn.mNetworkAgent = new NetworkAgent(mContext, Looper.getMainLooper(), TAG,
+ new NetworkCapabilities.Builder().build(), new LinkProperties(), 10 /* score */,
+ new NetworkAgentConfig.Builder().build(),
+ new NetworkProvider(mContext, Looper.getMainLooper(), TAG)) {};
+ return vpn;
+ }
+
+ @Test @IgnoreUpTo(S_V2)
+ public void testSetAndGetAppExclusionList() throws Exception {
+ final Vpn vpn = prepareVpnForVerifyAppExclusionList();
+ verify(mVpnProfileStore, never()).put(eq(PRIMARY_USER_APP_EXCLUDE_KEY), any());
+ vpn.setAppExclusionList(TEST_VPN_PKG, Arrays.asList(PKGS));
+ verify(mVpnProfileStore)
+ .put(eq(PRIMARY_USER_APP_EXCLUDE_KEY),
+ eq(HexDump.hexStringToByteArray(PKGS_BYTES)));
+ assertEquals(vpn.createUserAndRestrictedProfilesRanges(
+ primaryUser.id, null, Arrays.asList(PKGS)),
+ vpn.mNetworkCapabilities.getUids());
+ assertEquals(Arrays.asList(PKGS), vpn.getAppExclusionList(TEST_VPN_PKG));
+ }
+
+ @Test @IgnoreUpTo(S_V2)
+ public void testSetAndGetAppExclusionListRestrictedUser() throws Exception {
+ final Vpn vpn = prepareVpnForVerifyAppExclusionList();
+ // Mock it to restricted profile
+ when(mUserManager.getUserInfo(anyInt())).thenReturn(restrictedProfileA);
+ // Restricted users cannot configure VPNs
+ assertThrows(SecurityException.class,
+ () -> vpn.setAppExclusionList(TEST_VPN_PKG, new ArrayList<>()));
+ assertThrows(SecurityException.class, () -> vpn.getAppExclusionList(TEST_VPN_PKG));
+ }
+
@Test
public void testProvisionVpnProfilePreconsented() throws Exception {
final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
@@ -783,6 +927,30 @@
verify(mVpnProfileStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
}
+ private void verifyPlatformVpnIsActivated(String packageName) {
+ verify(mAppOps).noteOpNoThrow(
+ eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN),
+ eq(Process.myUid()),
+ eq(packageName),
+ eq(null) /* attributionTag */,
+ eq(null) /* message */);
+ verify(mAppOps).startOp(
+ eq(AppOpsManager.OPSTR_ESTABLISH_VPN_MANAGER),
+ eq(Process.myUid()),
+ eq(packageName),
+ eq(null) /* attributionTag */,
+ eq(null) /* message */);
+ }
+
+ private void verifyPlatformVpnIsDeactivated(String packageName) {
+ // Add a small delay to double confirm that finishOp is only called once.
+ verify(mAppOps, after(100)).finishOp(
+ eq(AppOpsManager.OPSTR_ESTABLISH_VPN_MANAGER),
+ eq(Process.myUid()),
+ eq(packageName),
+ eq(null) /* attributionTag */);
+ }
+
@Test
public void testStartVpnProfile() throws Exception {
final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
@@ -793,13 +961,7 @@
vpn.startVpnProfile(TEST_VPN_PKG);
verify(mVpnProfileStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
- verify(mAppOps)
- .noteOpNoThrow(
- eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN),
- eq(Process.myUid()),
- eq(TEST_VPN_PKG),
- eq(null) /* attributionTag */,
- eq(null) /* message */);
+ verifyPlatformVpnIsActivated(TEST_VPN_PKG);
}
@Test
@@ -811,7 +973,7 @@
vpn.startVpnProfile(TEST_VPN_PKG);
- // Verify that the the ACTIVATE_VPN appop was checked, but no error was thrown.
+ // Verify that the ACTIVATE_VPN appop was checked, but no error was thrown.
verify(mAppOps).noteOpNoThrow(AppOpsManager.OPSTR_ACTIVATE_VPN, Process.myUid(),
TEST_VPN_PKG, null /* attributionTag */, null /* message */);
}
@@ -896,18 +1058,7 @@
when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
.thenReturn(mVpnProfile.encode());
vpn.startVpnProfile(TEST_VPN_PKG);
- verify(mAppOps).noteOpNoThrow(
- eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN),
- eq(Process.myUid()),
- eq(TEST_VPN_PKG),
- eq(null) /* attributionTag */,
- eq(null) /* message */);
- verify(mAppOps).startOp(
- eq(AppOpsManager.OPSTR_ESTABLISH_VPN_MANAGER),
- eq(Process.myUid()),
- eq(TEST_VPN_PKG),
- eq(null) /* attributionTag */,
- eq(null) /* message */);
+ verifyPlatformVpnIsActivated(TEST_VPN_PKG);
// Add a small delay to make sure that startOp is only called once.
verify(mAppOps, after(100).times(1)).startOp(
eq(AppOpsManager.OPSTR_ESTABLISH_VPN_MANAGER),
@@ -923,12 +1074,7 @@
eq(null) /* attributionTag */,
eq(null) /* message */);
vpn.stopVpnProfile(TEST_VPN_PKG);
- // Add a small delay to double confirm that startOp is only called once.
- verify(mAppOps, after(100)).finishOp(
- eq(AppOpsManager.OPSTR_ESTABLISH_VPN_MANAGER),
- eq(Process.myUid()),
- eq(TEST_VPN_PKG),
- eq(null) /* attributionTag */);
+ verifyPlatformVpnIsDeactivated(TEST_VPN_PKG);
}
@Test
@@ -964,6 +1110,144 @@
eq(null) /* message */);
}
+ private void verifyVpnManagerEvent(String sessionKey, String category, int errorClass,
+ int errorCode, VpnProfileState... profileState) {
+ final Context userContext =
+ mContext.createContextAsUser(UserHandle.of(primaryUser.id), 0 /* flags */);
+ final ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
+
+ final int verifyTimes = (profileState == null) ? 1 : profileState.length;
+ verify(userContext, times(verifyTimes)).startService(intentArgumentCaptor.capture());
+
+ for (int i = 0; i < verifyTimes; i++) {
+ final Intent intent = intentArgumentCaptor.getAllValues().get(i);
+ assertEquals(sessionKey, intent.getStringExtra(VpnManager.EXTRA_SESSION_KEY));
+ final Set<String> categories = intent.getCategories();
+ assertTrue(categories.contains(category));
+ assertEquals(errorClass,
+ intent.getIntExtra(VpnManager.EXTRA_ERROR_CLASS, -1 /* defaultValue */));
+ assertEquals(errorCode,
+ intent.getIntExtra(VpnManager.EXTRA_ERROR_CODE, -1 /* defaultValue */));
+ if (profileState != null) {
+ assertEquals(profileState[i], intent.getParcelableExtra(
+ VpnManager.EXTRA_VPN_PROFILE_STATE, VpnProfileState.class));
+ }
+ }
+ reset(userContext);
+ }
+
+ @Test
+ public void testVpnManagerEventForUserDeactivated() throws Exception {
+ assumeTrue(SdkLevel.isAtLeastT());
+ // For security reasons, Vpn#prepare() will check that oldPackage and newPackage are either
+ // null or the package of the caller. This test will call Vpn#prepare() to pretend the old
+ // VPN is replaced by a new one. But only Settings can change to some other packages, and
+ // this is checked with CONTROL_VPN so simulate holding CONTROL_VPN in order to pass the
+ // security checks.
+ doReturn(PERMISSION_GRANTED).when(mContext).checkCallingOrSelfPermission(CONTROL_VPN);
+ final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+ when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
+ .thenReturn(mVpnProfile.encode());
+
+ // Test the case that the user deactivates the vpn in vpn app.
+ final String sessionKey1 = vpn.startVpnProfile(TEST_VPN_PKG);
+ verifyPlatformVpnIsActivated(TEST_VPN_PKG);
+ vpn.stopVpnProfile(TEST_VPN_PKG);
+ verifyPlatformVpnIsDeactivated(TEST_VPN_PKG);
+ verifyPowerSaveTempWhitelistApp(TEST_VPN_PKG);
+ reset(mDeviceIdleInternal);
+ // CATEGORY_EVENT_DEACTIVATED_BY_USER is not an error event, so both of errorClass and
+ // errorCode won't be set.
+ verifyVpnManagerEvent(sessionKey1, VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER,
+ -1 /* errorClass */, -1 /* errorCode */, null /* profileState */);
+ reset(mAppOps);
+
+ // Test the case that the user chooses another vpn and the original one is replaced.
+ final String sessionKey2 = vpn.startVpnProfile(TEST_VPN_PKG);
+ verifyPlatformVpnIsActivated(TEST_VPN_PKG);
+ vpn.prepare(TEST_VPN_PKG, "com.new.vpn" /* newPackage */, TYPE_VPN_PLATFORM);
+ verifyPlatformVpnIsDeactivated(TEST_VPN_PKG);
+ verifyPowerSaveTempWhitelistApp(TEST_VPN_PKG);
+ reset(mDeviceIdleInternal);
+ // CATEGORY_EVENT_DEACTIVATED_BY_USER is not an error event, so both of errorClass and
+ // errorCode won't be set.
+ verifyVpnManagerEvent(sessionKey2, VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER,
+ -1 /* errorClass */, -1 /* errorCode */, null /* profileState */);
+ }
+
+ @Test
+ public void testVpnManagerEventForAlwaysOnChanged() throws Exception {
+ assumeTrue(SdkLevel.isAtLeastT());
+ // Calling setAlwaysOnPackage() needs to hold CONTROL_VPN.
+ doReturn(PERMISSION_GRANTED).when(mContext).checkCallingOrSelfPermission(CONTROL_VPN);
+ final Vpn vpn = createVpn(primaryUser.id);
+ // Enable VPN always-on for PKGS[1].
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false /* lockdown */,
+ null /* lockdownAllowlist */));
+ verifyPowerSaveTempWhitelistApp(PKGS[1]);
+ reset(mDeviceIdleInternal);
+ verifyVpnManagerEvent(null /* sessionKey */,
+ VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED, -1 /* errorClass */,
+ -1 /* errorCode */, new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
+ null /* sessionKey */, true /* alwaysOn */, false /* lockdown */));
+
+ // Enable VPN lockdown for PKGS[1].
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true /* lockdown */,
+ null /* lockdownAllowlist */));
+ verifyPowerSaveTempWhitelistApp(PKGS[1]);
+ reset(mDeviceIdleInternal);
+ verifyVpnManagerEvent(null /* sessionKey */,
+ VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED, -1 /* errorClass */,
+ -1 /* errorCode */, new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
+ null /* sessionKey */, true /* alwaysOn */, true /* lockdown */));
+
+ // Disable VPN lockdown for PKGS[1].
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false /* lockdown */,
+ null /* lockdownAllowlist */));
+ verifyPowerSaveTempWhitelistApp(PKGS[1]);
+ reset(mDeviceIdleInternal);
+ verifyVpnManagerEvent(null /* sessionKey */,
+ VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED, -1 /* errorClass */,
+ -1 /* errorCode */, new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
+ null /* sessionKey */, true /* alwaysOn */, false /* lockdown */));
+
+ // Disable VPN always-on.
+ assertTrue(vpn.setAlwaysOnPackage(null, false /* lockdown */,
+ null /* lockdownAllowlist */));
+ verifyPowerSaveTempWhitelistApp(PKGS[1]);
+ reset(mDeviceIdleInternal);
+ verifyVpnManagerEvent(null /* sessionKey */,
+ VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED, -1 /* errorClass */,
+ -1 /* errorCode */, new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
+ null /* sessionKey */, false /* alwaysOn */, false /* lockdown */));
+
+ // Enable VPN always-on for PKGS[1] again.
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false /* lockdown */,
+ null /* lockdownAllowlist */));
+ verifyPowerSaveTempWhitelistApp(PKGS[1]);
+ reset(mDeviceIdleInternal);
+ verifyVpnManagerEvent(null /* sessionKey */,
+ VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED, -1 /* errorClass */,
+ -1 /* errorCode */, new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
+ null /* sessionKey */, true /* alwaysOn */, false /* lockdown */));
+
+ // Enable VPN always-on for PKGS[2].
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[2], false /* lockdown */,
+ null /* lockdownAllowlist */));
+ verifyPowerSaveTempWhitelistApp(PKGS[2]);
+ reset(mDeviceIdleInternal);
+ // PKGS[1] is replaced with PKGS[2].
+ // Pass 2 VpnProfileState objects to verifyVpnManagerEvent(), the first one is sent to
+ // PKGS[1] to notify PKGS[1] that the VPN always-on is disabled, the second one is sent to
+ // PKGS[2] to notify PKGS[2] that the VPN always-on is enabled.
+ verifyVpnManagerEvent(null /* sessionKey */,
+ VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED, -1 /* errorClass */,
+ -1 /* errorCode */, new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
+ null /* sessionKey */, false /* alwaysOn */, false /* lockdown */),
+ new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
+ null /* sessionKey */, true /* alwaysOn */, false /* lockdown */));
+ }
+
@Test
public void testSetPackageAuthorizationVpnService() throws Exception {
final Vpn vpn = createVpnAndSetupUidChecks();
@@ -981,7 +1265,7 @@
public void testSetPackageAuthorizationPlatformVpn() throws Exception {
final Vpn vpn = createVpnAndSetupUidChecks();
- assertTrue(vpn.setPackageAuthorization(TEST_VPN_PKG, VpnManager.TYPE_VPN_PLATFORM));
+ assertTrue(vpn.setPackageAuthorization(TEST_VPN_PKG, TYPE_VPN_PLATFORM));
verify(mAppOps)
.setMode(
eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN),
@@ -1031,15 +1315,16 @@
config -> Arrays.asList(config.flags).contains(flag)));
}
- @Test
- public void testStartPlatformVpnAuthenticationFailed() throws Exception {
+ private void setupPlatformVpnWithSpecificExceptionAndItsErrorCode(IkeException exception,
+ String category, int errorType, int errorCode) throws Exception {
final ArgumentCaptor<IkeSessionCallback> captor =
ArgumentCaptor.forClass(IkeSessionCallback.class);
- final IkeProtocolException exception = mock(IkeProtocolException.class);
- when(exception.getErrorType())
- .thenReturn(IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED);
- final Vpn vpn = startLegacyVpn(createVpn(primaryUser.id), (mVpnProfile));
+ final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+ when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
+ .thenReturn(mVpnProfile.encode());
+
+ final String sessionKey = vpn.startVpnProfile(TEST_VPN_PKG);
final NetworkCallback cb = triggerOnAvailableAndGetCallback();
verifyInterfaceSetCfgWithFlags(IF_STATE_UP);
@@ -1049,10 +1334,77 @@
verify(mIkev2SessionCreator, timeout(TEST_TIMEOUT_MS))
.createIkeSession(any(), any(), any(), any(), captor.capture(), any());
final IkeSessionCallback ikeCb = captor.getValue();
- ikeCb.onClosedExceptionally(exception);
+ ikeCb.onClosedWithException(exception);
- verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS)).unregisterNetworkCallback(eq(cb));
- assertEquals(LegacyVpnInfo.STATE_FAILED, vpn.getLegacyVpnInfo().state);
+ verifyPowerSaveTempWhitelistApp(TEST_VPN_PKG);
+ reset(mDeviceIdleInternal);
+ verifyVpnManagerEvent(sessionKey, category, errorType, errorCode, null /* profileState */);
+ if (errorType == VpnManager.ERROR_CLASS_NOT_RECOVERABLE) {
+ verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS))
+ .unregisterNetworkCallback(eq(cb));
+ }
+ }
+
+ @Test
+ public void testStartPlatformVpnAuthenticationFailed() throws Exception {
+ final IkeProtocolException exception = mock(IkeProtocolException.class);
+ final int errorCode = IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED;
+ when(exception.getErrorType()).thenReturn(errorCode);
+ setupPlatformVpnWithSpecificExceptionAndItsErrorCode(exception,
+ VpnManager.CATEGORY_EVENT_IKE_ERROR, VpnManager.ERROR_CLASS_NOT_RECOVERABLE,
+ errorCode);
+ }
+
+ @Test
+ public void testStartPlatformVpnFailedWithRecoverableError() throws Exception {
+ final IkeProtocolException exception = mock(IkeProtocolException.class);
+ final int errorCode = IkeProtocolException.ERROR_TYPE_TEMPORARY_FAILURE;
+ when(exception.getErrorType()).thenReturn(errorCode);
+ setupPlatformVpnWithSpecificExceptionAndItsErrorCode(exception,
+ VpnManager.CATEGORY_EVENT_IKE_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE, errorCode);
+ }
+
+ @Test
+ public void testStartPlatformVpnFailedWithUnknownHostException() throws Exception {
+ final IkeNonProtocolException exception = mock(IkeNonProtocolException.class);
+ final UnknownHostException unknownHostException = new UnknownHostException();
+ final int errorCode = VpnManager.ERROR_CODE_NETWORK_UNKNOWN_HOST;
+ when(exception.getCause()).thenReturn(unknownHostException);
+ setupPlatformVpnWithSpecificExceptionAndItsErrorCode(exception,
+ VpnManager.CATEGORY_EVENT_NETWORK_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE,
+ errorCode);
+ }
+
+ @Test
+ public void testStartPlatformVpnFailedWithIkeTimeoutException() throws Exception {
+ final IkeNonProtocolException exception = mock(IkeNonProtocolException.class);
+ final IkeTimeoutException ikeTimeoutException =
+ new IkeTimeoutException("IkeTimeoutException");
+ final int errorCode = VpnManager.ERROR_CODE_NETWORK_PROTOCOL_TIMEOUT;
+ when(exception.getCause()).thenReturn(ikeTimeoutException);
+ setupPlatformVpnWithSpecificExceptionAndItsErrorCode(exception,
+ VpnManager.CATEGORY_EVENT_NETWORK_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE,
+ errorCode);
+ }
+
+ @Test
+ public void testStartPlatformVpnFailedWithIkeNetworkLostException() throws Exception {
+ final IkeNetworkLostException exception = new IkeNetworkLostException(
+ new Network(100));
+ setupPlatformVpnWithSpecificExceptionAndItsErrorCode(exception,
+ VpnManager.CATEGORY_EVENT_NETWORK_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE,
+ VpnManager.ERROR_CODE_NETWORK_LOST);
+ }
+
+ @Test
+ public void testStartPlatformVpnFailedWithIOException() throws Exception {
+ final IkeNonProtocolException exception = mock(IkeNonProtocolException.class);
+ final IOException ioException = new IOException();
+ final int errorCode = VpnManager.ERROR_CODE_NETWORK_IO;
+ when(exception.getCause()).thenReturn(ioException);
+ setupPlatformVpnWithSpecificExceptionAndItsErrorCode(exception,
+ VpnManager.CATEGORY_EVENT_NETWORK_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE,
+ errorCode);
}
@Test
@@ -1208,7 +1560,7 @@
}
}
- private static final class TestDeps extends Vpn.Dependencies {
+ private final class TestDeps extends Vpn.Dependencies {
public final CompletableFuture<String[]> racoonArgs = new CompletableFuture();
public final CompletableFuture<String[]> mtpdArgs = new CompletableFuture();
public final File mStateFile;
@@ -1337,6 +1689,11 @@
@Override
public void setBlocking(FileDescriptor fd, boolean blocking) {}
+
+ @Override
+ public DeviceIdleInternal getDeviceIdleInternal() {
+ return mDeviceIdleInternal;
+ }
}
/**
diff --git a/tests/unit/java/com/android/server/ethernet/EthernetConfigStoreTest.java b/tests/unit/java/com/android/server/ethernet/EthernetConfigStoreTest.java
new file mode 100644
index 0000000..a9f80ea
--- /dev/null
+++ b/tests/unit/java/com/android/server/ethernet/EthernetConfigStoreTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.ethernet;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.net.InetAddresses;
+import android.net.IpConfiguration;
+import android.net.IpConfiguration.IpAssignment;
+import android.net.IpConfiguration.ProxySettings;
+import android.net.LinkAddress;
+import android.net.ProxyInfo;
+import android.net.StaticIpConfiguration;
+import android.util.ArrayMap;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class EthernetConfigStoreTest {
+ private static final LinkAddress LINKADDR = new LinkAddress("192.168.1.100/25");
+ private static final InetAddress GATEWAY = InetAddresses.parseNumericAddress("192.168.1.1");
+ private static final InetAddress DNS1 = InetAddresses.parseNumericAddress("8.8.8.8");
+ private static final InetAddress DNS2 = InetAddresses.parseNumericAddress("8.8.4.4");
+ private static final StaticIpConfiguration STATIC_IP_CONFIG =
+ new StaticIpConfiguration.Builder()
+ .setIpAddress(LINKADDR)
+ .setGateway(GATEWAY)
+ .setDnsServers(new ArrayList<InetAddress>(
+ List.of(DNS1, DNS2)))
+ .build();
+ private static final ProxyInfo PROXY_INFO = ProxyInfo.buildDirectProxy("test", 8888);
+ private static final IpConfiguration APEX_IP_CONFIG =
+ new IpConfiguration(IpAssignment.DHCP, ProxySettings.NONE, null, null);
+ private static final IpConfiguration LEGACY_IP_CONFIG =
+ new IpConfiguration(IpAssignment.STATIC, ProxySettings.STATIC, STATIC_IP_CONFIG,
+ PROXY_INFO);
+
+ private EthernetConfigStore mEthernetConfigStore;
+ private File mApexTestDir;
+ private File mLegacyTestDir;
+ private File mApexConfigFile;
+ private File mLegacyConfigFile;
+
+ private void createTestDir() {
+ final Context context = InstrumentationRegistry.getContext();
+ final File baseDir = context.getFilesDir();
+ mApexTestDir = new File(baseDir.getPath() + "/apex");
+ mApexTestDir.mkdirs();
+
+ mLegacyTestDir = new File(baseDir.getPath() + "/legacy");
+ mLegacyTestDir.mkdirs();
+ }
+
+ @Before
+ public void setUp() {
+ createTestDir();
+ mEthernetConfigStore = new EthernetConfigStore();
+ }
+
+ @After
+ public void tearDown() {
+ mApexTestDir.delete();
+ mLegacyTestDir.delete();
+ }
+
+ private void assertConfigFileExist(final String filepath) {
+ assertTrue(new File(filepath).exists());
+ }
+
+ /** Wait for the delayed write operation completes. */
+ private void waitForMs(long ms) {
+ try {
+ Thread.sleep(ms);
+ } catch (final InterruptedException e) {
+ fail("Thread was interrupted");
+ }
+ }
+
+ @Test
+ public void testWriteIpConfigToApexFilePathAndRead() throws Exception {
+ // Write the config file to the apex file path, pretend the config file exits and
+ // check if IP config should be read from apex file path.
+ mApexConfigFile = new File(mApexTestDir.getPath(), "test.txt");
+ mEthernetConfigStore.write("eth0", APEX_IP_CONFIG, mApexConfigFile.getPath());
+ waitForMs(50);
+
+ mEthernetConfigStore.read(mApexTestDir.getPath(), mLegacyTestDir.getPath(), "/test.txt");
+ final ArrayMap<String, IpConfiguration> ipConfigurations =
+ mEthernetConfigStore.getIpConfigurations();
+ assertEquals(APEX_IP_CONFIG, ipConfigurations.get("eth0"));
+
+ mApexConfigFile.delete();
+ }
+
+ @Test
+ public void testWriteIpConfigToLegacyFilePathAndRead() throws Exception {
+ // Write the config file to the legacy file path, pretend the config file exits and
+ // check if IP config should be read from legacy file path.
+ mLegacyConfigFile = new File(mLegacyTestDir, "test.txt");
+ mEthernetConfigStore.write("0", LEGACY_IP_CONFIG, mLegacyConfigFile.getPath());
+ waitForMs(50);
+
+ mEthernetConfigStore.read(mApexTestDir.getPath(), mLegacyTestDir.getPath(), "/test.txt");
+ final ArrayMap<String, IpConfiguration> ipConfigurations =
+ mEthernetConfigStore.getIpConfigurations();
+ assertEquals(LEGACY_IP_CONFIG, ipConfigurations.get("0"));
+
+ // Check the same config file in apex file path is created.
+ assertConfigFileExist(mApexTestDir.getPath() + "/test.txt");
+
+ final File apexConfigFile = new File(mApexTestDir.getPath() + "/test.txt");
+ apexConfigFile.delete();
+ mLegacyConfigFile.delete();
+ }
+}
diff --git a/tests/unit/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java b/tests/unit/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java
new file mode 100644
index 0000000..4f849d2
--- /dev/null
+++ b/tests/unit/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java
@@ -0,0 +1,717 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.ethernet;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.same;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.app.test.MockAnswerUtil.AnswerWithArguments;
+import android.content.Context;
+import android.content.res.Resources;
+import android.net.ConnectivityManager;
+import android.net.EthernetNetworkManagementException;
+import android.net.EthernetNetworkSpecifier;
+import android.net.INetworkInterfaceOutcomeReceiver;
+import android.net.IpConfiguration;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkAgentConfig;
+import android.net.NetworkCapabilities;
+import android.net.NetworkProvider;
+import android.net.NetworkProvider.NetworkOfferCallback;
+import android.net.NetworkRequest;
+import android.net.StaticIpConfiguration;
+import android.net.ip.IpClientCallbacks;
+import android.net.ip.IpClientManager;
+import android.os.Build;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.test.TestLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.net.module.util.InterfaceParams;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+
+@SmallTest
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
+public class EthernetNetworkFactoryTest {
+ private static final int TIMEOUT_MS = 2_000;
+ private static final String TEST_IFACE = "test123";
+ private static final INetworkInterfaceOutcomeReceiver NULL_LISTENER = null;
+ private static final String IP_ADDR = "192.0.2.2/25";
+ private static final LinkAddress LINK_ADDR = new LinkAddress(IP_ADDR);
+ private static final String HW_ADDR = "01:02:03:04:05:06";
+ private TestLooper mLooper;
+ private Handler mHandler;
+ private EthernetNetworkFactory mNetFactory = null;
+ private IpClientCallbacks mIpClientCallbacks;
+ @Mock private Context mContext;
+ @Mock private Resources mResources;
+ @Mock private EthernetNetworkFactory.Dependencies mDeps;
+ @Mock private IpClientManager mIpClient;
+ @Mock private EthernetNetworkAgent mNetworkAgent;
+ @Mock private InterfaceParams mInterfaceParams;
+ @Mock private Network mMockNetwork;
+ @Mock private NetworkProvider mNetworkProvider;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ setupNetworkAgentMock();
+ setupIpClientMock();
+ setupContext();
+ }
+
+ //TODO: Move away from usage of TestLooper in order to move this logic back into @Before.
+ private void initEthernetNetworkFactory() {
+ mLooper = new TestLooper();
+ mHandler = new Handler(mLooper.getLooper());
+ mNetFactory = new EthernetNetworkFactory(mHandler, mContext, mNetworkProvider, mDeps);
+ }
+
+ private void setupNetworkAgentMock() {
+ when(mDeps.makeEthernetNetworkAgent(any(), any(), any(), any(), any(), any(), any()))
+ .thenAnswer(new AnswerWithArguments() {
+ public EthernetNetworkAgent answer(
+ Context context,
+ Looper looper,
+ NetworkCapabilities nc,
+ LinkProperties lp,
+ NetworkAgentConfig config,
+ NetworkProvider provider,
+ EthernetNetworkAgent.Callbacks cb) {
+ when(mNetworkAgent.getCallbacks()).thenReturn(cb);
+ when(mNetworkAgent.getNetwork())
+ .thenReturn(mMockNetwork);
+ return mNetworkAgent;
+ }
+ }
+ );
+ }
+
+ private void setupIpClientMock() throws Exception {
+ doAnswer(inv -> {
+ // these tests only support one concurrent IpClient, so make sure we do not accidentally
+ // create a mess.
+ assertNull("An IpClient has already been created.", mIpClientCallbacks);
+
+ mIpClientCallbacks = inv.getArgument(2);
+ mIpClientCallbacks.onIpClientCreated(null);
+ mLooper.dispatchAll();
+ return null;
+ }).when(mDeps).makeIpClient(any(Context.class), anyString(), any());
+
+ doAnswer(inv -> {
+ mIpClientCallbacks.onQuit();
+ mLooper.dispatchAll();
+ mIpClientCallbacks = null;
+ return null;
+ }).when(mIpClient).shutdown();
+
+ when(mDeps.makeIpClientManager(any())).thenReturn(mIpClient);
+ }
+
+ private void triggerOnProvisioningSuccess() {
+ mIpClientCallbacks.onProvisioningSuccess(new LinkProperties());
+ mLooper.dispatchAll();
+ }
+
+ private void triggerOnProvisioningFailure() {
+ mIpClientCallbacks.onProvisioningFailure(new LinkProperties());
+ mLooper.dispatchAll();
+ }
+
+ private void triggerOnReachabilityLost() {
+ mIpClientCallbacks.onReachabilityLost("ReachabilityLost");
+ mLooper.dispatchAll();
+ }
+
+ private void setupContext() {
+ when(mDeps.getTcpBufferSizesFromResource(eq(mContext))).thenReturn("");
+ }
+
+ @After
+ public void tearDown() {
+ // looper is shared with the network agents, so there may still be messages to dispatch on
+ // tear down.
+ mLooper.dispatchAll();
+ }
+
+ private NetworkCapabilities createDefaultFilterCaps() {
+ return NetworkCapabilities.Builder.withoutDefaultCapabilities()
+ .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
+ .build();
+ }
+
+ private NetworkCapabilities.Builder createInterfaceCapsBuilder(final int transportType) {
+ return new NetworkCapabilities.Builder()
+ .addTransportType(transportType)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
+ }
+
+ private NetworkRequest.Builder createDefaultRequestBuilder() {
+ return new NetworkRequest.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+ }
+
+ private NetworkRequest createDefaultRequest() {
+ return createDefaultRequestBuilder().build();
+ }
+
+ private IpConfiguration createDefaultIpConfig() {
+ IpConfiguration ipConfig = new IpConfiguration();
+ ipConfig.setIpAssignment(IpConfiguration.IpAssignment.DHCP);
+ ipConfig.setProxySettings(IpConfiguration.ProxySettings.NONE);
+ return ipConfig;
+ }
+
+ /**
+ * Create an {@link IpConfiguration} with an associated {@link StaticIpConfiguration}.
+ *
+ * @return {@link IpConfiguration} with its {@link StaticIpConfiguration} set.
+ */
+ private IpConfiguration createStaticIpConfig() {
+ final IpConfiguration ipConfig = new IpConfiguration();
+ ipConfig.setIpAssignment(IpConfiguration.IpAssignment.STATIC);
+ ipConfig.setStaticIpConfiguration(
+ new StaticIpConfiguration.Builder().setIpAddress(LINK_ADDR).build());
+ return ipConfig;
+ }
+
+ // creates an interface with provisioning in progress (since updating the interface link state
+ // automatically starts the provisioning process)
+ private void createInterfaceUndergoingProvisioning(String iface) {
+ // Default to the ethernet transport type.
+ createInterfaceUndergoingProvisioning(iface, NetworkCapabilities.TRANSPORT_ETHERNET);
+ }
+
+ private void createInterfaceUndergoingProvisioning(
+ @NonNull final String iface, final int transportType) {
+ final IpConfiguration ipConfig = createDefaultIpConfig();
+ mNetFactory.addInterface(iface, HW_ADDR, ipConfig,
+ createInterfaceCapsBuilder(transportType).build());
+ assertTrue(mNetFactory.updateInterfaceLinkState(iface, true, NULL_LISTENER));
+
+ ArgumentCaptor<NetworkOfferCallback> captor = ArgumentCaptor.forClass(
+ NetworkOfferCallback.class);
+ verify(mNetworkProvider).registerNetworkOffer(any(), any(), any(), captor.capture());
+ captor.getValue().onNetworkNeeded(createDefaultRequest());
+
+ verifyStart(ipConfig);
+ clearInvocations(mDeps);
+ clearInvocations(mIpClient);
+ clearInvocations(mNetworkProvider);
+ }
+
+ // creates a provisioned interface
+ private void createAndVerifyProvisionedInterface(String iface) throws Exception {
+ // Default to the ethernet transport type.
+ createAndVerifyProvisionedInterface(iface, NetworkCapabilities.TRANSPORT_ETHERNET,
+ ConnectivityManager.TYPE_ETHERNET);
+ }
+
+ private void createVerifyAndRemoveProvisionedInterface(final int transportType,
+ final int expectedLegacyType) throws Exception {
+ createAndVerifyProvisionedInterface(TEST_IFACE, transportType,
+ expectedLegacyType);
+ mNetFactory.removeInterface(TEST_IFACE);
+ }
+
+ private void createAndVerifyProvisionedInterface(
+ @NonNull final String iface, final int transportType, final int expectedLegacyType)
+ throws Exception {
+ createInterfaceUndergoingProvisioning(iface, transportType);
+ triggerOnProvisioningSuccess();
+ // provisioning succeeded, verify that the network agent is created, registered, marked
+ // as connected and legacy type are correctly set.
+ final ArgumentCaptor<NetworkCapabilities> ncCaptor = ArgumentCaptor.forClass(
+ NetworkCapabilities.class);
+ verify(mDeps).makeEthernetNetworkAgent(any(), any(), ncCaptor.capture(), any(),
+ argThat(x -> x.getLegacyType() == expectedLegacyType), any(), any());
+ assertEquals(
+ new EthernetNetworkSpecifier(iface), ncCaptor.getValue().getNetworkSpecifier());
+ verifyNetworkAgentRegistersAndConnects();
+ clearInvocations(mDeps);
+ clearInvocations(mNetworkAgent);
+ }
+
+ // creates an unprovisioned interface
+ private void createUnprovisionedInterface(String iface) throws Exception {
+ // To create an unprovisioned interface, provision and then "stop" it, i.e. stop its
+ // NetworkAgent and IpClient. One way this can be done is by provisioning an interface and
+ // then calling onNetworkUnwanted.
+ mNetFactory.addInterface(iface, HW_ADDR, createDefaultIpConfig(),
+ createInterfaceCapsBuilder(NetworkCapabilities.TRANSPORT_ETHERNET).build());
+ assertTrue(mNetFactory.updateInterfaceLinkState(iface, true, NULL_LISTENER));
+
+ clearInvocations(mIpClient);
+ clearInvocations(mNetworkAgent);
+ }
+
+ @Test
+ public void testUpdateInterfaceLinkStateForActiveProvisioningInterface() throws Exception {
+ initEthernetNetworkFactory();
+ createInterfaceUndergoingProvisioning(TEST_IFACE);
+ final TestNetworkManagementListener listener = new TestNetworkManagementListener();
+
+ // verify that the IpClient gets shut down when interface state changes to down.
+ final boolean ret =
+ mNetFactory.updateInterfaceLinkState(TEST_IFACE, false /* up */, listener);
+
+ assertTrue(ret);
+ verify(mIpClient).shutdown();
+ assertEquals(listener.expectOnResult(), TEST_IFACE);
+ }
+
+ @Test
+ public void testUpdateInterfaceLinkStateForProvisionedInterface() throws Exception {
+ initEthernetNetworkFactory();
+ createAndVerifyProvisionedInterface(TEST_IFACE);
+ final TestNetworkManagementListener listener = new TestNetworkManagementListener();
+
+ final boolean ret =
+ mNetFactory.updateInterfaceLinkState(TEST_IFACE, false /* up */, listener);
+
+ assertTrue(ret);
+ verifyStop();
+ assertEquals(listener.expectOnResult(), TEST_IFACE);
+ }
+
+ @Test
+ public void testUpdateInterfaceLinkStateForUnprovisionedInterface() throws Exception {
+ initEthernetNetworkFactory();
+ createUnprovisionedInterface(TEST_IFACE);
+ final TestNetworkManagementListener listener = new TestNetworkManagementListener();
+
+ final boolean ret =
+ mNetFactory.updateInterfaceLinkState(TEST_IFACE, false /* up */, listener);
+
+ assertTrue(ret);
+ // There should not be an active IPClient or NetworkAgent.
+ verify(mDeps, never()).makeIpClient(any(), any(), any());
+ verify(mDeps, never())
+ .makeEthernetNetworkAgent(any(), any(), any(), any(), any(), any(), any());
+ assertEquals(listener.expectOnResult(), TEST_IFACE);
+ }
+
+ @Test
+ public void testUpdateInterfaceLinkStateForNonExistingInterface() throws Exception {
+ initEthernetNetworkFactory();
+ final TestNetworkManagementListener listener = new TestNetworkManagementListener();
+
+ // if interface was never added, link state cannot be updated.
+ final boolean ret =
+ mNetFactory.updateInterfaceLinkState(TEST_IFACE, true /* up */, listener);
+
+ assertFalse(ret);
+ verifyNoStopOrStart();
+ listener.expectOnError();
+ }
+
+ @Test
+ public void testUpdateInterfaceLinkStateWithNoChanges() throws Exception {
+ initEthernetNetworkFactory();
+ createAndVerifyProvisionedInterface(TEST_IFACE);
+ final TestNetworkManagementListener listener = new TestNetworkManagementListener();
+
+ final boolean ret =
+ mNetFactory.updateInterfaceLinkState(TEST_IFACE, true /* up */, listener);
+
+ assertFalse(ret);
+ verifyNoStopOrStart();
+ listener.expectOnError();
+ }
+
+ @Test
+ public void testProvisioningLoss() throws Exception {
+ initEthernetNetworkFactory();
+ when(mDeps.getNetworkInterfaceByName(TEST_IFACE)).thenReturn(mInterfaceParams);
+ createAndVerifyProvisionedInterface(TEST_IFACE);
+
+ triggerOnProvisioningFailure();
+ verifyStop();
+ // provisioning loss should trigger a retry, since the interface is still there
+ verify(mIpClient).startProvisioning(any());
+ }
+
+ @Test
+ public void testProvisioningLossForDisappearedInterface() throws Exception {
+ initEthernetNetworkFactory();
+ // mocked method returns null by default, but just to be explicit in the test:
+ when(mDeps.getNetworkInterfaceByName(eq(TEST_IFACE))).thenReturn(null);
+
+ createAndVerifyProvisionedInterface(TEST_IFACE);
+ triggerOnProvisioningFailure();
+
+ // the interface disappeared and getNetworkInterfaceByName returns null, we should not retry
+ verify(mIpClient, never()).startProvisioning(any());
+ verifyNoStopOrStart();
+ }
+
+ private void verifyNoStopOrStart() {
+ verify(mNetworkAgent, never()).register();
+ verify(mIpClient, never()).shutdown();
+ verify(mNetworkAgent, never()).unregister();
+ verify(mIpClient, never()).startProvisioning(any());
+ }
+
+ @Test
+ public void testLinkPropertiesChanged() throws Exception {
+ initEthernetNetworkFactory();
+ createAndVerifyProvisionedInterface(TEST_IFACE);
+
+ LinkProperties lp = new LinkProperties();
+ mIpClientCallbacks.onLinkPropertiesChange(lp);
+ mLooper.dispatchAll();
+ verify(mNetworkAgent).sendLinkPropertiesImpl(same(lp));
+ }
+
+ @Test
+ public void testNetworkUnwanted() throws Exception {
+ initEthernetNetworkFactory();
+ createAndVerifyProvisionedInterface(TEST_IFACE);
+
+ mNetworkAgent.getCallbacks().onNetworkUnwanted();
+ mLooper.dispatchAll();
+ verifyStop();
+ }
+
+ @Test
+ public void testNetworkUnwantedWithStaleNetworkAgent() throws Exception {
+ initEthernetNetworkFactory();
+ // ensures provisioning is restarted after provisioning loss
+ when(mDeps.getNetworkInterfaceByName(TEST_IFACE)).thenReturn(mInterfaceParams);
+ createAndVerifyProvisionedInterface(TEST_IFACE);
+
+ EthernetNetworkAgent.Callbacks oldCbs = mNetworkAgent.getCallbacks();
+ // replace network agent in EthernetNetworkFactory
+ // Loss of provisioning will restart the ip client and network agent.
+ triggerOnProvisioningFailure();
+ verify(mDeps).makeIpClient(any(), any(), any());
+
+ triggerOnProvisioningSuccess();
+ verify(mDeps).makeEthernetNetworkAgent(any(), any(), any(), any(), any(), any(), any());
+
+ // verify that unwanted is ignored
+ clearInvocations(mIpClient);
+ clearInvocations(mNetworkAgent);
+ oldCbs.onNetworkUnwanted();
+ verify(mIpClient, never()).shutdown();
+ verify(mNetworkAgent, never()).unregister();
+ }
+
+ @Test
+ public void testTransportOverrideIsCorrectlySet() throws Exception {
+ initEthernetNetworkFactory();
+ // createProvisionedInterface() has verifications in place for transport override
+ // functionality which for EthernetNetworkFactory is network score and legacy type mappings.
+ createVerifyAndRemoveProvisionedInterface(NetworkCapabilities.TRANSPORT_ETHERNET,
+ ConnectivityManager.TYPE_ETHERNET);
+ createVerifyAndRemoveProvisionedInterface(NetworkCapabilities.TRANSPORT_BLUETOOTH,
+ ConnectivityManager.TYPE_BLUETOOTH);
+ createVerifyAndRemoveProvisionedInterface(NetworkCapabilities.TRANSPORT_WIFI,
+ ConnectivityManager.TYPE_WIFI);
+ createVerifyAndRemoveProvisionedInterface(NetworkCapabilities.TRANSPORT_CELLULAR,
+ ConnectivityManager.TYPE_MOBILE);
+ createVerifyAndRemoveProvisionedInterface(NetworkCapabilities.TRANSPORT_LOWPAN,
+ ConnectivityManager.TYPE_NONE);
+ createVerifyAndRemoveProvisionedInterface(NetworkCapabilities.TRANSPORT_WIFI_AWARE,
+ ConnectivityManager.TYPE_NONE);
+ createVerifyAndRemoveProvisionedInterface(NetworkCapabilities.TRANSPORT_TEST,
+ ConnectivityManager.TYPE_NONE);
+ }
+
+ @Test
+ public void testReachabilityLoss() throws Exception {
+ initEthernetNetworkFactory();
+ createAndVerifyProvisionedInterface(TEST_IFACE);
+
+ triggerOnReachabilityLost();
+
+ // Reachability loss should trigger a stop and start, since the interface is still there
+ verifyRestart(createDefaultIpConfig());
+ }
+
+ private IpClientCallbacks getStaleIpClientCallbacks() throws Exception {
+ createAndVerifyProvisionedInterface(TEST_IFACE);
+ final IpClientCallbacks staleIpClientCallbacks = mIpClientCallbacks;
+ mNetFactory.removeInterface(TEST_IFACE);
+ verifyStop();
+ assertNotSame(mIpClientCallbacks, staleIpClientCallbacks);
+ return staleIpClientCallbacks;
+ }
+
+ @Test
+ public void testIgnoreOnIpLayerStartedCallbackForStaleCallback() throws Exception {
+ initEthernetNetworkFactory();
+ final IpClientCallbacks staleIpClientCallbacks = getStaleIpClientCallbacks();
+
+ staleIpClientCallbacks.onProvisioningSuccess(new LinkProperties());
+ mLooper.dispatchAll();
+
+ verify(mIpClient, never()).startProvisioning(any());
+ verify(mNetworkAgent, never()).register();
+ }
+
+ @Test
+ public void testIgnoreOnIpLayerStoppedCallbackForStaleCallback() throws Exception {
+ initEthernetNetworkFactory();
+ when(mDeps.getNetworkInterfaceByName(TEST_IFACE)).thenReturn(mInterfaceParams);
+ final IpClientCallbacks staleIpClientCallbacks = getStaleIpClientCallbacks();
+
+ staleIpClientCallbacks.onProvisioningFailure(new LinkProperties());
+ mLooper.dispatchAll();
+
+ verify(mIpClient, never()).startProvisioning(any());
+ }
+
+ @Test
+ public void testIgnoreLinkPropertiesCallbackForStaleCallback() throws Exception {
+ initEthernetNetworkFactory();
+ final IpClientCallbacks staleIpClientCallbacks = getStaleIpClientCallbacks();
+ final LinkProperties lp = new LinkProperties();
+
+ staleIpClientCallbacks.onLinkPropertiesChange(lp);
+ mLooper.dispatchAll();
+
+ verify(mNetworkAgent, never()).sendLinkPropertiesImpl(eq(lp));
+ }
+
+ @Test
+ public void testIgnoreNeighborLossCallbackForStaleCallback() throws Exception {
+ initEthernetNetworkFactory();
+ final IpClientCallbacks staleIpClientCallbacks = getStaleIpClientCallbacks();
+
+ staleIpClientCallbacks.onReachabilityLost("Neighbor Lost");
+ mLooper.dispatchAll();
+
+ verify(mIpClient, never()).startProvisioning(any());
+ verify(mNetworkAgent, never()).register();
+ }
+
+ private void verifyRestart(@NonNull final IpConfiguration ipConfig) {
+ verifyStop();
+ verifyStart(ipConfig);
+ }
+
+ private void verifyStart(@NonNull final IpConfiguration ipConfig) {
+ verify(mDeps).makeIpClient(any(Context.class), anyString(), any());
+ verify(mIpClient).startProvisioning(
+ argThat(x -> Objects.equals(x.mStaticIpConfig, ipConfig.getStaticIpConfiguration()))
+ );
+ }
+
+ private void verifyStop() {
+ verify(mIpClient).shutdown();
+ verify(mNetworkAgent).unregister();
+ }
+
+ private void verifyNetworkAgentRegistersAndConnects() {
+ verify(mNetworkAgent).register();
+ verify(mNetworkAgent).markConnected();
+ }
+
+ private static final class TestNetworkManagementListener
+ implements INetworkInterfaceOutcomeReceiver {
+ private final CompletableFuture<String> mResult = new CompletableFuture<>();
+
+ @Override
+ public void onResult(@NonNull String iface) {
+ mResult.complete(iface);
+ }
+
+ @Override
+ public void onError(@NonNull EthernetNetworkManagementException exception) {
+ mResult.completeExceptionally(exception);
+ }
+
+ String expectOnResult() throws Exception {
+ return mResult.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ }
+
+ void expectOnError() throws Exception {
+ assertThrows(EthernetNetworkManagementException.class, () -> {
+ try {
+ mResult.get();
+ } catch (ExecutionException e) {
+ throw e.getCause();
+ }
+ });
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return null;
+ }
+ }
+
+ @Test
+ public void testUpdateInterfaceCallsListenerCorrectlyOnSuccess() throws Exception {
+ initEthernetNetworkFactory();
+ createAndVerifyProvisionedInterface(TEST_IFACE);
+ final NetworkCapabilities capabilities = createDefaultFilterCaps();
+ final IpConfiguration ipConfiguration = createStaticIpConfig();
+ final TestNetworkManagementListener listener = new TestNetworkManagementListener();
+
+ mNetFactory.updateInterface(TEST_IFACE, ipConfiguration, capabilities, listener);
+ triggerOnProvisioningSuccess();
+
+ assertEquals(listener.expectOnResult(), TEST_IFACE);
+ }
+
+ @Test
+ public void testUpdateInterfaceAbortsOnConcurrentRemoveInterface() throws Exception {
+ initEthernetNetworkFactory();
+ verifyNetworkManagementCallIsAbortedWhenInterrupted(
+ TEST_IFACE,
+ () -> mNetFactory.removeInterface(TEST_IFACE));
+ }
+
+ @Test
+ public void testUpdateInterfaceAbortsOnConcurrentUpdateInterfaceLinkState() throws Exception {
+ initEthernetNetworkFactory();
+ verifyNetworkManagementCallIsAbortedWhenInterrupted(
+ TEST_IFACE,
+ () -> mNetFactory.updateInterfaceLinkState(TEST_IFACE, false, NULL_LISTENER));
+ }
+
+ @Test
+ public void testUpdateInterfaceCallsListenerCorrectlyOnConcurrentRequests() throws Exception {
+ initEthernetNetworkFactory();
+ final NetworkCapabilities capabilities = createDefaultFilterCaps();
+ final IpConfiguration ipConfiguration = createStaticIpConfig();
+ final TestNetworkManagementListener successfulListener =
+ new TestNetworkManagementListener();
+
+ // If two calls come in before the first one completes, the first listener will be aborted
+ // and the second one will be successful.
+ verifyNetworkManagementCallIsAbortedWhenInterrupted(
+ TEST_IFACE,
+ () -> {
+ mNetFactory.updateInterface(
+ TEST_IFACE, ipConfiguration, capabilities, successfulListener);
+ triggerOnProvisioningSuccess();
+ });
+
+ assertEquals(successfulListener.expectOnResult(), TEST_IFACE);
+ }
+
+ private void verifyNetworkManagementCallIsAbortedWhenInterrupted(
+ @NonNull final String iface,
+ @NonNull final Runnable interruptingRunnable) throws Exception {
+ createAndVerifyProvisionedInterface(iface);
+ final NetworkCapabilities capabilities = createDefaultFilterCaps();
+ final IpConfiguration ipConfiguration = createStaticIpConfig();
+ final TestNetworkManagementListener failedListener = new TestNetworkManagementListener();
+
+ // An active update request will be aborted on interrupt prior to provisioning completion.
+ mNetFactory.updateInterface(iface, ipConfiguration, capabilities, failedListener);
+ interruptingRunnable.run();
+
+ failedListener.expectOnError();
+ }
+
+ @Test
+ public void testUpdateInterfaceRestartsAgentCorrectly() throws Exception {
+ initEthernetNetworkFactory();
+ createAndVerifyProvisionedInterface(TEST_IFACE);
+ final NetworkCapabilities capabilities = createDefaultFilterCaps();
+ final IpConfiguration ipConfiguration = createStaticIpConfig();
+ final TestNetworkManagementListener listener = new TestNetworkManagementListener();
+
+ mNetFactory.updateInterface(TEST_IFACE, ipConfiguration, capabilities, listener);
+ triggerOnProvisioningSuccess();
+
+ assertEquals(listener.expectOnResult(), TEST_IFACE);
+ verify(mDeps).makeEthernetNetworkAgent(any(), any(),
+ eq(capabilities), any(), any(), any(), any());
+ verifyRestart(ipConfiguration);
+ }
+
+ @Test
+ public void testUpdateInterfaceForNonExistingInterface() throws Exception {
+ initEthernetNetworkFactory();
+ // No interface exists due to not calling createAndVerifyProvisionedInterface(...).
+ final NetworkCapabilities capabilities = createDefaultFilterCaps();
+ final IpConfiguration ipConfiguration = createStaticIpConfig();
+ final TestNetworkManagementListener listener = new TestNetworkManagementListener();
+
+ mNetFactory.updateInterface(TEST_IFACE, ipConfiguration, capabilities, listener);
+
+ verifyNoStopOrStart();
+ listener.expectOnError();
+ }
+
+ @Test
+ public void testUpdateInterfaceWithNullIpConfiguration() throws Exception {
+ initEthernetNetworkFactory();
+ createAndVerifyProvisionedInterface(TEST_IFACE);
+
+ final IpConfiguration initialIpConfig = createStaticIpConfig();
+ mNetFactory.updateInterface(TEST_IFACE, initialIpConfig, null /*capabilities*/,
+ null /*listener*/);
+ triggerOnProvisioningSuccess();
+ verifyRestart(initialIpConfig);
+
+ // TODO: have verifyXyz functions clear invocations.
+ clearInvocations(mDeps);
+ clearInvocations(mIpClient);
+ clearInvocations(mNetworkAgent);
+
+
+ // verify that sending a null ipConfig does not update the current ipConfig.
+ mNetFactory.updateInterface(TEST_IFACE, null /*ipConfig*/, null /*capabilities*/,
+ null /*listener*/);
+ triggerOnProvisioningSuccess();
+ verifyRestart(initialIpConfig);
+ }
+}
diff --git a/tests/unit/java/com/android/server/ethernet/EthernetServiceImplTest.java b/tests/unit/java/com/android/server/ethernet/EthernetServiceImplTest.java
new file mode 100644
index 0000000..b2b9f2c
--- /dev/null
+++ b/tests/unit/java/com/android/server/ethernet/EthernetServiceImplTest.java
@@ -0,0 +1,361 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.ethernet;
+
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
+
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.EthernetNetworkUpdateRequest;
+import android.net.INetworkInterfaceOutcomeReceiver;
+import android.net.IpConfiguration;
+import android.net.NetworkCapabilities;
+import android.os.Build;
+import android.os.Handler;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
+public class EthernetServiceImplTest {
+ private static final String TEST_IFACE = "test123";
+ private static final EthernetNetworkUpdateRequest UPDATE_REQUEST =
+ new EthernetNetworkUpdateRequest.Builder()
+ .setIpConfiguration(new IpConfiguration())
+ .setNetworkCapabilities(new NetworkCapabilities.Builder().build())
+ .build();
+ private static final EthernetNetworkUpdateRequest UPDATE_REQUEST_WITHOUT_CAPABILITIES =
+ new EthernetNetworkUpdateRequest.Builder()
+ .setIpConfiguration(new IpConfiguration())
+ .build();
+ private static final EthernetNetworkUpdateRequest UPDATE_REQUEST_WITHOUT_IP_CONFIG =
+ new EthernetNetworkUpdateRequest.Builder()
+ .setNetworkCapabilities(new NetworkCapabilities.Builder().build())
+ .build();
+ private static final INetworkInterfaceOutcomeReceiver NULL_LISTENER = null;
+ private EthernetServiceImpl mEthernetServiceImpl;
+ private Context mContext;
+ private Handler mHandler;
+ private EthernetTracker mEthernetTracker;
+ private PackageManager mPackageManager;
+
+ @Before
+ public void setup() {
+ mContext = mock(Context.class);
+ mHandler = mock(Handler.class);
+ mEthernetTracker = mock(EthernetTracker.class);
+ mPackageManager = mock(PackageManager.class);
+ doReturn(mPackageManager).when(mContext).getPackageManager();
+ mEthernetServiceImpl = new EthernetServiceImpl(mContext, mHandler, mEthernetTracker);
+ mEthernetServiceImpl.mStarted.set(true);
+ toggleAutomotiveFeature(true);
+ shouldTrackIface(TEST_IFACE, true);
+ }
+
+ private void toggleAutomotiveFeature(final boolean isEnabled) {
+ doReturn(isEnabled)
+ .when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+ }
+
+ private void shouldTrackIface(@NonNull final String iface, final boolean shouldTrack) {
+ doReturn(shouldTrack).when(mEthernetTracker).isTrackingInterface(iface);
+ }
+
+ @Test
+ public void testSetConfigurationRejectsWhenEthNotStarted() {
+ mEthernetServiceImpl.mStarted.set(false);
+ assertThrows(IllegalStateException.class, () -> {
+ mEthernetServiceImpl.setConfiguration("" /* iface */, new IpConfiguration());
+ });
+ }
+
+ @Test
+ public void testUpdateConfigurationRejectsWhenEthNotStarted() {
+ mEthernetServiceImpl.mStarted.set(false);
+ assertThrows(IllegalStateException.class, () -> {
+ mEthernetServiceImpl.updateConfiguration(
+ "" /* iface */, UPDATE_REQUEST, null /* listener */);
+ });
+ }
+
+ @Test
+ public void testEnableInterfaceRejectsWhenEthNotStarted() {
+ mEthernetServiceImpl.mStarted.set(false);
+ assertThrows(IllegalStateException.class, () -> {
+ mEthernetServiceImpl.enableInterface("" /* iface */, null /* listener */);
+ });
+ }
+
+ @Test
+ public void testDisableInterfaceRejectsWhenEthNotStarted() {
+ mEthernetServiceImpl.mStarted.set(false);
+ assertThrows(IllegalStateException.class, () -> {
+ mEthernetServiceImpl.disableInterface("" /* iface */, null /* listener */);
+ });
+ }
+
+ @Test
+ public void testUpdateConfigurationRejectsNullIface() {
+ assertThrows(NullPointerException.class, () -> {
+ mEthernetServiceImpl.updateConfiguration(null, UPDATE_REQUEST, NULL_LISTENER);
+ });
+ }
+
+ @Test
+ public void testEnableInterfaceRejectsNullIface() {
+ assertThrows(NullPointerException.class, () -> {
+ mEthernetServiceImpl.enableInterface(null /* iface */, NULL_LISTENER);
+ });
+ }
+
+ @Test
+ public void testDisableInterfaceRejectsNullIface() {
+ assertThrows(NullPointerException.class, () -> {
+ mEthernetServiceImpl.disableInterface(null /* iface */, NULL_LISTENER);
+ });
+ }
+
+ @Test
+ public void testUpdateConfigurationWithCapabilitiesRejectsWithoutAutomotiveFeature() {
+ toggleAutomotiveFeature(false);
+ assertThrows(UnsupportedOperationException.class, () -> {
+ mEthernetServiceImpl.updateConfiguration(TEST_IFACE, UPDATE_REQUEST, NULL_LISTENER);
+ });
+ }
+
+ @Test
+ public void testUpdateConfigurationWithCapabilitiesWithAutomotiveFeature() {
+ toggleAutomotiveFeature(false);
+ mEthernetServiceImpl.updateConfiguration(TEST_IFACE, UPDATE_REQUEST_WITHOUT_CAPABILITIES,
+ NULL_LISTENER);
+ verify(mEthernetTracker).updateConfiguration(eq(TEST_IFACE),
+ eq(UPDATE_REQUEST_WITHOUT_CAPABILITIES.getIpConfiguration()),
+ eq(UPDATE_REQUEST_WITHOUT_CAPABILITIES.getNetworkCapabilities()), isNull());
+ }
+
+ private void denyManageEthPermission() {
+ doThrow(new SecurityException("")).when(mContext)
+ .enforceCallingOrSelfPermission(
+ eq(Manifest.permission.MANAGE_ETHERNET_NETWORKS), anyString());
+ }
+
+ private void denyManageTestNetworksPermission() {
+ doThrow(new SecurityException("")).when(mContext)
+ .enforceCallingOrSelfPermission(
+ eq(Manifest.permission.MANAGE_TEST_NETWORKS), anyString());
+ }
+
+ @Test
+ public void testUpdateConfigurationRejectsWithoutManageEthPermission() {
+ denyManageEthPermission();
+ assertThrows(SecurityException.class, () -> {
+ mEthernetServiceImpl.updateConfiguration(TEST_IFACE, UPDATE_REQUEST, NULL_LISTENER);
+ });
+ }
+
+ @Test
+ public void testEnableInterfaceRejectsWithoutManageEthPermission() {
+ denyManageEthPermission();
+ assertThrows(SecurityException.class, () -> {
+ mEthernetServiceImpl.enableInterface(TEST_IFACE, NULL_LISTENER);
+ });
+ }
+
+ @Test
+ public void testDisableInterfaceRejectsWithoutManageEthPermission() {
+ denyManageEthPermission();
+ assertThrows(SecurityException.class, () -> {
+ mEthernetServiceImpl.disableInterface(TEST_IFACE, NULL_LISTENER);
+ });
+ }
+
+ private void enableTestInterface() {
+ when(mEthernetTracker.isValidTestInterface(eq(TEST_IFACE))).thenReturn(true);
+ }
+
+ @Test
+ public void testUpdateConfigurationRejectsTestRequestWithoutTestPermission() {
+ enableTestInterface();
+ denyManageTestNetworksPermission();
+ assertThrows(SecurityException.class, () -> {
+ mEthernetServiceImpl.updateConfiguration(TEST_IFACE, UPDATE_REQUEST, NULL_LISTENER);
+ });
+ }
+
+ @Test
+ public void testEnableInterfaceRejectsTestRequestWithoutTestPermission() {
+ enableTestInterface();
+ denyManageTestNetworksPermission();
+ assertThrows(SecurityException.class, () -> {
+ mEthernetServiceImpl.enableInterface(TEST_IFACE, NULL_LISTENER);
+ });
+ }
+
+ @Test
+ public void testDisableInterfaceRejectsTestRequestWithoutTestPermission() {
+ enableTestInterface();
+ denyManageTestNetworksPermission();
+ assertThrows(SecurityException.class, () -> {
+ mEthernetServiceImpl.disableInterface(TEST_IFACE, NULL_LISTENER);
+ });
+ }
+
+ @Test
+ public void testUpdateConfiguration() {
+ mEthernetServiceImpl.updateConfiguration(TEST_IFACE, UPDATE_REQUEST, NULL_LISTENER);
+ verify(mEthernetTracker).updateConfiguration(
+ eq(TEST_IFACE),
+ eq(UPDATE_REQUEST.getIpConfiguration()),
+ eq(UPDATE_REQUEST.getNetworkCapabilities()), eq(NULL_LISTENER));
+ }
+
+ @Test
+ public void testEnableInterface() {
+ mEthernetServiceImpl.enableInterface(TEST_IFACE, NULL_LISTENER);
+ verify(mEthernetTracker).enableInterface(eq(TEST_IFACE), eq(NULL_LISTENER));
+ }
+
+ @Test
+ public void testDisableInterface() {
+ mEthernetServiceImpl.disableInterface(TEST_IFACE, NULL_LISTENER);
+ verify(mEthernetTracker).disableInterface(eq(TEST_IFACE), eq(NULL_LISTENER));
+ }
+
+ @Test
+ public void testUpdateConfigurationAcceptsTestRequestWithNullCapabilities() {
+ enableTestInterface();
+ final EthernetNetworkUpdateRequest request =
+ new EthernetNetworkUpdateRequest
+ .Builder()
+ .setIpConfiguration(new IpConfiguration()).build();
+ mEthernetServiceImpl.updateConfiguration(TEST_IFACE, request, NULL_LISTENER);
+ verify(mEthernetTracker).updateConfiguration(eq(TEST_IFACE),
+ eq(request.getIpConfiguration()),
+ eq(request.getNetworkCapabilities()), isNull());
+ }
+
+ @Test
+ public void testUpdateConfigurationAcceptsRequestWithNullIpConfiguration() {
+ mEthernetServiceImpl.updateConfiguration(TEST_IFACE, UPDATE_REQUEST_WITHOUT_IP_CONFIG,
+ NULL_LISTENER);
+ verify(mEthernetTracker).updateConfiguration(eq(TEST_IFACE),
+ eq(UPDATE_REQUEST_WITHOUT_IP_CONFIG.getIpConfiguration()),
+ eq(UPDATE_REQUEST_WITHOUT_IP_CONFIG.getNetworkCapabilities()), isNull());
+ }
+
+ @Test
+ public void testUpdateConfigurationRejectsInvalidTestRequest() {
+ enableTestInterface();
+ assertThrows(IllegalArgumentException.class, () -> {
+ mEthernetServiceImpl.updateConfiguration(TEST_IFACE, UPDATE_REQUEST, NULL_LISTENER);
+ });
+ }
+
+ private EthernetNetworkUpdateRequest createTestNetworkUpdateRequest() {
+ final NetworkCapabilities nc = new NetworkCapabilities
+ .Builder(UPDATE_REQUEST.getNetworkCapabilities())
+ .addTransportType(TRANSPORT_TEST).build();
+
+ return new EthernetNetworkUpdateRequest
+ .Builder(UPDATE_REQUEST)
+ .setNetworkCapabilities(nc).build();
+ }
+
+ @Test
+ public void testUpdateConfigurationForTestRequestDoesNotRequireAutoOrEthernetPermission() {
+ enableTestInterface();
+ toggleAutomotiveFeature(false);
+ denyManageEthPermission();
+ final EthernetNetworkUpdateRequest request = createTestNetworkUpdateRequest();
+
+ mEthernetServiceImpl.updateConfiguration(TEST_IFACE, request, NULL_LISTENER);
+ verify(mEthernetTracker).updateConfiguration(
+ eq(TEST_IFACE),
+ eq(request.getIpConfiguration()),
+ eq(request.getNetworkCapabilities()), eq(NULL_LISTENER));
+ }
+
+ @Test
+ public void testEnableInterfaceForTestRequestDoesNotRequireNetPermission() {
+ enableTestInterface();
+ toggleAutomotiveFeature(false);
+ denyManageEthPermission();
+
+ mEthernetServiceImpl.enableInterface(TEST_IFACE, NULL_LISTENER);
+ verify(mEthernetTracker).enableInterface(eq(TEST_IFACE), eq(NULL_LISTENER));
+ }
+
+ @Test
+ public void testDisableInterfaceForTestRequestDoesNotRequireAutoOrNetPermission() {
+ enableTestInterface();
+ toggleAutomotiveFeature(false);
+ denyManageEthPermission();
+
+ mEthernetServiceImpl.disableInterface(TEST_IFACE, NULL_LISTENER);
+ verify(mEthernetTracker).disableInterface(eq(TEST_IFACE), eq(NULL_LISTENER));
+ }
+
+ private void denyPermissions(String... permissions) {
+ for (String permission: permissions) {
+ doReturn(PackageManager.PERMISSION_DENIED).when(mContext)
+ .checkCallingOrSelfPermission(eq(permission));
+ }
+ }
+
+ @Test
+ public void testSetEthernetEnabled() {
+ denyPermissions(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
+ mEthernetServiceImpl.setEthernetEnabled(true);
+ verify(mEthernetTracker).setEthernetEnabled(true);
+ reset(mEthernetTracker);
+
+ denyPermissions(Manifest.permission.NETWORK_STACK);
+ mEthernetServiceImpl.setEthernetEnabled(false);
+ verify(mEthernetTracker).setEthernetEnabled(false);
+ reset(mEthernetTracker);
+
+ denyPermissions(Manifest.permission.NETWORK_SETTINGS);
+ try {
+ mEthernetServiceImpl.setEthernetEnabled(true);
+ fail("Should get SecurityException");
+ } catch (SecurityException e) { }
+ verify(mEthernetTracker, never()).setEthernetEnabled(false);
+ }
+}
diff --git a/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java b/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java
new file mode 100644
index 0000000..38094ae
--- /dev/null
+++ b/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java
@@ -0,0 +1,518 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.ethernet;
+
+import static android.net.TestNetworkManager.TEST_TAP_PREFIX;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.EthernetManager;
+import android.net.IEthernetServiceListener;
+import android.net.INetd;
+import android.net.INetworkInterfaceOutcomeReceiver;
+import android.net.InetAddresses;
+import android.net.InterfaceConfigurationParcel;
+import android.net.IpConfiguration;
+import android.net.IpConfiguration.IpAssignment;
+import android.net.IpConfiguration.ProxySettings;
+import android.net.LinkAddress;
+import android.net.NetworkCapabilities;
+import android.net.StaticIpConfiguration;
+import android.os.Build;
+import android.os.HandlerThread;
+import android.os.RemoteException;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+import com.android.testutils.HandlerUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+@SmallTest
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
+public class EthernetTrackerTest {
+ private static final String TEST_IFACE = "test123";
+ private static final int TIMEOUT_MS = 1_000;
+ private static final String THREAD_NAME = "EthernetServiceThread";
+ private static final INetworkInterfaceOutcomeReceiver NULL_LISTENER = null;
+ private EthernetTracker tracker;
+ private HandlerThread mHandlerThread;
+ @Mock private Context mContext;
+ @Mock private EthernetNetworkFactory mFactory;
+ @Mock private INetd mNetd;
+ @Mock private EthernetTracker.Dependencies mDeps;
+
+ @Before
+ public void setUp() throws RemoteException {
+ MockitoAnnotations.initMocks(this);
+ initMockResources();
+ when(mFactory.updateInterfaceLinkState(anyString(), anyBoolean(), any())).thenReturn(false);
+ when(mNetd.interfaceGetList()).thenReturn(new String[0]);
+ mHandlerThread = new HandlerThread(THREAD_NAME);
+ mHandlerThread.start();
+ tracker = new EthernetTracker(mContext, mHandlerThread.getThreadHandler(), mFactory, mNetd,
+ mDeps);
+ }
+
+ @After
+ public void cleanUp() {
+ mHandlerThread.quitSafely();
+ }
+
+ private void initMockResources() {
+ when(mDeps.getInterfaceRegexFromResource(eq(mContext))).thenReturn("");
+ when(mDeps.getInterfaceConfigFromResource(eq(mContext))).thenReturn(new String[0]);
+ }
+
+ private void waitForIdle() {
+ HandlerUtils.waitForIdle(mHandlerThread, TIMEOUT_MS);
+ }
+
+ /**
+ * Test: Creation of various valid static IP configurations
+ */
+ @Test
+ public void createStaticIpConfiguration() {
+ // Empty gives default StaticIPConfiguration object
+ assertStaticConfiguration(new StaticIpConfiguration(), "");
+
+ // Setting only the IP address properly cascades and assumes defaults
+ assertStaticConfiguration(new StaticIpConfiguration.Builder()
+ .setIpAddress(new LinkAddress("192.0.2.10/24")).build(), "ip=192.0.2.10/24");
+
+ final ArrayList<InetAddress> dnsAddresses = new ArrayList<>();
+ dnsAddresses.add(InetAddresses.parseNumericAddress("4.4.4.4"));
+ dnsAddresses.add(InetAddresses.parseNumericAddress("8.8.8.8"));
+ // Setting other fields properly cascades them
+ assertStaticConfiguration(new StaticIpConfiguration.Builder()
+ .setIpAddress(new LinkAddress("192.0.2.10/24"))
+ .setDnsServers(dnsAddresses)
+ .setGateway(InetAddresses.parseNumericAddress("192.0.2.1"))
+ .setDomains("android").build(),
+ "ip=192.0.2.10/24 dns=4.4.4.4,8.8.8.8 gateway=192.0.2.1 domains=android");
+
+ // Verify order doesn't matter
+ assertStaticConfiguration(new StaticIpConfiguration.Builder()
+ .setIpAddress(new LinkAddress("192.0.2.10/24"))
+ .setDnsServers(dnsAddresses)
+ .setGateway(InetAddresses.parseNumericAddress("192.0.2.1"))
+ .setDomains("android").build(),
+ "domains=android ip=192.0.2.10/24 gateway=192.0.2.1 dns=4.4.4.4,8.8.8.8 ");
+ }
+
+ /**
+ * Test: Attempt creation of various bad static IP configurations
+ */
+ @Test
+ public void createStaticIpConfiguration_Bad() {
+ assertStaticConfigurationFails("ip=192.0.2.1/24 gateway= blah=20.20.20.20"); // Unknown key
+ assertStaticConfigurationFails("ip=192.0.2.1"); // mask is missing
+ assertStaticConfigurationFails("ip=a.b.c"); // not a valid ip address
+ assertStaticConfigurationFails("dns=4.4.4.4,1.2.3.A"); // not valid ip address in dns
+ assertStaticConfigurationFails("="); // Key and value is empty
+ assertStaticConfigurationFails("ip="); // Value is empty
+ assertStaticConfigurationFails("ip=192.0.2.1/24 gateway="); // Gateway is empty
+ }
+
+ private void assertStaticConfigurationFails(String config) {
+ try {
+ EthernetTracker.parseStaticIpConfiguration(config);
+ fail("Expected to fail: " + config);
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ }
+
+ private void assertStaticConfiguration(StaticIpConfiguration expectedStaticIpConfig,
+ String configAsString) {
+ final IpConfiguration expectedIpConfiguration = new IpConfiguration();
+ expectedIpConfiguration.setIpAssignment(IpAssignment.STATIC);
+ expectedIpConfiguration.setProxySettings(ProxySettings.NONE);
+ expectedIpConfiguration.setStaticIpConfiguration(expectedStaticIpConfig);
+
+ assertEquals(expectedIpConfiguration,
+ EthernetTracker.parseStaticIpConfiguration(configAsString));
+ }
+
+ private NetworkCapabilities.Builder makeEthernetCapabilitiesBuilder(boolean clearAll) {
+ final NetworkCapabilities.Builder builder =
+ clearAll ? NetworkCapabilities.Builder.withoutDefaultCapabilities()
+ : new NetworkCapabilities.Builder();
+ return builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED);
+ }
+
+ /**
+ * Test: Attempt to create a capabilties with various valid sets of capabilities/transports
+ */
+ @Test
+ public void createNetworkCapabilities() {
+
+ // Particularly common expected results
+ NetworkCapabilities defaultEthernetCleared =
+ makeEthernetCapabilitiesBuilder(true /* clearAll */)
+ .setLinkUpstreamBandwidthKbps(100000)
+ .setLinkDownstreamBandwidthKbps(100000)
+ .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
+ .build();
+
+ NetworkCapabilities ethernetClearedWithCommonCaps =
+ makeEthernetCapabilitiesBuilder(true /* clearAll */)
+ .setLinkUpstreamBandwidthKbps(100000)
+ .setLinkDownstreamBandwidthKbps(100000)
+ .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
+ .addCapability(12)
+ .addCapability(13)
+ .addCapability(14)
+ .addCapability(15)
+ .build();
+
+ // Empty capabilities and transports lists with a "please clear defaults" should
+ // yield an empty capabilities set with TRANPORT_ETHERNET
+ assertParsedNetworkCapabilities(defaultEthernetCleared, true, "", "");
+
+ // Empty capabilities and transports without the clear defaults flag should return the
+ // default capabilities set with TRANSPORT_ETHERNET
+ assertParsedNetworkCapabilities(
+ makeEthernetCapabilitiesBuilder(false /* clearAll */)
+ .setLinkUpstreamBandwidthKbps(100000)
+ .setLinkDownstreamBandwidthKbps(100000)
+ .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
+ .build(),
+ false, "", "");
+
+ // A list of capabilities without the clear defaults flag should return the default
+ // capabilities, mixed with the desired capabilities, and TRANSPORT_ETHERNET
+ assertParsedNetworkCapabilities(
+ makeEthernetCapabilitiesBuilder(false /* clearAll */)
+ .setLinkUpstreamBandwidthKbps(100000)
+ .setLinkDownstreamBandwidthKbps(100000)
+ .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
+ .addCapability(11)
+ .addCapability(12)
+ .build(),
+ false, "11,12", "");
+
+ // Adding a list of capabilities with a clear defaults will leave exactly those capabilities
+ // with a default TRANSPORT_ETHERNET since no overrides are specified
+ assertParsedNetworkCapabilities(ethernetClearedWithCommonCaps, true, "12,13,14,15", "");
+
+ // Adding any invalid capabilities to the list will cause them to be ignored
+ assertParsedNetworkCapabilities(ethernetClearedWithCommonCaps, true, "12,13,14,15,65,73", "");
+ assertParsedNetworkCapabilities(ethernetClearedWithCommonCaps, true, "12,13,14,15,abcdefg", "");
+
+ // Adding a valid override transport will remove the default TRANSPORT_ETHERNET transport
+ // and apply only the override to the capabiltities object
+ assertParsedNetworkCapabilities(
+ makeEthernetCapabilitiesBuilder(true /* clearAll */)
+ .setLinkUpstreamBandwidthKbps(100000)
+ .setLinkDownstreamBandwidthKbps(100000)
+ .addTransportType(0)
+ .build(),
+ true, "", "0");
+ assertParsedNetworkCapabilities(
+ makeEthernetCapabilitiesBuilder(true /* clearAll */)
+ .setLinkUpstreamBandwidthKbps(100000)
+ .setLinkDownstreamBandwidthKbps(100000)
+ .addTransportType(1)
+ .build(),
+ true, "", "1");
+ assertParsedNetworkCapabilities(
+ makeEthernetCapabilitiesBuilder(true /* clearAll */)
+ .setLinkUpstreamBandwidthKbps(100000)
+ .setLinkDownstreamBandwidthKbps(100000)
+ .addTransportType(2)
+ .build(),
+ true, "", "2");
+ assertParsedNetworkCapabilities(
+ makeEthernetCapabilitiesBuilder(true /* clearAll */)
+ .setLinkUpstreamBandwidthKbps(100000)
+ .setLinkDownstreamBandwidthKbps(100000)
+ .addTransportType(3)
+ .build(),
+ true, "", "3");
+
+ // "4" is TRANSPORT_VPN, which is unsupported. Should default back to TRANPORT_ETHERNET
+ assertParsedNetworkCapabilities(defaultEthernetCleared, true, "", "4");
+
+ // "5" is TRANSPORT_WIFI_AWARE, which is currently supported due to no legacy TYPE_NONE
+ // conversion. When that becomes available, this test must be updated
+ assertParsedNetworkCapabilities(defaultEthernetCleared, true, "", "5");
+
+ // "6" is TRANSPORT_LOWPAN, which is currently supported due to no legacy TYPE_NONE
+ // conversion. When that becomes available, this test must be updated
+ assertParsedNetworkCapabilities(defaultEthernetCleared, true, "", "6");
+
+ // Adding an invalid override transport will leave the transport as TRANSPORT_ETHERNET
+ assertParsedNetworkCapabilities(defaultEthernetCleared,true, "", "100");
+ assertParsedNetworkCapabilities(defaultEthernetCleared, true, "", "abcdefg");
+
+ // Ensure the adding of both capabilities and transports work
+ assertParsedNetworkCapabilities(
+ makeEthernetCapabilitiesBuilder(true /* clearAll */)
+ .setLinkUpstreamBandwidthKbps(100000)
+ .setLinkDownstreamBandwidthKbps(100000)
+ .addCapability(12)
+ .addCapability(13)
+ .addCapability(14)
+ .addCapability(15)
+ .addTransportType(3)
+ .build(),
+ true, "12,13,14,15", "3");
+
+ // Ensure order does not matter for capability list
+ assertParsedNetworkCapabilities(ethernetClearedWithCommonCaps, true, "13,12,15,14", "");
+ }
+
+ private void assertParsedNetworkCapabilities(NetworkCapabilities expectedNetworkCapabilities,
+ boolean clearCapabilties, String configCapabiltiies,String configTransports) {
+ assertEquals(expectedNetworkCapabilities,
+ EthernetTracker.createNetworkCapabilities(clearCapabilties, configCapabiltiies,
+ configTransports).build());
+ }
+
+ @Test
+ public void testCreateEthernetTrackerConfigReturnsCorrectValue() {
+ final String capabilities = "2";
+ final String ipConfig = "3";
+ final String transport = "4";
+ final String configString = String.join(";", TEST_IFACE, capabilities, ipConfig, transport);
+
+ final EthernetTracker.EthernetTrackerConfig config =
+ EthernetTracker.createEthernetTrackerConfig(configString);
+
+ assertEquals(TEST_IFACE, config.mIface);
+ assertEquals(capabilities, config.mCapabilities);
+ assertEquals(ipConfig, config.mIpConfig);
+ assertEquals(transport, config.mTransport);
+ }
+
+ @Test
+ public void testCreateEthernetTrackerConfigThrowsNpeWithNullInput() {
+ assertThrows(NullPointerException.class,
+ () -> EthernetTracker.createEthernetTrackerConfig(null));
+ }
+
+ @Test
+ public void testUpdateConfiguration() {
+ final NetworkCapabilities capabilities = new NetworkCapabilities.Builder().build();
+ final LinkAddress linkAddr = new LinkAddress("192.0.2.2/25");
+ final StaticIpConfiguration staticIpConfig =
+ new StaticIpConfiguration.Builder().setIpAddress(linkAddr).build();
+ final IpConfiguration ipConfig =
+ new IpConfiguration.Builder().setStaticIpConfiguration(staticIpConfig).build();
+ final INetworkInterfaceOutcomeReceiver listener = null;
+
+ tracker.updateConfiguration(TEST_IFACE, ipConfig, capabilities, listener);
+ waitForIdle();
+
+ verify(mFactory).updateInterface(
+ eq(TEST_IFACE), eq(ipConfig), eq(capabilities), eq(listener));
+ }
+
+ @Test
+ public void testEnableInterfaceCorrectlyCallsFactory() {
+ tracker.enableInterface(TEST_IFACE, NULL_LISTENER);
+ waitForIdle();
+
+ verify(mFactory).updateInterfaceLinkState(eq(TEST_IFACE), eq(true /* up */),
+ eq(NULL_LISTENER));
+ }
+
+ @Test
+ public void testDisableInterfaceCorrectlyCallsFactory() {
+ tracker.disableInterface(TEST_IFACE, NULL_LISTENER);
+ waitForIdle();
+
+ verify(mFactory).updateInterfaceLinkState(eq(TEST_IFACE), eq(false /* up */),
+ eq(NULL_LISTENER));
+ }
+
+ @Test
+ public void testIsValidTestInterfaceIsFalseWhenTestInterfacesAreNotIncluded() {
+ final String validIfaceName = TEST_TAP_PREFIX + "123";
+ tracker.setIncludeTestInterfaces(false);
+ waitForIdle();
+
+ final boolean isValidTestInterface = tracker.isValidTestInterface(validIfaceName);
+
+ assertFalse(isValidTestInterface);
+ }
+
+ @Test
+ public void testIsValidTestInterfaceIsFalseWhenTestInterfaceNameIsInvalid() {
+ final String invalidIfaceName = "123" + TEST_TAP_PREFIX;
+ tracker.setIncludeTestInterfaces(true);
+ waitForIdle();
+
+ final boolean isValidTestInterface = tracker.isValidTestInterface(invalidIfaceName);
+
+ assertFalse(isValidTestInterface);
+ }
+
+ @Test
+ public void testIsValidTestInterfaceIsTrueWhenTestInterfacesIncludedAndValidName() {
+ final String validIfaceName = TEST_TAP_PREFIX + "123";
+ tracker.setIncludeTestInterfaces(true);
+ waitForIdle();
+
+ final boolean isValidTestInterface = tracker.isValidTestInterface(validIfaceName);
+
+ assertTrue(isValidTestInterface);
+ }
+
+ public static class EthernetStateListener extends IEthernetServiceListener.Stub {
+ @Override
+ public void onEthernetStateChanged(int state) { }
+
+ @Override
+ public void onInterfaceStateChanged(String iface, int state, int role,
+ IpConfiguration configuration) { }
+ }
+
+ private InterfaceConfigurationParcel createMockedIfaceParcel(final String ifname,
+ final String hwAddr) {
+ final InterfaceConfigurationParcel ifaceParcel = new InterfaceConfigurationParcel();
+ ifaceParcel.ifName = ifname;
+ ifaceParcel.hwAddr = hwAddr;
+ ifaceParcel.flags = new String[] {INetd.IF_STATE_UP};
+ return ifaceParcel;
+ }
+
+ @Test
+ public void testListenEthernetStateChange() throws Exception {
+ tracker.setIncludeTestInterfaces(true);
+ waitForIdle();
+
+ final String testIface = "testtap123";
+ final String testHwAddr = "11:22:33:44:55:66";
+ final InterfaceConfigurationParcel ifaceParcel = createMockedIfaceParcel(testIface,
+ testHwAddr);
+ when(mNetd.interfaceGetList()).thenReturn(new String[] {testIface});
+ when(mNetd.interfaceGetCfg(eq(testIface))).thenReturn(ifaceParcel);
+ doReturn(new String[] {testIface}).when(mFactory).getAvailableInterfaces(anyBoolean());
+
+ final AtomicBoolean ifaceUp = new AtomicBoolean(true);
+ doAnswer(inv -> ifaceUp.get()).when(mFactory).hasInterface(testIface);
+ doAnswer(inv ->
+ ifaceUp.get() ? EthernetManager.STATE_LINK_UP : EthernetManager.STATE_ABSENT)
+ .when(mFactory).getInterfaceState(testIface);
+ doAnswer(inv -> {
+ ifaceUp.set(true);
+ return null;
+ }).when(mFactory).addInterface(eq(testIface), eq(testHwAddr), any(), any());
+ doAnswer(inv -> {
+ ifaceUp.set(false);
+ return null;
+ }).when(mFactory).removeInterface(testIface);
+
+ final EthernetStateListener listener = spy(new EthernetStateListener());
+ tracker.addListener(listener, true /* canUseRestrictedNetworks */);
+ // Check default state.
+ waitForIdle();
+ verify(listener).onInterfaceStateChanged(eq(testIface), eq(EthernetManager.STATE_LINK_UP),
+ anyInt(), any());
+ verify(listener).onEthernetStateChanged(eq(EthernetManager.ETHERNET_STATE_ENABLED));
+ reset(listener);
+
+ tracker.setEthernetEnabled(false);
+ waitForIdle();
+ verify(mFactory).removeInterface(eq(testIface));
+ verify(listener).onEthernetStateChanged(eq(EthernetManager.ETHERNET_STATE_DISABLED));
+ verify(listener).onInterfaceStateChanged(eq(testIface), eq(EthernetManager.STATE_ABSENT),
+ anyInt(), any());
+ reset(listener);
+
+ tracker.setEthernetEnabled(true);
+ waitForIdle();
+ verify(mFactory).addInterface(eq(testIface), eq(testHwAddr), any(), any());
+ verify(listener).onEthernetStateChanged(eq(EthernetManager.ETHERNET_STATE_ENABLED));
+ verify(listener).onInterfaceStateChanged(eq(testIface), eq(EthernetManager.STATE_LINK_UP),
+ anyInt(), any());
+ }
+
+ @Test
+ public void testListenEthernetStateChange_unsolicitedEventListener() throws Exception {
+ when(mNetd.interfaceGetList()).thenReturn(new String[] {});
+ doReturn(new String[] {}).when(mFactory).getAvailableInterfaces(anyBoolean());
+
+ tracker.setIncludeTestInterfaces(true);
+ tracker.start();
+
+ final ArgumentCaptor<EthernetTracker.InterfaceObserver> captor =
+ ArgumentCaptor.forClass(EthernetTracker.InterfaceObserver.class);
+ verify(mNetd, timeout(TIMEOUT_MS)).registerUnsolicitedEventListener(captor.capture());
+ final EthernetTracker.InterfaceObserver observer = captor.getValue();
+
+ tracker.setEthernetEnabled(false);
+ waitForIdle();
+ reset(mFactory);
+ reset(mNetd);
+
+ final String testIface = "testtap1";
+ observer.onInterfaceAdded(testIface);
+ verify(mFactory, never()).addInterface(eq(testIface), anyString(), any(), any());
+ observer.onInterfaceRemoved(testIface);
+ verify(mFactory, never()).removeInterface(eq(testIface));
+
+ final String testHwAddr = "11:22:33:44:55:66";
+ final InterfaceConfigurationParcel testIfaceParce =
+ createMockedIfaceParcel(testIface, testHwAddr);
+ when(mNetd.interfaceGetList()).thenReturn(new String[] {testIface});
+ when(mNetd.interfaceGetCfg(eq(testIface))).thenReturn(testIfaceParce);
+ doReturn(new String[] {testIface}).when(mFactory).getAvailableInterfaces(anyBoolean());
+ tracker.setEthernetEnabled(true);
+ waitForIdle();
+ reset(mFactory);
+
+ final String testIface2 = "testtap2";
+ observer.onInterfaceRemoved(testIface2);
+ verify(mFactory, timeout(TIMEOUT_MS)).removeInterface(eq(testIface2));
+ }
+}
diff --git a/tests/unit/java/com/android/server/net/BpfInterfaceMapUpdaterTest.java b/tests/unit/java/com/android/server/net/BpfInterfaceMapUpdaterTest.java
index 987b7b7..c6852d1 100644
--- a/tests/unit/java/com/android/server/net/BpfInterfaceMapUpdaterTest.java
+++ b/tests/unit/java/com/android/server/net/BpfInterfaceMapUpdaterTest.java
@@ -24,16 +24,18 @@
import android.content.Context;
import android.net.INetd;
import android.net.MacAddress;
+import android.os.Build;
import android.os.Handler;
import android.os.test.TestLooper;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
import com.android.net.module.util.IBpfMap;
import com.android.net.module.util.InterfaceParams;
import com.android.net.module.util.Struct.U32;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.Before;
import org.junit.Test;
@@ -42,8 +44,9 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-@RunWith(AndroidJUnit4.class)
@SmallTest
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public final class BpfInterfaceMapUpdaterTest {
private static final int TEST_INDEX = 1;
private static final int TEST_INDEX2 = 2;
diff --git a/tests/unit/java/com/android/server/net/IpConfigStoreTest.java b/tests/unit/java/com/android/server/net/IpConfigStoreTest.java
new file mode 100644
index 0000000..4adc999
--- /dev/null
+++ b/tests/unit/java/com/android/server/net/IpConfigStoreTest.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.net.InetAddresses;
+import android.net.IpConfiguration;
+import android.net.IpConfiguration.IpAssignment;
+import android.net.IpConfiguration.ProxySettings;
+import android.net.LinkAddress;
+import android.net.ProxyInfo;
+import android.net.StaticIpConfiguration;
+import android.os.Build;
+import android.os.HandlerThread;
+import android.util.ArrayMap;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+import com.android.testutils.HandlerUtils;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Unit tests for {@link IpConfigStore}
+ */
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
+public class IpConfigStoreTest {
+ private static final int TIMEOUT_MS = 2_000;
+ private static final int KEY_CONFIG = 17;
+ private static final String IFACE_1 = "eth0";
+ private static final String IFACE_2 = "eth1";
+ private static final String IP_ADDR_1 = "192.168.1.10/24";
+ private static final String IP_ADDR_2 = "192.168.1.20/24";
+ private static final String DNS_IP_ADDR_1 = "1.2.3.4";
+ private static final String DNS_IP_ADDR_2 = "5.6.7.8";
+
+ private static final ArrayList<InetAddress> DNS_SERVERS = new ArrayList<>(List.of(
+ InetAddresses.parseNumericAddress(DNS_IP_ADDR_1),
+ InetAddresses.parseNumericAddress(DNS_IP_ADDR_2)));
+ private static final StaticIpConfiguration STATIC_IP_CONFIG_1 =
+ new StaticIpConfiguration.Builder()
+ .setIpAddress(new LinkAddress(IP_ADDR_1))
+ .setDnsServers(DNS_SERVERS)
+ .build();
+ private static final StaticIpConfiguration STATIC_IP_CONFIG_2 =
+ new StaticIpConfiguration.Builder()
+ .setIpAddress(new LinkAddress(IP_ADDR_2))
+ .setDnsServers(DNS_SERVERS)
+ .build();
+ private static final ProxyInfo PROXY_INFO =
+ ProxyInfo.buildDirectProxy("10.10.10.10", 88, Arrays.asList("host1", "host2"));
+
+ @Test
+ public void backwardCompatibility2to3() throws IOException {
+ ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
+ DataOutputStream outputStream = new DataOutputStream(byteStream);
+
+ final IpConfiguration expectedConfig =
+ newIpConfiguration(IpAssignment.DHCP, ProxySettings.NONE, null, null);
+
+ // Emulate writing to old format.
+ writeDhcpConfigV2(outputStream, KEY_CONFIG, expectedConfig);
+
+ InputStream in = new ByteArrayInputStream(byteStream.toByteArray());
+ ArrayMap<String, IpConfiguration> configurations = IpConfigStore.readIpConfigurations(in);
+
+ assertNotNull(configurations);
+ assertEquals(1, configurations.size());
+ IpConfiguration actualConfig = configurations.get(String.valueOf(KEY_CONFIG));
+ assertNotNull(actualConfig);
+ assertEquals(expectedConfig, actualConfig);
+ }
+
+ @Test
+ public void staticIpMultiNetworks() throws Exception {
+ final IpConfiguration expectedConfig1 = newIpConfiguration(IpAssignment.STATIC,
+ ProxySettings.STATIC, STATIC_IP_CONFIG_1, PROXY_INFO);
+ final IpConfiguration expectedConfig2 = newIpConfiguration(IpAssignment.STATIC,
+ ProxySettings.STATIC, STATIC_IP_CONFIG_2, PROXY_INFO);
+
+ final ArrayMap<String, IpConfiguration> expectedNetworks = new ArrayMap<>();
+ expectedNetworks.put(IFACE_1, expectedConfig1);
+ expectedNetworks.put(IFACE_2, expectedConfig2);
+
+ final MockedDelayedDiskWrite writer = new MockedDelayedDiskWrite();
+ final IpConfigStore store = new IpConfigStore(writer);
+ store.writeIpConfigurations("file/path/not/used/", expectedNetworks);
+
+ final InputStream in = new ByteArrayInputStream(writer.mByteStream.toByteArray());
+ final ArrayMap<String, IpConfiguration> actualNetworks =
+ IpConfigStore.readIpConfigurations(in);
+ assertNotNull(actualNetworks);
+ assertEquals(2, actualNetworks.size());
+ assertEquals(expectedNetworks.get(IFACE_1), actualNetworks.get(IFACE_1));
+ assertEquals(expectedNetworks.get(IFACE_2), actualNetworks.get(IFACE_2));
+ }
+
+ @Test
+ public void readIpConfigurationFromFilePath() throws Exception {
+ final HandlerThread testHandlerThread = new HandlerThread("IpConfigStoreTest");
+ final DelayedDiskWrite.Dependencies dependencies =
+ new DelayedDiskWrite.Dependencies() {
+ @Override
+ public HandlerThread makeHandlerThread() {
+ return testHandlerThread;
+ }
+ @Override
+ public void quitHandlerThread(HandlerThread handlerThread) {
+ testHandlerThread.quitSafely();
+ }
+ };
+
+ final IpConfiguration ipConfig = newIpConfiguration(IpAssignment.STATIC,
+ ProxySettings.STATIC, STATIC_IP_CONFIG_1, PROXY_INFO);
+ final ArrayMap<String, IpConfiguration> expectedNetworks = new ArrayMap<>();
+ expectedNetworks.put(IFACE_1, ipConfig);
+
+ // Write IP config to specific file path and read it later.
+ final Context context = InstrumentationRegistry.getContext();
+ final File configFile = new File(context.getFilesDir().getPath(),
+ "IpConfigStoreTest-ipconfig.txt");
+ final DelayedDiskWrite writer = new DelayedDiskWrite(dependencies);
+ final IpConfigStore store = new IpConfigStore(writer);
+ store.writeIpConfigurations(configFile.getPath(), expectedNetworks);
+ HandlerUtils.waitForIdle(testHandlerThread, TIMEOUT_MS);
+
+ // Read IP config from the file path.
+ final ArrayMap<String, IpConfiguration> actualNetworks =
+ IpConfigStore.readIpConfigurations(configFile.getPath());
+ assertNotNull(actualNetworks);
+ assertEquals(1, actualNetworks.size());
+ assertEquals(expectedNetworks.get(IFACE_1), actualNetworks.get(IFACE_1));
+
+ // Return an empty array when reading IP configuration from an non-exist config file.
+ final ArrayMap<String, IpConfiguration> emptyNetworks =
+ IpConfigStore.readIpConfigurations("/dev/null");
+ assertNotNull(emptyNetworks);
+ assertEquals(0, emptyNetworks.size());
+
+ configFile.delete();
+ }
+
+ private IpConfiguration newIpConfiguration(IpAssignment ipAssignment,
+ ProxySettings proxySettings, StaticIpConfiguration staticIpConfig, ProxyInfo info) {
+ final IpConfiguration config = new IpConfiguration();
+ config.setIpAssignment(ipAssignment);
+ config.setProxySettings(proxySettings);
+ config.setStaticIpConfiguration(staticIpConfig);
+ config.setHttpProxy(info);
+ return config;
+ }
+
+ // This is simplified snapshot of code that was used to store values in V2 format (key as int).
+ private static void writeDhcpConfigV2(DataOutputStream out, int configKey,
+ IpConfiguration config) throws IOException {
+ out.writeInt(2); // VERSION 2
+ switch (config.getIpAssignment()) {
+ case DHCP:
+ out.writeUTF("ipAssignment");
+ out.writeUTF(config.getIpAssignment().toString());
+ break;
+ default:
+ fail("Not supported in test environment");
+ }
+
+ out.writeUTF("id");
+ out.writeInt(configKey);
+ out.writeUTF("eos");
+ }
+
+ /** Synchronously writes into given byte steam */
+ private static class MockedDelayedDiskWrite extends DelayedDiskWrite {
+ final ByteArrayOutputStream mByteStream = new ByteArrayOutputStream();
+
+ @Override
+ public void write(String filePath, Writer w) {
+ DataOutputStream outputStream = new DataOutputStream(mByteStream);
+
+ try {
+ w.onWriteCalled(outputStream);
+ } catch (IOException e) {
+ fail();
+ }
+ }
+ }
+}
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java b/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
index 79744b1..5400a00 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
@@ -29,6 +29,7 @@
import static android.net.NetworkStats.UID_ALL;
import static com.android.server.net.NetworkStatsFactory.kernelToTag;
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
@@ -38,7 +39,6 @@
import android.net.NetworkStats;
import android.net.TrafficStats;
import android.net.UnderlyingNetworkInfo;
-import android.os.Build;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -67,7 +67,7 @@
/** Tests for {@link NetworkStatsFactory}. */
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
public class NetworkStatsFactoryTest extends NetworkStatsBaseTest {
private static final String CLAT_PREFIX = "v4-";
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java b/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
index 5f9d1ff..5747e10 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
@@ -32,6 +32,7 @@
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
@@ -64,6 +65,7 @@
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
import java.util.Objects;
/**
@@ -86,11 +88,19 @@
private static NetworkTemplate sTemplateImsi1 = buildTemplateMobileAll(IMSI_1);
private static NetworkTemplate sTemplateImsi2 = buildTemplateMobileAll(IMSI_2);
+ private static final int PID_SYSTEM = 1234;
+ private static final int PID_RED = 1235;
+ private static final int PID_BLUE = 1236;
+
private static final int UID_RED = UserHandle.PER_USER_RANGE + 1;
private static final int UID_BLUE = UserHandle.PER_USER_RANGE + 2;
private static final int UID_GREEN = UserHandle.PER_USER_RANGE + 3;
private static final int UID_ANOTHER_USER = 2 * UserHandle.PER_USER_RANGE + 4;
+ private static final String PACKAGE_SYSTEM = "android";
+ private static final String PACKAGE_RED = "RED";
+ private static final String PACKAGE_BLUE = "BLUE";
+
private static final long WAIT_TIMEOUT_MS = 500;
private static final long THRESHOLD_BYTES = 2 * MB_IN_BYTES;
private static final long BASE_BYTES = 7 * MB_IN_BYTES;
@@ -131,14 +141,15 @@
DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, thresholdTooLowBytes);
final DataUsageRequest requestByApp = mStatsObservers.register(mContext, inputRequest,
- mUsageCallback, UID_RED, NetworkStatsAccess.Level.DEVICE);
+ mUsageCallback, PID_RED , UID_RED, PACKAGE_RED, NetworkStatsAccess.Level.DEVICE);
assertTrue(requestByApp.requestId > 0);
assertTrue(Objects.equals(sTemplateWifi, requestByApp.template));
assertEquals(thresholdTooLowBytes, requestByApp.thresholdInBytes);
// Verify the threshold requested by system uid won't be overridden.
final DataUsageRequest requestBySystem = mStatsObservers.register(mContext, inputRequest,
- mUsageCallback, Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+ mUsageCallback, PID_SYSTEM, Process.SYSTEM_UID, PACKAGE_SYSTEM,
+ NetworkStatsAccess.Level.DEVICE);
assertTrue(requestBySystem.requestId > 0);
assertTrue(Objects.equals(sTemplateWifi, requestBySystem.template));
assertEquals(1, requestBySystem.thresholdInBytes);
@@ -151,7 +162,7 @@
DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, highThresholdBytes);
DataUsageRequest request = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
- Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+ PID_SYSTEM, Process.SYSTEM_UID, PACKAGE_SYSTEM, NetworkStatsAccess.Level.DEVICE);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateWifi, request.template));
assertEquals(highThresholdBytes, request.thresholdInBytes);
@@ -163,19 +174,64 @@
DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, THRESHOLD_BYTES);
DataUsageRequest request1 = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
- Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+ PID_SYSTEM, Process.SYSTEM_UID, PACKAGE_SYSTEM, NetworkStatsAccess.Level.DEVICE);
assertTrue(request1.requestId > 0);
assertTrue(Objects.equals(sTemplateWifi, request1.template));
assertEquals(THRESHOLD_BYTES, request1.thresholdInBytes);
DataUsageRequest request2 = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
- Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+ PID_SYSTEM, Process.SYSTEM_UID, PACKAGE_SYSTEM, NetworkStatsAccess.Level.DEVICE);
assertTrue(request2.requestId > request1.requestId);
assertTrue(Objects.equals(sTemplateWifi, request2.template));
assertEquals(THRESHOLD_BYTES, request2.thresholdInBytes);
}
@Test
+ public void testRegister_limit() throws Exception {
+ final DataUsageRequest inputRequest = new DataUsageRequest(
+ DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, THRESHOLD_BYTES);
+
+ // Register maximum requests for red.
+ final ArrayList<DataUsageRequest> redRequests = new ArrayList<>();
+ for (int i = 0; i < NetworkStatsObservers.MAX_REQUESTS_PER_UID; i++) {
+ final DataUsageRequest returnedRequest =
+ mStatsObservers.register(mContext, inputRequest, mUsageCallback,
+ PID_RED, UID_RED, PACKAGE_RED, NetworkStatsAccess.Level.DEVICE);
+ redRequests.add(returnedRequest);
+ assertTrue(returnedRequest.requestId > 0);
+ }
+
+ // Verify request exceeds the limit throws.
+ assertThrows(IllegalStateException.class, () ->
+ mStatsObservers.register(mContext, inputRequest, mUsageCallback,
+ PID_RED, UID_RED, PACKAGE_RED, NetworkStatsAccess.Level.DEVICE));
+
+ // Verify another uid is not affected.
+ final ArrayList<DataUsageRequest> blueRequests = new ArrayList<>();
+ for (int i = 0; i < NetworkStatsObservers.MAX_REQUESTS_PER_UID; i++) {
+ final DataUsageRequest returnedRequest =
+ mStatsObservers.register(mContext, inputRequest, mUsageCallback,
+ PID_BLUE, UID_BLUE, PACKAGE_BLUE, NetworkStatsAccess.Level.DEVICE);
+ blueRequests.add(returnedRequest);
+ assertTrue(returnedRequest.requestId > 0);
+ }
+
+ // Again, verify request exceeds the limit throws for the 2nd uid.
+ assertThrows(IllegalStateException.class, () ->
+ mStatsObservers.register(mContext, inputRequest, mUsageCallback,
+ PID_RED, UID_RED, PACKAGE_RED, NetworkStatsAccess.Level.DEVICE));
+
+ // Unregister all registered requests. Note that exceptions cannot be tested since
+ // unregister is handled in the handler thread.
+ for (final DataUsageRequest request : redRequests) {
+ mStatsObservers.unregister(request, UID_RED);
+ }
+ for (final DataUsageRequest request : blueRequests) {
+ mStatsObservers.unregister(request, UID_BLUE);
+ }
+ }
+
+ @Test
public void testUnregister_unknownRequest_noop() throws Exception {
DataUsageRequest unknownRequest = new DataUsageRequest(
123456 /* id */, sTemplateWifi, THRESHOLD_BYTES);
@@ -189,7 +245,7 @@
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
DataUsageRequest request = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
- Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+ PID_SYSTEM, Process.SYSTEM_UID, PACKAGE_SYSTEM, NetworkStatsAccess.Level.DEVICE);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
@@ -209,7 +265,7 @@
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
DataUsageRequest request = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
- UID_RED, NetworkStatsAccess.Level.DEVICE);
+ PID_RED, UID_RED, PACKAGE_RED, NetworkStatsAccess.Level.DEVICE);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
@@ -218,8 +274,12 @@
mStatsObservers.unregister(request, UID_BLUE);
waitForObserverToIdle();
-
Mockito.verifyZeroInteractions(mUsageCallbackBinder);
+
+ // Verify that system uid can unregister for other uids.
+ mStatsObservers.unregister(request, Process.SYSTEM_UID);
+ waitForObserverToIdle();
+ mUsageCallback.expectOnCallbackReleased(request);
}
private NetworkIdentitySet makeTestIdentSet() {
@@ -237,7 +297,7 @@
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
DataUsageRequest request = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
- Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+ PID_SYSTEM, Process.SYSTEM_UID, PACKAGE_SYSTEM, NetworkStatsAccess.Level.DEVICE);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
@@ -261,7 +321,7 @@
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
DataUsageRequest request = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
- Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+ PID_SYSTEM, Process.SYSTEM_UID, PACKAGE_SYSTEM, NetworkStatsAccess.Level.DEVICE);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
@@ -291,7 +351,7 @@
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
DataUsageRequest request = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
- Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+ PID_SYSTEM, Process.SYSTEM_UID, PACKAGE_SYSTEM, NetworkStatsAccess.Level.DEVICE);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
@@ -322,7 +382,7 @@
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
DataUsageRequest request = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
- UID_RED, NetworkStatsAccess.Level.DEFAULT);
+ PID_RED, UID_RED, PACKAGE_SYSTEM , NetworkStatsAccess.Level.DEFAULT);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
@@ -355,7 +415,7 @@
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
DataUsageRequest request = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
- UID_BLUE, NetworkStatsAccess.Level.DEFAULT);
+ PID_BLUE, UID_BLUE, PACKAGE_BLUE, NetworkStatsAccess.Level.DEFAULT);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
@@ -387,7 +447,7 @@
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
DataUsageRequest request = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
- UID_BLUE, NetworkStatsAccess.Level.USER);
+ PID_BLUE, UID_BLUE, PACKAGE_BLUE, NetworkStatsAccess.Level.USER);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
@@ -420,7 +480,7 @@
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
DataUsageRequest request = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
- UID_RED, NetworkStatsAccess.Level.USER);
+ PID_RED, UID_RED, PACKAGE_RED, NetworkStatsAccess.Level.USER);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index ceeb997..f1820b3 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -18,6 +18,7 @@
import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
import static android.Manifest.permission.UPDATE_DEVICE_STATS;
+import static android.app.usage.NetworkStatsManager.PREFIX_DEV;
import static android.content.Intent.ACTION_UID_REMOVED;
import static android.content.Intent.EXTRA_UID;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
@@ -56,6 +57,9 @@
import static android.net.TrafficStats.MB_IN_BYTES;
import static android.net.TrafficStats.UID_REMOVED;
import static android.net.TrafficStats.UID_TETHERING;
+import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID;
+import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID_TAG;
+import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_XT;
import static android.text.format.DateUtils.DAY_IN_MILLIS;
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
@@ -77,6 +81,7 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
@@ -96,6 +101,7 @@
import android.net.NetworkCapabilities;
import android.net.NetworkStateSnapshot;
import android.net.NetworkStats;
+import android.net.NetworkStatsCollection;
import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
import android.net.TelephonyNetworkSpecifier;
@@ -104,6 +110,7 @@
import android.net.UnderlyingNetworkInfo;
import android.net.netstats.provider.INetworkStatsProviderCallback;
import android.net.wifi.WifiInfo;
+import android.os.DropBoxManager;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
@@ -112,11 +119,13 @@
import android.provider.Settings;
import android.system.ErrnoException;
import android.telephony.TelephonyManager;
+import android.util.ArrayMap;
import androidx.annotation.Nullable;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
+import com.android.internal.util.FileRotator;
import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.net.module.util.IBpfMap;
import com.android.net.module.util.LocationPermissionChecker;
@@ -131,6 +140,16 @@
import com.android.testutils.TestBpfMap;
import com.android.testutils.TestableNetworkStatsProviderBinder;
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.time.Clock;
+import java.time.ZoneOffset;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+
import libcore.testing.io.TestIoUtils;
import org.junit.After;
@@ -142,13 +161,6 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.io.File;
-import java.time.Clock;
-import java.time.ZoneOffset;
-import java.util.Objects;
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicBoolean;
-
/**
* Tests for {@link NetworkStatsService}.
*
@@ -187,6 +199,7 @@
private long mElapsedRealtime;
private File mStatsDir;
+ private File mLegacyStatsDir;
private MockContext mServiceContext;
private @Mock TelephonyManager mTelephonyManager;
private static @Mock WifiInfo sWifiInfo;
@@ -220,6 +233,12 @@
private ContentObserver mContentObserver;
private Handler mHandler;
private TetheringManager.TetheringEventCallback mTetheringEventCallback;
+ private Map<String, NetworkStatsCollection> mPlatformNetworkStatsCollection =
+ new ArrayMap<String, NetworkStatsCollection>();
+ private boolean mStoreFilesInApexData = false;
+ private int mImportLegacyTargetAttempts = 0;
+ private @Mock PersistentInt mImportLegacyAttemptsCounter;
+ private @Mock PersistentInt mImportLegacySuccessesCounter;
private class MockContext extends BroadcastInterceptingContext {
private final Context mBaseContext;
@@ -286,6 +305,8 @@
any(), any(), anyInt(), anyBoolean(), any())).thenReturn(true);
when(sWifiInfo.getNetworkKey()).thenReturn(TEST_WIFI_NETWORK_KEY);
mStatsDir = TestIoUtils.createTemporaryDirectory(getClass().getSimpleName());
+ mLegacyStatsDir = TestIoUtils.createTemporaryDirectory(
+ getClass().getSimpleName() + "-legacy");
PowerManager powerManager = (PowerManager) mServiceContext.getSystemService(
Context.POWER_SERVICE);
@@ -295,8 +316,7 @@
mHandlerThread = new HandlerThread("HandlerThread");
final NetworkStatsService.Dependencies deps = makeDependencies();
mService = new NetworkStatsService(mServiceContext, mNetd, mAlarmManager, wakeLock,
- mClock, mSettings, mStatsFactory, new NetworkStatsObservers(), mStatsDir,
- getBaseDir(mStatsDir), deps);
+ mClock, mSettings, mStatsFactory, new NetworkStatsObservers(), deps);
mElapsedRealtime = 0L;
@@ -339,6 +359,44 @@
private NetworkStatsService.Dependencies makeDependencies() {
return new NetworkStatsService.Dependencies() {
@Override
+ public File getLegacyStatsDir() {
+ return mLegacyStatsDir;
+ }
+
+ @Override
+ public File getOrCreateStatsDir() {
+ return mStatsDir;
+ }
+
+ @Override
+ public boolean getStoreFilesInApexData() {
+ return mStoreFilesInApexData;
+ }
+
+ @Override
+ public int getImportLegacyTargetAttempts() {
+ return mImportLegacyTargetAttempts;
+ }
+
+ @Override
+ public PersistentInt createImportLegacyAttemptsCounter(
+ @androidx.annotation.NonNull Path path) {
+ return mImportLegacyAttemptsCounter;
+ }
+
+ @Override
+ public PersistentInt createImportLegacySuccessesCounter(
+ @androidx.annotation.NonNull Path path) {
+ return mImportLegacySuccessesCounter;
+ }
+
+ @Override
+ public NetworkStatsCollection readPlatformCollection(
+ @NonNull String prefix, long bucketDuration) {
+ return mPlatformNetworkStatsCollection.get(prefix);
+ }
+
+ @Override
public HandlerThread makeHandlerThread() {
return mHandlerThread;
}
@@ -1704,10 +1762,108 @@
assertNetworkTotal(sTemplateImsi1, 0L, 0L, 0L, 0L, 0);
}
- private static File getBaseDir(File statsDir) {
- File baseDir = new File(statsDir, "netstats");
- baseDir.mkdirs();
- return baseDir;
+ /**
+ * Verify the service will perform data migration process can be controlled by the device flag.
+ */
+ @Test
+ public void testDataMigration() throws Exception {
+ assertStatsFilesExist(false);
+ expectDefaultSettings();
+
+ NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildWifiState()};
+
+ mService.notifyNetworkStatus(NETWORKS_WIFI, states, getActiveIface(states),
+ new UnderlyingNetworkInfo[0]);
+
+ // modify some number on wifi, and trigger poll event
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ // expectDefaultSettings();
+ expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ .insertEntry(TEST_IFACE, 1024L, 8L, 2048L, 16L));
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 2)
+ .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
+ .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 256L, 2L, 128L, 1L, 0L)
+ .insertEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
+ .insertEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 256L, 2L, 128L, 1L, 0L)
+ .insertEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 128L, 1L, 128L, 1L, 0L));
+
+ mService.noteUidForeground(UID_RED, false);
+ verify(mUidCounterSetMap, never()).deleteEntry(any());
+ mService.incrementOperationCount(UID_RED, 0xFAAD, 4);
+ mService.noteUidForeground(UID_RED, true);
+ verify(mUidCounterSetMap).updateEntry(
+ eq(new U32(UID_RED)), eq(new U8((short) SET_FOREGROUND)));
+ mService.incrementOperationCount(UID_RED, 0xFAAD, 6);
+
+ forcePollAndWaitForIdle();
+ // Simulate shutdown to force persisting data
+ mServiceContext.sendBroadcast(new Intent(Intent.ACTION_SHUTDOWN));
+ assertStatsFilesExist(true);
+
+ // Move the files to the legacy directory to simulate an import from old data
+ for (File f : mStatsDir.listFiles()) {
+ Files.move(f.toPath(), mLegacyStatsDir.toPath().resolve(f.getName()));
+ }
+ assertStatsFilesExist(false);
+
+ // Fetch the stats from the legacy files and set platform stats collection to be identical
+ mPlatformNetworkStatsCollection.put(PREFIX_DEV,
+ getLegacyCollection(PREFIX_DEV, false /* includeTags */));
+ mPlatformNetworkStatsCollection.put(PREFIX_XT,
+ getLegacyCollection(PREFIX_XT, false /* includeTags */));
+ mPlatformNetworkStatsCollection.put(PREFIX_UID,
+ getLegacyCollection(PREFIX_UID, false /* includeTags */));
+ mPlatformNetworkStatsCollection.put(PREFIX_UID_TAG,
+ getLegacyCollection(PREFIX_UID_TAG, true /* includeTags */));
+
+ // Mock zero usage and boot through serviceReady(), verify there is no imported data.
+ expectDefaultSettings();
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectSystemReady();
+ mService.systemReady();
+ assertStatsFilesExist(false);
+
+ // Set the flag and reboot, verify the imported data is not there until next boot.
+ mStoreFilesInApexData = true;
+ mImportLegacyTargetAttempts = 3;
+ mServiceContext.sendBroadcast(new Intent(Intent.ACTION_SHUTDOWN));
+ assertStatsFilesExist(false);
+
+ // Boot through systemReady() again.
+ expectDefaultSettings();
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectSystemReady();
+ mService.systemReady();
+
+ // After systemReady(), the service should have historical stats loaded again.
+ // Thus, verify
+ // 1. The stats are absorbed by the recorder.
+ // 2. The imported data are persisted.
+ // 3. The attempts count is set to target attempts count to indicate a successful
+ // migration.
+ assertNetworkTotal(sTemplateWifi, 1024L, 8L, 2048L, 16L, 0);
+ assertStatsFilesExist(true);
+ verify(mImportLegacyAttemptsCounter).set(3);
+ verify(mImportLegacySuccessesCounter).set(1);
+
+ // TODO: Verify upgrading with Exception won't damege original data and
+ // will decrease the retry counter by 1.
+ }
+
+ private NetworkStatsRecorder makeTestRecorder(File directory, String prefix, Config config,
+ boolean includeTags) {
+ final NetworkStats.NonMonotonicObserver observer =
+ mock(NetworkStats.NonMonotonicObserver.class);
+ final DropBoxManager dropBox = mock(DropBoxManager.class);
+ return new NetworkStatsRecorder(new FileRotator(
+ directory, prefix, config.rotateAgeMillis, config.deleteAgeMillis),
+ observer, dropBox, prefix, config.bucketDuration, includeTags);
+ }
+
+ private NetworkStatsCollection getLegacyCollection(String prefix, boolean includeTags) {
+ final NetworkStatsRecorder recorder = makeTestRecorder(mLegacyStatsDir, PREFIX_DEV,
+ mSettings.getDevConfig(), includeTags);
+ return recorder.getOrLoadCompleteLocked();
}
private void assertNetworkTotal(NetworkTemplate template, long rxBytes, long rxPackets,
@@ -1816,11 +1972,10 @@
}
private void assertStatsFilesExist(boolean exist) {
- final File basePath = new File(mStatsDir, "netstats");
if (exist) {
- assertTrue(basePath.list().length > 0);
+ assertTrue(mStatsDir.list().length > 0);
} else {
- assertTrue(basePath.list().length == 0);
+ assertTrue(mStatsDir.list().length == 0);
}
}
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java b/tests/unit/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java
index 0d34609..622f2be 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java
@@ -19,6 +19,8 @@
import static android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE;
import static android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA;
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
@@ -37,7 +39,6 @@
import android.annotation.Nullable;
import android.app.usage.NetworkStatsManager;
import android.content.Context;
-import android.os.Build;
import android.os.Looper;
import android.os.Parcel;
import android.telephony.SubscriptionManager;
@@ -63,7 +64,7 @@
import java.util.concurrent.Executors;
@RunWith(DevSdkIgnoreRunner.class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
public final class NetworkStatsSubscriptionsMonitorTest {
private static final int TEST_SUBID1 = 3;
private static final int TEST_SUBID2 = 5;
diff --git a/tests/unit/java/com/android/server/net/PersistentIntTest.kt b/tests/unit/java/com/android/server/net/PersistentIntTest.kt
new file mode 100644
index 0000000..9268352
--- /dev/null
+++ b/tests/unit/java/com/android/server/net/PersistentIntTest.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net
+
+import android.util.SystemConfigFileCommitEventLogger
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.SC_V2
+import com.android.testutils.assertThrows
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.io.File
+import java.io.IOException
+import java.nio.file.Files
+import java.nio.file.Path
+import java.nio.file.attribute.PosixFilePermission
+import java.nio.file.attribute.PosixFilePermission.OWNER_EXECUTE
+import java.nio.file.attribute.PosixFilePermission.OWNER_READ
+import java.nio.file.attribute.PosixFilePermission.OWNER_WRITE
+import java.util.Random
+import kotlin.test.assertEquals
+
+@RunWith(DevSdkIgnoreRunner::class)
+@IgnoreUpTo(SC_V2)
+class PersistentIntTest {
+ val tempFilesCreated = mutableSetOf<Path>()
+ lateinit var tempDir: Path
+
+ @Before
+ fun setUp() {
+ tempDir = Files.createTempDirectory("tmp.PersistentIntTest.")
+ }
+
+ @After
+ fun tearDown() {
+ var permissions = setOf(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE)
+ Files.setPosixFilePermissions(tempDir, permissions)
+
+ for (file in tempFilesCreated) {
+ Files.deleteIfExists(file)
+ }
+ Files.delete(tempDir)
+ }
+
+ @Test
+ fun testNormalReadWrite() {
+ // New, initialized to 0.
+ val pi = createPersistentInt()
+ assertEquals(0, pi.get())
+ pi.set(12345)
+ assertEquals(12345, pi.get())
+
+ // Existing.
+ val pi2 = createPersistentInt(pathOf(pi))
+ assertEquals(12345, pi2.get())
+ }
+
+ @Test
+ fun testReadOrWriteFailsInCreate() {
+ setWritable(tempDir, false)
+ assertThrows(IOException::class.java) {
+ createPersistentInt()
+ }
+ }
+
+ @Test
+ fun testReadOrWriteFailsAfterCreate() {
+ val pi = createPersistentInt()
+ pi.set(42)
+ assertEquals(42, pi.get())
+
+ val path = pathOf(pi)
+ setReadable(path, false)
+ assertThrows(IOException::class.java) { pi.get() }
+ pi.set(77)
+
+ setReadable(path, true)
+ setWritable(path, false)
+ setWritable(tempDir, false) // Writing creates a new file+renames, make this fail.
+ assertThrows(IOException::class.java) { pi.set(99) }
+ assertEquals(77, pi.get())
+ }
+
+ fun addOrRemovePermission(p: Path, permission: PosixFilePermission, add: Boolean) {
+ val permissions = Files.getPosixFilePermissions(p)
+ if (add) {
+ permissions.add(permission)
+ } else {
+ permissions.remove(permission)
+ }
+ Files.setPosixFilePermissions(p, permissions)
+ }
+
+ fun setReadable(p: Path, readable: Boolean) {
+ addOrRemovePermission(p, OWNER_READ, readable)
+ }
+
+ fun setWritable(p: Path, writable: Boolean) {
+ addOrRemovePermission(p, OWNER_WRITE, writable)
+ }
+
+ fun pathOf(pi: PersistentInt): Path {
+ return File(pi.path).toPath()
+ }
+
+ fun createPersistentInt(path: Path = randomTempPath()): PersistentInt {
+ tempFilesCreated.add(path)
+ return PersistentInt(path.toString(),
+ SystemConfigFileCommitEventLogger("PersistentIntTest"))
+ }
+
+ fun randomTempPath(): Path {
+ return tempDir.resolve(Integer.toHexString(Random().nextInt())).also {
+ tempFilesCreated.add(it)
+ }
+ }
+}