Merge "Include FlaggedApi.bp" into main
diff --git a/Cronet/tests/common/Android.bp b/Cronet/tests/common/Android.bp
index a484adb..edeb0b3 100644
--- a/Cronet/tests/common/Android.bp
+++ b/Cronet/tests/common/Android.bp
@@ -17,6 +17,7 @@
// They must be fast and stable, and exercise public or test APIs.
package {
+ default_team: "trendy_team_fwk_core_networking",
// See: http://go/android-license-faq
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/Cronet/tests/cts/Android.bp b/Cronet/tests/cts/Android.bp
index 7b52694..92b73d9 100644
--- a/Cronet/tests/cts/Android.bp
+++ b/Cronet/tests/cts/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -46,7 +47,9 @@
"framework-connectivity",
"org.apache.http.legacy",
],
- lint: { test: true }
+ lint: {
+ test: true,
+ },
}
android_test {
diff --git a/Cronet/tests/cts/src/android/net/http/cts/HttpEngineTest.java b/Cronet/tests/cts/src/android/net/http/cts/HttpEngineTest.java
index 9fc4389..f86ac29 100644
--- a/Cronet/tests/cts/src/android/net/http/cts/HttpEngineTest.java
+++ b/Cronet/tests/cts/src/android/net/http/cts/HttpEngineTest.java
@@ -247,10 +247,8 @@
@Test
public void testHttpEngine_requestUsesDefaultUserAgent() throws Exception {
mEngine = mEngineBuilder.build();
- HttpCtsTestServer server =
- new HttpCtsTestServer(ApplicationProvider.getApplicationContext());
- String url = server.getUserAgentUrl();
+ String url = mTestServer.getUserAgentUrl();
UrlRequest request =
mEngine.newUrlRequestBuilder(url, mCallback.getExecutor(), mCallback).build();
request.start();
@@ -266,14 +264,12 @@
@Test
public void testHttpEngine_requestUsesCustomUserAgent() throws Exception {
String userAgent = "CtsTests User Agent";
- HttpCtsTestServer server =
- new HttpCtsTestServer(ApplicationProvider.getApplicationContext());
mEngine =
new HttpEngine.Builder(ApplicationProvider.getApplicationContext())
.setUserAgent(userAgent)
.build();
- String url = server.getUserAgentUrl();
+ String url = mTestServer.getUserAgentUrl();
UrlRequest request =
mEngine.newUrlRequestBuilder(url, mCallback.getExecutor(), mCallback).build();
request.start();
diff --git a/Cronet/tests/mts/Android.bp b/Cronet/tests/mts/Android.bp
index 743a1ca..9486e1f 100644
--- a/Cronet/tests/mts/Android.bp
+++ b/Cronet/tests/mts/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_fwk_core_networking",
// See: http://go/android-license-faq
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/DnsResolver/Android.bp b/DnsResolver/Android.bp
index d133034..716eb10 100644
--- a/DnsResolver/Android.bp
+++ b/DnsResolver/Android.bp
@@ -14,6 +14,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -56,7 +57,10 @@
cc_test {
name: "dns_helper_unit_test",
defaults: ["netd_defaults"],
- test_suites: ["general-tests", "mts-tethering"],
+ test_suites: [
+ "general-tests",
+ "mts-tethering",
+ ],
test_config_template: ":net_native_test_config_template",
header_libs: [
"bpf_connectivity_headers",
@@ -68,8 +72,8 @@
"libcom.android.tethering.dns_helper",
],
shared_libs: [
- "libbase",
- "libcutils",
+ "libbase",
+ "libcutils",
],
compile_multilib: "both",
multilib: {
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index 73c11ba..e4e6c70 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -82,7 +83,6 @@
],
manifest: "AndroidManifestBase.xml",
lint: {
- strict_updatability_linting: true,
error_checks: ["NewApi"],
},
}
@@ -102,7 +102,6 @@
],
apex_available: ["com.android.tethering"],
lint: {
- strict_updatability_linting: true,
baseline_filename: "lint-baseline.xml",
},
}
@@ -120,7 +119,6 @@
],
apex_available: ["com.android.tethering"],
lint: {
- strict_updatability_linting: true,
baseline_filename: "lint-baseline.xml",
},
}
@@ -195,9 +193,6 @@
optimize: {
proguard_flags_files: ["proguard.flags"],
},
- lint: {
- strict_updatability_linting: true,
- },
}
// Updatable tethering packaged for finalized API
@@ -213,10 +208,6 @@
use_embedded_native_libs: true,
privapp_allowlist: ":privapp_allowlist_com.android.tethering",
apex_available: ["com.android.tethering"],
- lint: {
- strict_updatability_linting: true,
- baseline_filename: "lint-baseline.xml",
- },
}
android_app {
@@ -233,9 +224,7 @@
privapp_allowlist: ":privapp_allowlist_com.android.tethering",
apex_available: ["com.android.tethering"],
lint: {
- strict_updatability_linting: true,
error_checks: ["NewApi"],
- baseline_filename: "lint-baseline.xml",
},
}
@@ -263,9 +252,6 @@
static_libs: ["tetheringprotos"],
apex_available: ["com.android.tethering"],
min_sdk_version: "30",
- lint: {
- baseline_filename: "lint-baseline.xml",
- },
}
genrule {
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index de9017a..30bdf37 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -24,6 +25,7 @@
name: "ConnectivityNextEnableDefaults",
enabled: true,
}
+
java_defaults {
name: "NetworkStackApiShimSettingsForCurrentBranch",
// API shims to include in the networking modules built from the branch. Branches that disable
@@ -31,6 +33,7 @@
// (X_current API level).
static_libs: ["NetworkStackApiCurrentShims"],
}
+
apex_defaults {
name: "ConnectivityApexDefaults",
// Tethering app to include in the AOSP apex. Branches that disable the "next" targets may use
@@ -38,6 +41,7 @@
// package names and keys, so that apex will be unused anyway.
apps: ["TetheringNext"], // Replace to "Tethering" if ConnectivityNextEnableDefaults is false.
}
+
enable_tethering_next_apex = true
// This is a placeholder comment to avoid merge conflicts
// as the above target may have different "enabled" values
@@ -50,16 +54,6 @@
"//external/cronet/third_party/boringssl:libcrypto",
"//external/cronet/third_party/boringssl:libssl",
],
- arch: {
- riscv64: {
- // TODO: remove this when there is a riscv64 libcronet
- exclude_jni_libs: [
- "cronet_aml_components_cronet_android_cronet",
- "//external/cronet/third_party/boringssl:libcrypto",
- "//external/cronet/third_party/boringssl:libssl",
- ],
- },
- },
}
apex {
@@ -92,7 +86,7 @@
both: {
jni_libs: [
"libframework-connectivity-jni",
- "libframework-connectivity-tiramisu-jni"
+ "libframework-connectivity-tiramisu-jni",
],
},
},
@@ -117,8 +111,9 @@
"ServiceConnectivityResources",
],
prebuilts: [
- "ot-daemon.init.34rc",
"current_sdkinfo",
+ "netbpfload.mainline.rc",
+ "ot-daemon.init.34rc",
],
manifest: "manifest.json",
key: "com.android.tethering.key",
diff --git a/Tethering/apex/permissions/Android.bp b/Tethering/apex/permissions/Android.bp
index 69c1aa2..20772a8 100644
--- a/Tethering/apex/permissions/Android.bp
+++ b/Tethering/apex/permissions/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
default_visibility: ["//packages/modules/Connectivity/Tethering:__subpackages__"],
}
@@ -22,4 +23,4 @@
filegroup {
name: "privapp_allowlist_com.android.tethering",
srcs: ["permissions.xml"],
-}
\ No newline at end of file
+}
diff --git a/Tethering/common/TetheringLib/Android.bp b/Tethering/common/TetheringLib/Android.bp
index bcea425..9c2a59d 100644
--- a/Tethering/common/TetheringLib/Android.bp
+++ b/Tethering/common/TetheringLib/Android.bp
@@ -14,6 +14,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -55,14 +56,16 @@
hostdex: true, // for hiddenapi check
permitted_packages: ["android.net"],
- lint: { strict_updatability_linting: true },
+ lint: {
+ strict_updatability_linting: true,
+ },
}
java_library {
- name: "framework-tethering-pre-jarjar",
- defaults: [
- "framework-tethering-defaults",
- ],
+ name: "framework-tethering-pre-jarjar",
+ defaults: [
+ "framework-tethering-defaults",
+ ],
}
java_genrule {
@@ -88,7 +91,7 @@
name: "framework-tethering-defaults",
defaults: ["framework-module-defaults"],
srcs: [
- ":framework-tethering-srcs"
+ ":framework-tethering-srcs",
],
libs: ["framework-connectivity.stubs.module_lib"],
aidl: {
@@ -107,5 +110,5 @@
"src/**/*.aidl",
"src/**/*.java",
],
- path: "src"
+ path: "src",
}
diff --git a/Tethering/lint-baseline.xml b/Tethering/lint-baseline.xml
index 37511c6..4f92c9c 100644
--- a/Tethering/lint-baseline.xml
+++ b/Tethering/lint-baseline.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-dev" type="baseline" dependencies="true" variant="all" version="8.0.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
<issue
id="NewApi"
@@ -8,7 +8,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/modules/Connectivity/Tethering/src/com/android/networkstack/tethering/OffloadController.java"
- line="293"
+ line="283"
column="44"/>
</issue>
diff --git a/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java b/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
index 53c80ae..13a7a22 100644
--- a/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
+++ b/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
@@ -16,6 +16,7 @@
package com.android.networkstack.tethering;
+import static com.android.net.module.util.netlink.NetlinkUtils.SOCKET_RECV_BUFSIZE;
import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_DUMP;
import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST;
@@ -198,7 +199,8 @@
public NativeHandle createConntrackSocket(final int groups) {
final FileDescriptor fd;
try {
- fd = NetlinkUtils.netlinkSocketForProto(OsConstants.NETLINK_NETFILTER);
+ fd = NetlinkUtils.netlinkSocketForProto(OsConstants.NETLINK_NETFILTER,
+ SOCKET_RECV_BUFSIZE);
} catch (ErrnoException e) {
mLog.e("Unable to create conntrack socket " + e);
return null;
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 552b105..873961a 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -368,6 +368,7 @@
// Load tethering configuration.
updateConfiguration();
+ mConfig.readEnableSyncSM(mContext);
// It is OK for the configuration to be passed to the PrivateAddressCoordinator at
// construction time because the only part of the configuration it uses is
// shouldEnableWifiP2pDedicatedIp(), and currently do not support changing that.
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
index 0678525..298940e 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
@@ -132,6 +132,9 @@
public static final String TETHER_FORCE_RANDOM_PREFIX_BASE_SELECTION =
"tether_force_random_prefix_base_selection";
+
+ public static final String TETHER_ENABLE_SYNC_SM = "tether_enable_sync_sm";
+
/**
* Default value that used to periodic polls tether offload stats from tethering offload HAL
* to make the data warnings work.
@@ -139,7 +142,7 @@
public static final int DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS = 5000;
/** A flag for using synchronous or asynchronous state machine. */
- public static final boolean USE_SYNC_SM = false;
+ public static boolean USE_SYNC_SM = false;
public final String[] tetherableUsbRegexs;
public final String[] tetherableWifiRegexs;
@@ -385,6 +388,16 @@
return mRandomPrefixBase;
}
+ /**
+ * Check whether sync SM is enabled then set it to USE_SYNC_SM. This should be called once
+ * when tethering is created. Otherwise if the flag is pushed while tethering is enabled,
+ * then it's possible for some IpServer(s) running the new sync state machine while others
+ * use the async state machine.
+ */
+ public void readEnableSyncSM(final Context ctx) {
+ USE_SYNC_SM = mDeps.isFeatureEnabled(ctx, TETHER_ENABLE_SYNC_SM);
+ }
+
/** Does the dumping.*/
public void dump(PrintWriter pw) {
pw.print("activeDataSubId: ");
@@ -438,6 +451,9 @@
pw.print("mRandomPrefixBase: ");
pw.println(mRandomPrefixBase);
+
+ pw.print("USE_SYNC_SM: ");
+ pw.println(USE_SYNC_SM);
}
/** Returns the string representation of this object.*/
diff --git a/Tethering/tests/Android.bp b/Tethering/tests/Android.bp
index 72ca666..22cf3c5 100644
--- a/Tethering/tests/Android.bp
+++ b/Tethering/tests/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -24,5 +25,5 @@
visibility: [
"//packages/modules/Connectivity/tests:__subpackages__",
"//packages/modules/Connectivity/Tethering/tests:__subpackages__",
- ]
+ ],
}
diff --git a/Tethering/tests/integration/Android.bp b/Tethering/tests/integration/Android.bp
index 2594a5e..f17396d 100644
--- a/Tethering/tests/integration/Android.bp
+++ b/Tethering/tests/integration/Android.bp
@@ -14,6 +14,7 @@
// limitations under the License.
//
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -50,7 +51,7 @@
visibility: [
"//packages/modules/Connectivity/Tethering/tests/mts",
"//packages/modules/Connectivity/tests/cts/net",
- ]
+ ],
}
// Library including tethering integration tests targeting the latest stable SDK.
@@ -67,7 +68,7 @@
"//packages/modules/Connectivity/tests/cts/tethering",
"//packages/modules/Connectivity/tests:__subpackages__",
"//packages/modules/Connectivity/Tethering/tests:__subpackages__",
- ]
+ ],
}
// Library including tethering integration tests targeting current development SDK.
@@ -83,7 +84,7 @@
visibility: [
"//packages/modules/Connectivity/tests/cts/tethering",
"//packages/modules/Connectivity/Tethering/tests/mts",
- ]
+ ],
}
// TODO: remove because TetheringIntegrationTests has been covered by ConnectivityCoverageTests.
diff --git a/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java b/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
index c232697..2933a44 100644
--- a/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
+++ b/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
@@ -865,12 +865,12 @@
// trigger the reselection, the total test time may over test suite 1 minmute timeout.
// Probably need to disable/restore all internet networks in a common place of test
// process. Currently, EthernetTetheringTest is part of CTS test which needs wifi network
- // connection if device has wifi feature. CtsNetUtils#toggleWifi() checks wifi connection
- // during the toggling process.
- // See Tethering#chooseUpstreamType, CtsNetUtils#toggleWifi.
+ // connection if device has wifi feature.
+ // See Tethering#chooseUpstreamType
// TODO: toggle cellular network if the device has no WIFI feature.
Log.d(TAG, "Toggle WIFI to retry upstream selection");
- sCtsNetUtils.toggleWifi();
+ sCtsNetUtils.disableWifi();
+ sCtsNetUtils.ensureWifiConnected();
// Wait for expected upstream.
final CompletableFuture<Network> future = new CompletableFuture<>();
diff --git a/Tethering/tests/mts/Android.bp b/Tethering/tests/mts/Android.bp
index 4f4b03c..a80e49e 100644
--- a/Tethering/tests/mts/Android.bp
+++ b/Tethering/tests/mts/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/Tethering/tests/privileged/Android.bp b/Tethering/tests/privileged/Android.bp
index c890197..ba6be66 100644
--- a/Tethering/tests/privileged/Android.bp
+++ b/Tethering/tests/privileged/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/Tethering/tests/unit/Android.bp b/Tethering/tests/unit/Android.bp
index 36d9a63..24407ca 100644
--- a/Tethering/tests/unit/Android.bp
+++ b/Tethering/tests/unit/Android.bp
@@ -16,6 +16,7 @@
// Tests in this folder are included both in unit tests and CTS.
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -23,7 +24,7 @@
name: "TetheringCommonTests",
srcs: [
"common/**/*.java",
- "common/**/*.kt"
+ "common/**/*.kt",
],
static_libs: [
"androidx.test.rules",
@@ -95,7 +96,7 @@
visibility: [
"//packages/modules/Connectivity/tests:__subpackages__",
"//packages/modules/Connectivity/Tethering/tests:__subpackages__",
- ]
+ ],
}
android_test {
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 aa322dc..dd51c7a 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
@@ -754,4 +754,27 @@
new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
assertEquals(p2pLeasesSubnetPrefixLength, p2pCfg.getP2pLeasesSubnetPrefixLength());
}
+
+ private void setTetherEnableSyncSMFlagEnabled(Boolean enabled) {
+ mDeps.setFeatureEnabled(TetheringConfiguration.TETHER_ENABLE_SYNC_SM, enabled);
+ new TetheringConfiguration(
+ mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps).readEnableSyncSM(mMockContext);
+ }
+
+ private void assertEnableSyncSM(boolean value) {
+ assertEquals(value, TetheringConfiguration.USE_SYNC_SM);
+ }
+
+ @Test
+ public void testEnableSyncSMFlag() throws Exception {
+ // Test default disabled
+ setTetherEnableSyncSMFlagEnabled(null);
+ assertEnableSyncSM(false);
+
+ setTetherEnableSyncSMFlagEnabled(true);
+ assertEnableSyncSM(true);
+
+ setTetherEnableSyncSMFlagEnabled(false);
+ assertEnableSyncSM(false);
+ }
}
diff --git a/bpf_progs/Android.bp b/bpf_progs/Android.bp
index cdf47e7..674cd98 100644
--- a/bpf_progs/Android.bp
+++ b/bpf_progs/Android.bp
@@ -18,6 +18,7 @@
// struct definitions shared with JNI
//
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
index f223dd1..c4b27b8 100644
--- a/bpf_progs/netd.c
+++ b/bpf_progs/netd.c
@@ -446,8 +446,18 @@
const struct egress_bool egress,
const bool enable_tracing,
const struct kver_uint kver) {
+ // sock_uid will be 'overflowuid' if !sk_fullsock(sk_to_full_sk(skb->sk))
uint32_t sock_uid = bpf_get_socket_uid(skb);
- uint64_t cookie = bpf_get_socket_cookie(skb);
+
+ // kernel's DEFAULT_OVERFLOWUID is 65534, this is the overflow 'nobody' uid,
+ // usually this being returned means that skb->sk is NULL during RX
+ // (early decap socket lookup failure), which commonly happens for incoming
+ // packets to an unconnected udp socket.
+ // But it can also happen for egress from a timewait socket.
+ // Let's treat such cases as 'root' which is_system_uid()
+ if (sock_uid == 65534) sock_uid = 0;
+
+ uint64_t cookie = bpf_get_socket_cookie(skb); // 0 iff !skb->sk
UidTagValue* utag = bpf_cookie_tag_map_lookup_elem(&cookie);
uint32_t uid, tag;
if (utag) {
@@ -616,12 +626,13 @@
uint32_t sock_uid = bpf_get_socket_uid(skb);
if (is_system_uid(sock_uid)) return BPF_MATCH;
- // 65534 is the overflow 'nobody' uid, usually this being returned means
- // that skb->sk is NULL during RX (early decap socket lookup failure),
- // which commonly happens for incoming packets to an unconnected udp socket.
- // Additionally bpf_get_socket_cookie() returns 0 if skb->sk is NULL
- if ((sock_uid == 65534) && !bpf_get_socket_cookie(skb) && is_received_skb(skb))
- return BPF_MATCH;
+ // kernel's DEFAULT_OVERFLOWUID is 65534, this is the overflow 'nobody' uid,
+ // usually this being returned means that skb->sk is NULL during RX
+ // (early decap socket lookup failure), which commonly happens for incoming
+ // packets to an unconnected udp socket.
+ // But it can also happen for egress from a timewait socket.
+ // Let's treat such cases as 'root' which is_system_uid()
+ if (sock_uid == 65534) return BPF_MATCH;
UidOwnerValue* allowlistMatch = bpf_uid_owner_map_lookup_elem(&sock_uid);
if (allowlistMatch) return allowlistMatch->rule & HAPPY_BOX_MATCH ? BPF_MATCH : BPF_NOMATCH;
diff --git a/clatd/Android.bp b/clatd/Android.bp
index 595c6b9..43eb2d8 100644
--- a/clatd/Android.bp
+++ b/clatd/Android.bp
@@ -1,4 +1,5 @@
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["external_android-clat_license"],
}
@@ -54,7 +55,7 @@
defaults: ["clatd_defaults"],
srcs: [
":clatd_common",
- "main.c"
+ "main.c",
],
static_libs: [
"libip_checksum",
@@ -101,7 +102,7 @@
defaults: ["clatd_defaults"],
srcs: [
":clatd_common",
- "clatd_test.cpp"
+ "clatd_test.cpp",
],
static_libs: [
"libbase",
diff --git a/common/Android.bp b/common/Android.bp
index 69d1252..0048a0a 100644
--- a/common/Android.bp
+++ b/common/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_fwk_core_networking",
// See: http://go/android-license-faq
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/common/FlaggedApi.bp b/common/FlaggedApi.bp
index 2cb9b2f..c382e76 100644
--- a/common/FlaggedApi.bp
+++ b/common/FlaggedApi.bp
@@ -17,6 +17,7 @@
aconfig_declarations {
name: "com.android.net.flags-aconfig",
package: "com.android.net.flags",
+ container: "system",
srcs: ["flags.aconfig"],
visibility: ["//packages/modules/Connectivity:__subpackages__"],
}
diff --git a/common/flags.aconfig b/common/flags.aconfig
index 8eb3cbf..010fafc 100644
--- a/common/flags.aconfig
+++ b/common/flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.net.flags"
+container: "system"
# This file contains aconfig flags for FlaggedAPI annotations
# Flags used from platform code must be in under frameworks
@@ -38,3 +39,10 @@
bug: "294777050"
}
+flag {
+ name: "ipsec_transform_state"
+ namespace: "android_core_networking_ipsec"
+ description: "The flag controls the access for getIpSecTransformState and IpSecTransformState"
+ bug: "308011229"
+}
+
diff --git a/framework-t/Android.bp b/framework-t/Android.bp
index b90d99f..9203a3e 100644
--- a/framework-t/Android.bp
+++ b/framework-t/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_fwk_core_networking",
// See: http://go/android-license-faq
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -34,6 +35,7 @@
name: "enable-framework-connectivity-t-targets",
enabled: true,
}
+
// The above defaults can be used to disable framework-connectivity t
// targets while minimizing merge conflicts in the build rules.
@@ -116,6 +118,7 @@
"framework-bluetooth",
"framework-wifi",
"framework-connectivity-pre-jarjar",
+ "framework-location.stubs.module_lib",
],
visibility: ["//packages/modules/Connectivity:__subpackages__"],
}
@@ -138,6 +141,7 @@
"sdk_module-lib_current_framework-connectivity",
],
libs: [
+ "framework-location.stubs.module_lib",
"sdk_module-lib_current_framework-connectivity",
],
permitted_packages: [
diff --git a/framework-t/api/current.txt b/framework-t/api/current.txt
index 60a88c0..7cd3d4f 100644
--- a/framework-t/api/current.txt
+++ b/framework-t/api/current.txt
@@ -210,9 +210,26 @@
package android.net.nsd {
+ @FlaggedApi("com.android.net.flags.nsd_subtypes_support_enabled") public final class DiscoveryRequest implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public android.net.Network getNetwork();
+ method @NonNull public String getServiceType();
+ method @Nullable public String getSubtype();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.nsd.DiscoveryRequest> CREATOR;
+ }
+
+ public static final class DiscoveryRequest.Builder {
+ ctor public DiscoveryRequest.Builder(@NonNull String);
+ method @NonNull public android.net.nsd.DiscoveryRequest build();
+ method @NonNull public android.net.nsd.DiscoveryRequest.Builder setNetwork(@Nullable android.net.Network);
+ method @NonNull public android.net.nsd.DiscoveryRequest.Builder setSubtype(@Nullable String);
+ }
+
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 java.util.concurrent.Executor, @NonNull android.net.nsd.NsdManager.DiscoveryListener);
+ method @FlaggedApi("com.android.net.flags.nsd_subtypes_support_enabled") public void discoverServices(@NonNull android.net.nsd.DiscoveryRequest, @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);
diff --git a/framework-t/api/system-current.txt b/framework-t/api/system-current.txt
index d346af3..1f1953c 100644
--- a/framework-t/api/system-current.txt
+++ b/framework-t/api/system-current.txt
@@ -59,11 +59,17 @@
}
public class NearbyManager {
+ method @FlaggedApi("com.android.nearby.flags.powered_off_finding") @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getPoweredOffFindingMode();
method public void queryOffloadCapability(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.nearby.OffloadCapability>);
+ method @FlaggedApi("com.android.nearby.flags.powered_off_finding") @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void setPoweredOffFindingEphemeralIds(@NonNull java.util.List<byte[]>);
+ method @FlaggedApi("com.android.nearby.flags.powered_off_finding") @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void setPoweredOffFindingMode(int);
method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void startBroadcast(@NonNull android.nearby.BroadcastRequest, @NonNull java.util.concurrent.Executor, @NonNull android.nearby.BroadcastCallback);
method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int startScan(@NonNull android.nearby.ScanRequest, @NonNull java.util.concurrent.Executor, @NonNull android.nearby.ScanCallback);
method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void stopBroadcast(@NonNull android.nearby.BroadcastCallback);
method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void stopScan(@NonNull android.nearby.ScanCallback);
+ field @FlaggedApi("com.android.nearby.flags.powered_off_finding") public static final int POWERED_OFF_FINDING_MODE_DISABLED = 1; // 0x1
+ field @FlaggedApi("com.android.nearby.flags.powered_off_finding") public static final int POWERED_OFF_FINDING_MODE_ENABLED = 2; // 0x2
+ field @FlaggedApi("com.android.nearby.flags.powered_off_finding") public static final int POWERED_OFF_FINDING_MODE_UNSUPPORTED = 0; // 0x0
}
public final class OffloadCapability implements android.os.Parcelable {
@@ -500,6 +506,7 @@
method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_NETWORK_STATE, "android.permission.THREAD_NETWORK_PRIVILEGED"}) public void registerOperationalDatasetCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.thread.ThreadNetworkController.OperationalDatasetCallback);
method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.thread.ThreadNetworkController.StateCallback);
method @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") public void scheduleMigration(@NonNull android.net.thread.PendingOperationalDataset, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.net.thread.ThreadNetworkException>);
+ method @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") public void setEnabled(boolean, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.net.thread.ThreadNetworkException>);
method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_NETWORK_STATE, "android.permission.THREAD_NETWORK_PRIVILEGED"}) public void unregisterOperationalDatasetCallback(@NonNull android.net.thread.ThreadNetworkController.OperationalDatasetCallback);
method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void unregisterStateCallback(@NonNull android.net.thread.ThreadNetworkController.StateCallback);
field public static final int DEVICE_ROLE_CHILD = 2; // 0x2
@@ -507,6 +514,9 @@
field public static final int DEVICE_ROLE_LEADER = 4; // 0x4
field public static final int DEVICE_ROLE_ROUTER = 3; // 0x3
field public static final int DEVICE_ROLE_STOPPED = 0; // 0x0
+ field public static final int STATE_DISABLED = 0; // 0x0
+ field public static final int STATE_DISABLING = 2; // 0x2
+ field public static final int STATE_ENABLED = 1; // 0x1
field public static final int THREAD_VERSION_1_3 = 4; // 0x4
}
@@ -518,6 +528,7 @@
public static interface ThreadNetworkController.StateCallback {
method public void onDeviceRoleChanged(int);
method public default void onPartitionIdChanged(long);
+ method public default void onThreadEnableStateChanged(int);
}
@FlaggedApi("com.android.net.thread.flags.thread_enabled") public class ThreadNetworkException extends java.lang.Exception {
@@ -530,6 +541,7 @@
field public static final int ERROR_REJECTED_BY_PEER = 8; // 0x8
field public static final int ERROR_RESOURCE_EXHAUSTED = 10; // 0xa
field public static final int ERROR_RESPONSE_BAD_FORMAT = 9; // 0x9
+ field public static final int ERROR_THREAD_DISABLED = 12; // 0xc
field public static final int ERROR_TIMEOUT = 3; // 0x3
field public static final int ERROR_UNAVAILABLE = 4; // 0x4
field public static final int ERROR_UNKNOWN = 11; // 0xb
diff --git a/framework-t/src/android/app/usage/NetworkStatsManager.java b/framework-t/src/android/app/usage/NetworkStatsManager.java
index d139544..7fa0661 100644
--- a/framework-t/src/android/app/usage/NetworkStatsManager.java
+++ b/framework-t/src/android/app/usage/NetworkStatsManager.java
@@ -510,6 +510,27 @@
* 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.
*
+ * @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
+ * @return Statistics which is described above.
+ * @throws SecurityException if permissions are insufficient to read network statistics.
* @see #queryDetailsForUidTagState(int, String, long, long, int, int, int)
*/
@NonNull
diff --git a/framework-t/src/android/net/NetworkStatsAccess.java b/framework-t/src/android/net/NetworkStatsAccess.java
index 7fe499b..7c9b3ec 100644
--- a/framework-t/src/android/net/NetworkStatsAccess.java
+++ b/framework-t/src/android/net/NetworkStatsAccess.java
@@ -128,7 +128,7 @@
final int appId = UserHandle.getAppId(callingUid);
- final boolean isNetworkStack = PermissionUtils.checkAnyPermissionOf(
+ final boolean isNetworkStack = PermissionUtils.hasAnyPermissionOf(
context, callingPid, callingUid, android.Manifest.permission.NETWORK_STACK,
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
diff --git a/framework-t/src/android/net/nsd/DiscoveryRequest.java b/framework-t/src/android/net/nsd/DiscoveryRequest.java
new file mode 100644
index 0000000..b0b71ea
--- /dev/null
+++ b/framework-t/src/android/net/nsd/DiscoveryRequest.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Network;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.Objects;
+
+/**
+ * Encapsulates parameters for {@link NsdManager#discoverServices}.
+ */
+@FlaggedApi(NsdManager.Flags.NSD_SUBTYPES_SUPPORT_ENABLED)
+public final class DiscoveryRequest implements Parcelable {
+ private final int mProtocolType;
+
+ @NonNull
+ private final String mServiceType;
+
+ @Nullable
+ private final String mSubtype;
+
+ @Nullable
+ private final Network mNetwork;
+
+ // TODO: add mDiscoveryConfig for more fine-grained discovery behavior control
+
+ @NonNull
+ public static final Creator<DiscoveryRequest> CREATOR =
+ new Creator<>() {
+ @Override
+ public DiscoveryRequest createFromParcel(Parcel in) {
+ int protocolType = in.readInt();
+ String serviceType = in.readString();
+ String subtype = in.readString();
+ Network network =
+ in.readParcelable(Network.class.getClassLoader(), Network.class);
+ return new DiscoveryRequest(protocolType, serviceType, subtype, network);
+ }
+
+ @Override
+ public DiscoveryRequest[] newArray(int size) {
+ return new DiscoveryRequest[size];
+ }
+ };
+
+ private DiscoveryRequest(int protocolType, @NonNull String serviceType,
+ @Nullable String subtype, @Nullable Network network) {
+ mProtocolType = protocolType;
+ mServiceType = serviceType;
+ mSubtype = subtype;
+ mNetwork = network;
+ }
+
+ /**
+ * Returns the service type in format of dot-joint string of two labels.
+ *
+ * For example, "_ipp._tcp" for internet printer and "_matter._tcp" for <a
+ * href="https://csa-iot.org/all-solutions/matter">Matter</a> operational device.
+ */
+ @NonNull
+ public String getServiceType() {
+ return mServiceType;
+ }
+
+ /**
+ * Returns the subtype without the trailing "._sub" label or {@code null} if no subtype is
+ * specified.
+ *
+ * For example, the return value will be "_printer" for subtype "_printer._sub".
+ */
+ @Nullable
+ public String getSubtype() {
+ return mSubtype;
+ }
+
+ /**
+ * Returns the service discovery protocol.
+ *
+ * @hide
+ */
+ public int getProtocolType() {
+ return mProtocolType;
+ }
+
+ /**
+ * Returns the {@link Network} on which the query should be sent or {@code null} if no
+ * network is specified.
+ */
+ @Nullable
+ public Network getNetwork() {
+ return mNetwork;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(", protocolType: ").append(mProtocolType)
+ .append(", serviceType: ").append(mServiceType)
+ .append(", subtype: ").append(mSubtype)
+ .append(", network: ").append(mNetwork);
+ return sb.toString();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ } else if (!(other instanceof DiscoveryRequest)) {
+ return false;
+ } else {
+ DiscoveryRequest otherRequest = (DiscoveryRequest) other;
+ return mProtocolType == otherRequest.mProtocolType
+ && Objects.equals(mServiceType, otherRequest.mServiceType)
+ && Objects.equals(mSubtype, otherRequest.mSubtype)
+ && Objects.equals(mNetwork, otherRequest.mNetwork);
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mProtocolType, mServiceType, mSubtype, mNetwork);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mProtocolType);
+ dest.writeString(mServiceType);
+ dest.writeString(mSubtype);
+ dest.writeParcelable(mNetwork, flags);
+ }
+
+ /** The builder for creating new {@link DiscoveryRequest} objects. */
+ public static final class Builder {
+ private final int mProtocolType;
+
+ @NonNull
+ private String mServiceType;
+
+ @Nullable
+ private String mSubtype;
+
+ @Nullable
+ private Network mNetwork;
+
+ /**
+ * Creates a new default {@link Builder} object with given service type.
+ *
+ * @throws IllegalArgumentException if {@code serviceType} is {@code null} or an empty
+ * string
+ */
+ public Builder(@NonNull String serviceType) {
+ this(NsdManager.PROTOCOL_DNS_SD, serviceType);
+ }
+
+ /** @hide */
+ public Builder(int protocolType, @NonNull String serviceType) {
+ NsdManager.checkProtocol(protocolType);
+ mProtocolType = protocolType;
+ setServiceType(serviceType);
+ }
+
+ /**
+ * Sets the service type to be discovered or {@code null} if no services should be queried.
+ *
+ * The {@code serviceType} must be a dot-joint string of two labels. For example,
+ * "_ipp._tcp" for internet printer. Additionally, the first label must start with
+ * underscore ('_') and the second label must be either "_udp" or "_tcp". Otherwise, {@link
+ * NsdManager#discoverServices} will fail with {@link NsdManager#FAILURE_BAD_PARAMETER}.
+ *
+ * @throws IllegalArgumentException if {@code serviceType} is {@code null} or an empty
+ * string
+ *
+ * @hide
+ */
+ @NonNull
+ public Builder setServiceType(@NonNull String serviceType) {
+ if (TextUtils.isEmpty(serviceType)) {
+ throw new IllegalArgumentException("Service type cannot be empty");
+ }
+ mServiceType = serviceType;
+ return this;
+ }
+
+ /**
+ * Sets the optional subtype of the services to be discovered.
+ *
+ * If a non-empty {@code subtype} is specified, it must start with underscore ('_') and
+ * have the trailing "._sub" removed. Otherwise, {@link NsdManager#discoverServices} will
+ * fail with {@link NsdManager#FAILURE_BAD_PARAMETER}. For example, {@code subtype} should
+ * be "_printer" for DNS name "_printer._sub._http._tcp". In this case, only services with
+ * this {@code subtype} will be queried, rather than all services of the base service type.
+ *
+ * Note that a non-empty service type must be specified with {@link #setServiceType} if a
+ * non-empty subtype is specified by this method.
+ */
+ @NonNull
+ public Builder setSubtype(@Nullable String subtype) {
+ mSubtype = subtype;
+ return this;
+ }
+
+ /**
+ * Sets the {@link Network} on which the discovery queries should be sent.
+ *
+ * @param network the discovery network or {@code null} if the query should be sent on
+ * all supported networks
+ */
+ @NonNull
+ public Builder setNetwork(@Nullable Network network) {
+ mNetwork = network;
+ return this;
+ }
+
+ /**
+ * Creates a new {@link DiscoveryRequest} object.
+ */
+ @NonNull
+ public DiscoveryRequest build() {
+ return new DiscoveryRequest(mProtocolType, mServiceType, mSubtype, mNetwork);
+ }
+ }
+}
diff --git a/framework-t/src/android/net/nsd/INsdManagerCallback.aidl b/framework-t/src/android/net/nsd/INsdManagerCallback.aidl
index d89bfa9..55820ec 100644
--- a/framework-t/src/android/net/nsd/INsdManagerCallback.aidl
+++ b/framework-t/src/android/net/nsd/INsdManagerCallback.aidl
@@ -17,6 +17,7 @@
package android.net.nsd;
import android.os.Messenger;
+import android.net.nsd.DiscoveryRequest;
import android.net.nsd.NsdServiceInfo;
/**
@@ -24,7 +25,7 @@
* @hide
*/
oneway interface INsdManagerCallback {
- void onDiscoverServicesStarted(int listenerKey, in NsdServiceInfo info);
+ void onDiscoverServicesStarted(int listenerKey, in DiscoveryRequest discoveryRequest);
void onDiscoverServicesFailed(int listenerKey, int error);
void onServiceFound(int listenerKey, in NsdServiceInfo info);
void onServiceLost(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
index b03eb29..9a31278 100644
--- a/framework-t/src/android/net/nsd/INsdServiceConnector.aidl
+++ b/framework-t/src/android/net/nsd/INsdServiceConnector.aidl
@@ -17,6 +17,7 @@
package android.net.nsd;
import android.net.nsd.AdvertisingRequest;
+import android.net.nsd.DiscoveryRequest;
import android.net.nsd.INsdManagerCallback;
import android.net.nsd.IOffloadEngine;
import android.net.nsd.NsdServiceInfo;
@@ -30,7 +31,7 @@
interface INsdServiceConnector {
void registerService(int listenerKey, in AdvertisingRequest advertisingRequest);
void unregisterService(int listenerKey);
- void discoverServices(int listenerKey, in NsdServiceInfo serviceInfo);
+ void discoverServices(int listenerKey, in DiscoveryRequest discoveryRequest);
void stopDiscovery(int listenerKey);
void resolveService(int listenerKey, in NsdServiceInfo serviceInfo);
void startDaemon();
@@ -39,4 +40,4 @@
void unregisterServiceInfoCallback(int listenerKey);
void registerOffloadEngine(String ifaceName, in IOffloadEngine cb, long offloadCapabilities, long offloadType);
void unregisterOffloadEngine(in IOffloadEngine cb);
-}
\ No newline at end of file
+}
diff --git a/framework-t/src/android/net/nsd/NsdManager.java b/framework-t/src/android/net/nsd/NsdManager.java
index b4f2be9..f6e1324 100644
--- a/framework-t/src/android/net/nsd/NsdManager.java
+++ b/framework-t/src/android/net/nsd/NsdManager.java
@@ -158,13 +158,36 @@
"com.android.net.flags.nsd_subtypes_support_enabled";
static final String ADVERTISE_REQUEST_API =
"com.android.net.flags.advertise_request_api";
+ static final String NSD_CUSTOM_HOSTNAME_ENABLED =
+ "com.android.net.flags.nsd_custom_hostname_enabled";
}
/**
* A regex for the acceptable format of a type or subtype label.
* @hide
*/
- public static final String TYPE_SUBTYPE_LABEL_REGEX = "_[a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]";
+ public static final String TYPE_LABEL_REGEX = "_[a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]";
+
+ /**
+ * A regex for the acceptable format of a subtype label.
+ *
+ * As per RFC 6763 7.1, "Subtype strings are not required to begin with an underscore, though
+ * they often do.", and "Subtype strings [...] may be constructed using arbitrary 8-bit data
+ * values. In many cases these data values may be UTF-8 [RFC3629] representations of text, or
+ * even (as in the example above) plain ASCII [RFC20], but they do not have to be.".
+ *
+ * This regex is overly conservative as it mandates the underscore and only allows printable
+ * ASCII characters (codes 0x20 to 0x7e, space to tilde), except for comma (0x2c) and dot
+ * (0x2e); so the NsdManager API does not allow everything the RFC allows. This may be revisited
+ * in the future, but using arbitrary bytes makes logging and testing harder, and using other
+ * characters would probably be a bad idea for interoperability for apps.
+ * @hide
+ */
+ public static final String SUBTYPE_LABEL_REGEX = "_["
+ + "\\x20-\\x2b"
+ + "\\x2d"
+ + "\\x2f-\\x7e"
+ + "]{1,62}";
/**
* A regex for the acceptable format of a service type specification.
@@ -177,14 +200,14 @@
public static final String TYPE_REGEX =
// Optional leading subtype (_subtype._type._tcp)
// (?: xxx) is a non-capturing parenthesis, don't capture the dot
- "^(?:(" + TYPE_SUBTYPE_LABEL_REGEX + ")\\.)?"
+ "^(?:(" + SUBTYPE_LABEL_REGEX + ")\\.)?"
// Actual type (_type._tcp.local)
- + "(" + TYPE_SUBTYPE_LABEL_REGEX + "\\._(?:tcp|udp))"
+ + "(" + TYPE_LABEL_REGEX + "\\._(?:tcp|udp))"
// Drop '.' at the end of service type that is compatible with old backend.
// e.g. allow "_type._tcp.local."
+ "\\.?"
// Optional subtype after comma, for "_type._tcp,_subtype1,_subtype2" format
- + "((?:," + TYPE_SUBTYPE_LABEL_REGEX + ")*)"
+ + "((?:," + SUBTYPE_LABEL_REGEX + ")*)"
+ "$";
/**
@@ -360,6 +383,8 @@
@GuardedBy("mMapLock")
private final SparseArray<NsdServiceInfo> mServiceMap = new SparseArray<>();
@GuardedBy("mMapLock")
+ private final SparseArray<DiscoveryRequest> mDiscoveryMap = 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
@@ -715,6 +740,12 @@
mServHandler.sendMessage(mServHandler.obtainMessage(message, 0, listenerKey, info));
}
+ private void sendDiscoveryRequest(
+ int message, int listenerKey, DiscoveryRequest discoveryRequest) {
+ mServHandler.sendMessage(
+ mServHandler.obtainMessage(message, 0, listenerKey, discoveryRequest));
+ }
+
private void sendError(int message, int listenerKey, int error) {
mServHandler.sendMessage(mServHandler.obtainMessage(message, error, listenerKey));
}
@@ -724,8 +755,8 @@
}
@Override
- public void onDiscoverServicesStarted(int listenerKey, NsdServiceInfo info) {
- sendInfo(DISCOVER_SERVICES_STARTED, listenerKey, info);
+ public void onDiscoverServicesStarted(int listenerKey, DiscoveryRequest discoveryRequest) {
+ sendDiscoveryRequest(DISCOVER_SERVICES_STARTED, listenerKey, discoveryRequest);
}
@Override
@@ -1003,10 +1034,12 @@
final Object obj = message.obj;
final Object listener;
final NsdServiceInfo ns;
+ final DiscoveryRequest discoveryRequest;
final Executor executor;
synchronized (mMapLock) {
listener = mListenerMap.get(key);
ns = mServiceMap.get(key);
+ discoveryRequest = mDiscoveryMap.get(key);
executor = mExecutorMap.get(key);
}
if (listener == null) {
@@ -1014,17 +1047,22 @@
return;
}
if (DBG) {
- Log.d(TAG, "received " + nameOf(what) + " for key " + key + ", service " + ns);
+ if (discoveryRequest != null) {
+ Log.d(TAG, "received " + nameOf(what) + " for key " + key + ", discovery "
+ + discoveryRequest);
+ } else {
+ Log.d(TAG, "received " + nameOf(what) + " for key " + key + ", service " + ns);
+ }
}
switch (what) {
case DISCOVER_SERVICES_STARTED:
- final String s = getNsdServiceInfoType((NsdServiceInfo) obj);
+ final String s = getNsdServiceInfoType((DiscoveryRequest) obj);
executor.execute(() -> ((DiscoveryListener) listener).onDiscoveryStarted(s));
break;
case DISCOVER_SERVICES_FAILED:
removeListener(key);
executor.execute(() -> ((DiscoveryListener) listener).onStartDiscoveryFailed(
- getNsdServiceInfoType(ns), errorCode));
+ getNsdServiceInfoType(discoveryRequest), errorCode));
break;
case SERVICE_FOUND:
executor.execute(() -> ((DiscoveryListener) listener).onServiceFound(
@@ -1039,12 +1077,12 @@
// the effect for the client is indistinguishable from STOP_DISCOVERY_SUCCEEDED
removeListener(key);
executor.execute(() -> ((DiscoveryListener) listener).onStopDiscoveryFailed(
- getNsdServiceInfoType(ns), errorCode));
+ getNsdServiceInfoType(discoveryRequest), errorCode));
break;
case STOP_DISCOVERY_SUCCEEDED:
removeListener(key);
executor.execute(() -> ((DiscoveryListener) listener).onDiscoveryStopped(
- getNsdServiceInfoType(ns)));
+ getNsdServiceInfoType(discoveryRequest)));
break;
case REGISTER_SERVICE_FAILED:
removeListener(key);
@@ -1117,21 +1155,33 @@
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;
+ private int putListener(Object listener, Executor e, NsdServiceInfo serviceInfo) {
synchronized (mMapLock) {
- int valueIndex = mListenerMap.indexOfValue(listener);
+ return putListener(listener, e, mServiceMap, serviceInfo);
+ }
+ }
+
+ private int putListener(Object listener, Executor e, DiscoveryRequest discoveryRequest) {
+ synchronized (mMapLock) {
+ return putListener(listener, e, mDiscoveryMap, discoveryRequest);
+ }
+ }
+
+ // Assert that the listener is not in the map, then add it and returns its key
+ private <T> int putListener(Object listener, Executor e, SparseArray<T> map, T value) {
+ synchronized (mMapLock) {
+ checkListener(listener);
+ final int key;
+ final 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);
+ map.put(key, value);
mExecutorMap.put(key, e);
+ return key;
}
- return key;
}
private int updateRegisteredListener(Object listener, Executor e, NsdServiceInfo s) {
@@ -1148,6 +1198,7 @@
synchronized (mMapLock) {
mListenerMap.remove(key);
mServiceMap.remove(key);
+ mDiscoveryMap.remove(key);
mExecutorMap.remove(key);
}
}
@@ -1163,9 +1214,9 @@
}
}
- private static String getNsdServiceInfoType(NsdServiceInfo s) {
- if (s == null) return "?";
- return s.getServiceType();
+ private static String getNsdServiceInfoType(DiscoveryRequest r) {
+ if (r == null) return "?";
+ return r.getServiceType();
}
/**
@@ -1208,7 +1259,7 @@
*/
public void registerService(@NonNull NsdServiceInfo serviceInfo, int protocolType,
@NonNull Executor executor, @NonNull RegistrationListener listener) {
- checkServiceInfo(serviceInfo);
+ checkServiceInfoForRegistration(serviceInfo);
checkProtocol(protocolType);
final AdvertisingRequest.Builder builder = new AdvertisingRequest.Builder(serviceInfo,
protocolType);
@@ -1267,7 +1318,10 @@
* @return Type and comma-separated list of subtypes, or null if invalid format.
*/
@Nullable
- private static Pair<String, String> getTypeAndSubtypes(@NonNull String typeWithSubtype) {
+ private static Pair<String, String> getTypeAndSubtypes(@Nullable String typeWithSubtype) {
+ if (typeWithSubtype == null) {
+ return null;
+ }
final Matcher matcher = Pattern.compile(TYPE_REGEX).matcher(typeWithSubtype);
if (!matcher.matches()) return null;
// Reject specifications using leading subtypes with a dot
@@ -1298,10 +1352,7 @@
@NonNull RegistrationListener listener) {
final NsdServiceInfo serviceInfo = advertisingRequest.getServiceInfo();
final int protocolType = advertisingRequest.getProtocolType();
- if (serviceInfo.getPort() <= 0) {
- throw new IllegalArgumentException("Invalid port number");
- }
- checkServiceInfo(serviceInfo);
+ checkServiceInfoForRegistration(serviceInfo);
checkProtocol(protocolType);
final int key;
// For update only request, the old listener has to be reused
@@ -1406,15 +1457,44 @@
if (TextUtils.isEmpty(serviceType)) {
throw new IllegalArgumentException("Service type cannot be empty");
}
- checkProtocol(protocolType);
+ DiscoveryRequest request = new DiscoveryRequest.Builder(protocolType, serviceType)
+ .setNetwork(network).build();
+ discoverServices(request, executor, listener);
+ }
- NsdServiceInfo s = new NsdServiceInfo();
- s.setServiceType(serviceType);
- s.setNetwork(network);
-
- int key = putListener(listener, executor, s);
+ /**
+ * Initiates 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 discoveryRequest the {@link DiscoveryRequest} object which specifies the discovery
+ * parameters such as service type, subtype and network
+ * @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}.
+ */
+ @FlaggedApi(Flags.NSD_SUBTYPES_SUPPORT_ENABLED)
+ public void discoverServices(@NonNull DiscoveryRequest discoveryRequest,
+ @NonNull Executor executor, @NonNull DiscoveryListener listener) {
+ int key = putListener(listener, executor, discoveryRequest);
try {
- mService.discoverServices(key, s);
+ mService.discoverServices(key, discoveryRequest);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
@@ -1465,12 +1545,10 @@
throw new IllegalArgumentException("Service type cannot be empty");
}
Objects.requireNonNull(networkRequest, "NetworkRequest cannot be null");
- checkProtocol(protocolType);
+ DiscoveryRequest discoveryRequest =
+ new DiscoveryRequest.Builder(protocolType, serviceType).build();
- NsdServiceInfo s = new NsdServiceInfo();
- s.setServiceType(serviceType);
-
- final int baseListenerKey = putListener(listener, executor, s);
+ final int baseListenerKey = putListener(listener, executor, discoveryRequest);
final PerNetworkDiscoveryTracker discoveryInfo = new PerNetworkDiscoveryTracker(
serviceType, protocolType, executor, listener);
@@ -1551,7 +1629,7 @@
@Deprecated
public void resolveService(@NonNull NsdServiceInfo serviceInfo,
@NonNull Executor executor, @NonNull ResolveListener listener) {
- checkServiceInfo(serviceInfo);
+ checkServiceInfoForResolution(serviceInfo);
int key = putListener(listener, executor, serviceInfo);
try {
mService.resolveService(key, serviceInfo);
@@ -1602,9 +1680,10 @@
* @param executor Executor to run callbacks with
* @param listener to receive callback upon service update
*/
+ // TODO: use {@link DiscoveryRequest} to specify the service to be subscribed
public void registerServiceInfoCallback(@NonNull NsdServiceInfo serviceInfo,
@NonNull Executor executor, @NonNull ServiceInfoCallback listener) {
- checkServiceInfo(serviceInfo);
+ checkServiceInfoForResolution(serviceInfo);
int key = putListener(listener, executor, serviceInfo);
try {
mService.registerServiceInfoCallback(key, serviceInfo);
@@ -1643,13 +1722,13 @@
Objects.requireNonNull(listener, "listener cannot be null");
}
- private static void checkProtocol(int protocolType) {
+ static void checkProtocol(int protocolType) {
if (protocolType != PROTOCOL_DNS_SD) {
throw new IllegalArgumentException("Unsupported protocol");
}
}
- private static void checkServiceInfo(NsdServiceInfo serviceInfo) {
+ private static void checkServiceInfoForResolution(NsdServiceInfo serviceInfo) {
Objects.requireNonNull(serviceInfo, "NsdServiceInfo cannot be null");
if (TextUtils.isEmpty(serviceInfo.getServiceName())) {
throw new IllegalArgumentException("Service name cannot be empty");
@@ -1658,4 +1737,46 @@
throw new IllegalArgumentException("Service type cannot be empty");
}
}
+
+ /**
+ * Check if the {@link NsdServiceInfo} is valid for registration.
+ *
+ * The following can be registered:
+ * - A service with an optional host.
+ * - A hostname with addresses.
+ *
+ * Note that:
+ * - When registering a service, the service name, service type and port must be specified. If
+ * hostname is specified, the host addresses can optionally be specified.
+ * - When registering a host without a service, the addresses must be specified.
+ *
+ * @hide
+ */
+ public static void checkServiceInfoForRegistration(NsdServiceInfo serviceInfo) {
+ Objects.requireNonNull(serviceInfo, "NsdServiceInfo cannot be null");
+ boolean hasServiceName = !TextUtils.isEmpty(serviceInfo.getServiceName());
+ boolean hasServiceType = !TextUtils.isEmpty(serviceInfo.getServiceType());
+ boolean hasHostname = !TextUtils.isEmpty(serviceInfo.getHostname());
+ boolean hasHostAddresses = !CollectionUtils.isEmpty(serviceInfo.getHostAddresses());
+
+ if (serviceInfo.getPort() < 0) {
+ throw new IllegalArgumentException("Invalid port");
+ }
+
+ if (hasServiceType || hasServiceName || (serviceInfo.getPort() > 0)) {
+ if (!(hasServiceType && hasServiceName && (serviceInfo.getPort() > 0))) {
+ throw new IllegalArgumentException(
+ "The service type, service name or port is missing");
+ }
+ }
+
+ if (!hasServiceType && !hasHostname) {
+ throw new IllegalArgumentException("No service or host specified in NsdServiceInfo");
+ }
+
+ if (!hasServiceType && hasHostname && !hasHostAddresses) {
+ // TODO: b/317946010 - This may be allowed when it supports registering KEY RR.
+ throw new IllegalArgumentException("No host addresses specified in NsdServiceInfo");
+ }
+ }
}
diff --git a/framework-t/src/android/net/nsd/NsdServiceInfo.java b/framework-t/src/android/net/nsd/NsdServiceInfo.java
index ac4ea23..146d4ca 100644
--- a/framework-t/src/android/net/nsd/NsdServiceInfo.java
+++ b/framework-t/src/android/net/nsd/NsdServiceInfo.java
@@ -49,8 +49,10 @@
private static final String TAG = "NsdServiceInfo";
+ @Nullable
private String mServiceName;
+ @Nullable
private String mServiceType;
private final Set<String> mSubtypes;
@@ -59,6 +61,9 @@
private final List<InetAddress> mHostAddresses;
+ @Nullable
+ private String mHostname;
+
private int mPort;
@Nullable
@@ -90,6 +95,7 @@
mSubtypes = new ArraySet<>(other.getSubtypes());
mTxtRecord = new ArrayMap<>(other.mTxtRecord);
mHostAddresses = new ArrayList<>(other.getHostAddresses());
+ mHostname = other.getHostname();
mPort = other.getPort();
mNetwork = other.getNetwork();
mInterfaceIndex = other.getInterfaceIndex();
@@ -169,6 +175,43 @@
}
/**
+ * Get the hostname.
+ *
+ * <p>When a service is resolved, it returns the hostname of the resolved service . The top
+ * level domain ".local." is omitted.
+ *
+ * <p>For example, it returns "MyHost" when the service's hostname is "MyHost.local.".
+ *
+ * @hide
+ */
+// @FlaggedApi(NsdManager.Flags.NSD_CUSTOM_HOSTNAME_ENABLED)
+ @Nullable
+ public String getHostname() {
+ return mHostname;
+ }
+
+ /**
+ * Set a custom hostname for this service instance for registration.
+ *
+ * <p>A hostname must be in ".local." domain. The ".local." must be omitted when calling this
+ * method.
+ *
+ * <p>For example, you should call setHostname("MyHost") to use the hostname "MyHost.local.".
+ *
+ * <p>If a hostname is set with this method, the addresses set with {@link #setHostAddresses}
+ * will be registered with the hostname.
+ *
+ * <p>If the hostname is null (which is the default for a new {@link NsdServiceInfo}), a random
+ * hostname is used and the addresses of this device will be registered.
+ *
+ * @hide
+ */
+// @FlaggedApi(NsdManager.Flags.NSD_CUSTOM_HOSTNAME_ENABLED)
+ public void setHostname(@Nullable String hostname) {
+ mHostname = hostname;
+ }
+
+ /**
* Unpack txt information from a base-64 encoded byte array.
*
* @param txtRecordsRawBytes The raw base64 encoded byte array.
@@ -454,6 +497,7 @@
.append(", type: ").append(mServiceType)
.append(", subtypes: ").append(TextUtils.join(", ", mSubtypes))
.append(", hostAddresses: ").append(TextUtils.join(", ", mHostAddresses))
+ .append(", hostname: ").append(mHostname)
.append(", port: ").append(mPort)
.append(", network: ").append(mNetwork);
@@ -494,6 +538,7 @@
for (InetAddress address : mHostAddresses) {
InetAddressUtils.parcelInetAddress(dest, address, flags);
}
+ dest.writeString(mHostname);
}
/** Implement the Parcelable interface */
@@ -523,6 +568,7 @@
for (int i = 0; i < size; i++) {
info.mHostAddresses.add(InetAddressUtils.unparcelInetAddress(in));
}
+ info.mHostname = in.readString();
return info;
}
diff --git a/framework/Android.bp b/framework/Android.bp
index 432cabf..1356eea 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_fwk_core_networking",
// See: http://go/android-license-faq
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -106,9 +107,6 @@
apex_available: [
"com.android.tethering",
],
- lint: {
- strict_updatability_linting: true,
- },
}
java_library {
@@ -139,9 +137,6 @@
"framework-wifi.stubs.module_lib",
],
visibility: ["//packages/modules/Connectivity:__subpackages__"],
- lint: {
- baseline_filename: "lint-baseline.xml",
- },
}
java_defaults {
@@ -259,9 +254,6 @@
apex_available: [
"com.android.tethering",
],
- lint: {
- baseline_filename: "lint-baseline.xml",
- },
}
java_genrule {
@@ -322,9 +314,6 @@
java_library {
name: "framework-connectivity-module-api-stubs-including-flagged",
srcs: [":framework-connectivity-module-api-stubs-including-flagged-droidstubs"],
- lint: {
- baseline_filename: "lint-baseline.xml",
- },
}
// Library providing limited APIs within the connectivity module, so that R+ components like
@@ -349,7 +338,4 @@
visibility: [
"//packages/modules/Connectivity/Tethering:__subpackages__",
],
- lint: {
- baseline_filename: "lint-baseline.xml",
- },
}
diff --git a/framework/aidl-export/android/net/nsd/DiscoveryRequest.aidl b/framework/aidl-export/android/net/nsd/DiscoveryRequest.aidl
new file mode 100644
index 0000000..481a066
--- /dev/null
+++ b/framework/aidl-export/android/net/nsd/DiscoveryRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 DiscoveryRequest;
diff --git a/framework/api/current.txt b/framework/api/current.txt
index 6860c3c..ef8415c 100644
--- a/framework/api/current.txt
+++ b/framework/api/current.txt
@@ -315,6 +315,7 @@
method @Nullable public android.net.NetworkSpecifier getNetworkSpecifier();
method public int getOwnerUid();
method public int getSignalStrength();
+ method @FlaggedApi("com.android.net.flags.request_restricted_wifi") @NonNull public java.util.Set<java.lang.Integer> getSubscriptionIds();
method @Nullable public android.net.TransportInfo getTransportInfo();
method public boolean hasCapability(int);
method public boolean hasEnterpriseId(int);
@@ -332,6 +333,7 @@
field public static final int NET_CAPABILITY_IA = 7; // 0x7
field public static final int NET_CAPABILITY_IMS = 4; // 0x4
field public static final int NET_CAPABILITY_INTERNET = 12; // 0xc
+ field @FlaggedApi("com.android.net.flags.net_capability_local_network") public static final int NET_CAPABILITY_LOCAL_NETWORK = 36; // 0x24
field public static final int NET_CAPABILITY_MCX = 23; // 0x17
field public static final int NET_CAPABILITY_MMS = 0; // 0x0
field public static final int NET_CAPABILITY_MMTEL = 33; // 0x21
@@ -360,6 +362,7 @@
field public static final int TRANSPORT_CELLULAR = 0; // 0x0
field public static final int TRANSPORT_ETHERNET = 3; // 0x3
field public static final int TRANSPORT_LOWPAN = 6; // 0x6
+ field @FlaggedApi("com.android.net.flags.support_transport_satellite") public static final int TRANSPORT_SATELLITE = 10; // 0xa
field public static final int TRANSPORT_THREAD = 9; // 0x9
field public static final int TRANSPORT_USB = 8; // 0x8
field public static final int TRANSPORT_VPN = 4; // 0x4
@@ -418,6 +421,7 @@
method public int describeContents();
method @NonNull public int[] getCapabilities();
method @Nullable public android.net.NetworkSpecifier getNetworkSpecifier();
+ method @FlaggedApi("com.android.net.flags.request_restricted_wifi") @NonNull public java.util.Set<java.lang.Integer> getSubscriptionIds();
method @NonNull public int[] getTransportTypes();
method public boolean hasCapability(int);
method public boolean hasTransport(int);
@@ -437,6 +441,7 @@
method @NonNull public android.net.NetworkRequest.Builder setIncludeOtherUidNetworks(boolean);
method @Deprecated public android.net.NetworkRequest.Builder setNetworkSpecifier(String);
method public android.net.NetworkRequest.Builder setNetworkSpecifier(android.net.NetworkSpecifier);
+ method @FlaggedApi("com.android.net.flags.request_restricted_wifi") @NonNull public android.net.NetworkRequest.Builder setSubscriptionIds(@NonNull java.util.Set<java.lang.Integer>);
}
public class ParseException extends java.lang.RuntimeException {
diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt
index e812024..bef29a4 100644
--- a/framework/api/system-current.txt
+++ b/framework/api/system-current.txt
@@ -307,7 +307,6 @@
method @NonNull public int[] getAdministratorUids();
method @Nullable public static String getCapabilityCarrierName(int);
method @Nullable public String getSsid();
- method @NonNull public java.util.Set<java.lang.Integer> getSubscriptionIds();
method @NonNull public int[] getTransportTypes();
method @Nullable public java.util.List<android.net.Network> getUnderlyingNetworks();
method public boolean isPrivateDnsBroken();
@@ -373,7 +372,6 @@
public static class NetworkRequest.Builder {
method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkRequest.Builder setSignalStrength(int);
- method @NonNull public android.net.NetworkRequest.Builder setSubscriptionIds(@NonNull java.util.Set<java.lang.Integer>);
}
public final class NetworkScore implements android.os.Parcelable {
diff --git a/framework/jni/android_net_NetworkUtils.cpp b/framework/jni/android_net_NetworkUtils.cpp
index 51eaf1c..3779a00 100644
--- a/framework/jni/android_net_NetworkUtils.cpp
+++ b/framework/jni/android_net_NetworkUtils.cpp
@@ -255,6 +255,10 @@
return bpf::isKernel64Bit();
}
+static jboolean android_net_utils_isKernelX86(JNIEnv *env, jclass clazz) {
+ return bpf::isX86();
+}
+
// ----------------------------------------------------------------------------
/*
@@ -278,6 +282,7 @@
{ "setsockoptBytes", "(Ljava/io/FileDescriptor;II[B)V",
(void*) android_net_utils_setsockoptBytes},
{ "isKernel64Bit", "()Z", (void*) android_net_utils_isKernel64Bit },
+ { "isKernelX86", "()Z", (void*) android_net_utils_isKernelX86 },
};
// clang-format on
diff --git a/framework/lint-baseline.xml b/framework/lint-baseline.xml
index f68aad7..2c0b15f 100644
--- a/framework/lint-baseline.xml
+++ b/framework/lint-baseline.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-dev" type="baseline" dependencies="true" variant="all" version="8.0.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
<issue
id="NewApi"
@@ -8,78 +8,177 @@
errorLine2=" ~~~~~~~~~~~~~">
<location
file="packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java"
- line="2456"
+ line="2490"
column="71"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.net.Proxy#setHttpProxyConfiguration`"
- errorLine1=" Proxy.setHttpProxyConfiguration(getInstance().getDefaultProxy());"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java"
- line="5323"
- column="23"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.os.Build#isDebuggable`"
- errorLine1=" if (!Build.isDebuggable()) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/framework/src/android/net/ConnectivitySettingsManager.java"
- line="1072"
- column="24"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.os.UserHandle#getUid`"
- errorLine1=" final int end = nextUser.getUid(0 /* appId */) - 1;"
- errorLine2=" ~~~~~~">
- <location
- file="packages/modules/Connectivity/framework/src/android/net/UidRange.java"
- line="50"
- column="34"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.os.UserHandle#getUid`"
- errorLine1=" final int start = user.getUid(0 /* appId */);"
- errorLine2=" ~~~~~~">
- <location
- file="packages/modules/Connectivity/framework/src/android/net/UidRange.java"
- line="49"
- column="32"/>
- </issue>
-
- <issue
- id="NewApi"
message="Call requires API level 31 (current min is 30): `android.provider.Settings#checkAndNoteWriteSettingsOperation`"
errorLine1=" return Settings.checkAndNoteWriteSettingsOperation(context, uid, callingPackage,"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java"
- line="2799"
+ line="2853"
column="25"/>
</issue>
<issue
id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.net.Proxy#setHttpProxyConfiguration`"
+ errorLine1=" Proxy.setHttpProxyConfiguration(getInstance().getDefaultProxy());"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java"
+ line="5422"
+ column="23"/>
+ </issue>
+
+ <issue
+ id="NewApi"
message="Call requires API level 31 (current min is 30): `java.net.InetAddress#clearDnsCache`"
errorLine1=" InetAddress.clearDnsCache();"
errorLine2=" ~~~~~~~~~~~~~">
<location
file="packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java"
- line="5329"
+ line="5428"
column="25"/>
</issue>
<issue
id="NewApi"
+ message="Call requires API level 31 (current min is 30): `libcore.net.event.NetworkEventDispatcher#dispatchNetworkConfigurationChange`"
+ errorLine1=" NetworkEventDispatcher.getInstance().dispatchNetworkConfigurationChange();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java"
+ line="5431"
+ column="50"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `libcore.net.event.NetworkEventDispatcher#getInstance`"
+ errorLine1=" NetworkEventDispatcher.getInstance().dispatchNetworkConfigurationChange();"
+ errorLine2=" ~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java"
+ line="5431"
+ column="36"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.os.Build#isDebuggable`"
+ errorLine1=" if (!Build.isDebuggable()) {"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/ConnectivitySettingsManager.java"
+ line="1095"
+ column="24"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Field requires API level 31 (current min is 30): `android.system.OsConstants#ENONET`"
+ errorLine1=' new DnsException(ERROR_SYSTEM, new ErrnoException("resNetworkQuery", ENONET))));'
+ errorLine2=" ~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/DnsResolver.java"
+ line="367"
+ column="90"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
+ errorLine1=" IoUtils.closeQuietly(socket);"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/util/DnsUtils.java"
+ line="181"
+ column="21"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
+ errorLine1=" IoUtils.closeQuietly(socket);"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/util/DnsUtils.java"
+ line="373"
+ column="21"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
+ errorLine1=" IoUtils.closeQuietly(is);"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/core/java/com/android/internal/util/FileRotator.java"
+ line="171"
+ column="33"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
+ errorLine1=" IoUtils.closeQuietly(zos);"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/core/java/com/android/internal/util/FileRotator.java"
+ line="178"
+ column="21"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
+ errorLine1=" IoUtils.closeQuietly(bis);"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/core/java/com/android/internal/util/FileRotator.java"
+ line="401"
+ column="21"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
+ errorLine1=" IoUtils.closeQuietly(bos);"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/core/java/com/android/internal/util/FileRotator.java"
+ line="416"
+ column="21"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `libcore.net.InetAddressUtils#isNumericAddress`"
+ errorLine1=" return InetAddressUtils.isNumericAddress(address);"
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/InetAddresses.java"
+ line="46"
+ column="33"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `libcore.net.InetAddressUtils#parseNumericAddress`"
+ errorLine1=" return InetAddressUtils.parseNumericAddress(address);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/InetAddresses.java"
+ line="63"
+ column="33"/>
+ </issue>
+
+ <issue
+ id="NewApi"
message="Call requires API level 31 (current min is 30): `java.net.InetAddress#getAllByNameOnNet`"
errorLine1=" return InetAddress.getAllByNameOnNet(host, getNetIdForResolv());"
errorLine2=" ~~~~~~~~~~~~~~~~~">
@@ -103,17 +202,6 @@
<issue
id="NewApi"
message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
- errorLine1=" IoUtils.closeQuietly(is);"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="frameworks/base/core/java/com/android/internal/util/FileRotator.java"
- line="168"
- column="33"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
errorLine1=" if (failed) IoUtils.closeQuietly(socket);"
errorLine2=" ~~~~~~~~~~~~">
<location
@@ -157,105 +245,6 @@
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
- errorLine1=" IoUtils.closeQuietly(bis);"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="frameworks/base/core/java/com/android/internal/util/FileRotator.java"
- line="391"
- column="21"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
- errorLine1=" IoUtils.closeQuietly(bos);"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="frameworks/base/core/java/com/android/internal/util/FileRotator.java"
- line="406"
- column="21"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
- errorLine1=" IoUtils.closeQuietly(socket);"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/framework/src/android/net/util/DnsUtils.java"
- line="181"
- column="21"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
- errorLine1=" IoUtils.closeQuietly(socket);"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/framework/src/android/net/util/DnsUtils.java"
- line="373"
- column="21"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
- errorLine1=" IoUtils.closeQuietly(zos);"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="frameworks/base/core/java/com/android/internal/util/FileRotator.java"
- line="175"
- column="21"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `libcore.net.InetAddressUtils#isNumericAddress`"
- errorLine1=" return InetAddressUtils.isNumericAddress(address);"
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/framework/src/android/net/InetAddresses.java"
- line="46"
- column="33"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `libcore.net.InetAddressUtils#parseNumericAddress`"
- errorLine1=" return InetAddressUtils.parseNumericAddress(address);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/framework/src/android/net/InetAddresses.java"
- line="63"
- column="33"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `libcore.net.event.NetworkEventDispatcher#dispatchNetworkConfigurationChange`"
- errorLine1=" NetworkEventDispatcher.getInstance().dispatchNetworkConfigurationChange();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java"
- line="5332"
- column="50"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `libcore.net.event.NetworkEventDispatcher#getInstance`"
- errorLine1=" NetworkEventDispatcher.getInstance().dispatchNetworkConfigurationChange();"
- errorLine2=" ~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java"
- line="5332"
- column="36"/>
- </issue>
-
- <issue
- id="NewApi"
message="Call requires API level 31 (current min is 30): `libcore.net.http.HttpURLConnectionFactory#createInstance`"
errorLine1=" HttpURLConnectionFactory urlConnectionFactory = HttpURLConnectionFactory.createInstance();"
errorLine2=" ~~~~~~~~~~~~~~">
@@ -267,17 +256,6 @@
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `libcore.net.http.HttpURLConnectionFactory#openConnection`"
- errorLine1=" return urlConnectionFactory.openConnection(url, socketFactory, proxy);"
- errorLine2=" ~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/framework/src/android/net/Network.java"
- line="372"
- column="37"/>
- </issue>
-
- <issue
- id="NewApi"
message="Call requires API level 31 (current min is 30): `libcore.net.http.HttpURLConnectionFactory#setDns`"
errorLine1=" urlConnectionFactory.setDns(dnsLookup); // Let traffic go via dnsLookup"
errorLine2=" ~~~~~~">
@@ -300,35 +278,13 @@
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `new android.net.EthernetNetworkSpecifier`"
- errorLine1=" return setNetworkSpecifier(new EthernetNetworkSpecifier(networkSpecifier));"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `libcore.net.http.HttpURLConnectionFactory#openConnection`"
+ errorLine1=" return urlConnectionFactory.openConnection(url, socketFactory, proxy);"
+ errorLine2=" ~~~~~~~~~~~~~~">
<location
- file="packages/modules/Connectivity/framework/src/android/net/NetworkRequest.java"
- line="525"
- column="48"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Cast from `EthernetNetworkSpecifier` to `NetworkSpecifier` requires API level 31 (current min is 30)"
- errorLine1=" return setNetworkSpecifier(new EthernetNetworkSpecifier(networkSpecifier));"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/framework/src/android/net/NetworkRequest.java"
- line="525"
- column="48"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Class requires API level 31 (current min is 30): `android.telephony.data.EpsBearerQosSessionAttributes`"
- errorLine1=" (EpsBearerQosSessionAttributes)attributes));"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/framework/src/android/net/NetworkAgent.java"
- line="1421"
- column="22"/>
+ file="packages/modules/Connectivity/framework/src/android/net/Network.java"
+ line="372"
+ column="37"/>
</issue>
<issue
@@ -338,18 +294,18 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/modules/Connectivity/framework/src/android/net/NetworkAgent.java"
- line="1418"
+ line="1462"
column="35"/>
</issue>
<issue
id="NewApi"
- message="Class requires API level 31 (current min is 30): `android.telephony.data.NrQosSessionAttributes`"
- errorLine1=" (NrQosSessionAttributes)attributes));"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
+ message="Class requires API level 31 (current min is 30): `android.telephony.data.EpsBearerQosSessionAttributes`"
+ errorLine1=" (EpsBearerQosSessionAttributes)attributes));"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/modules/Connectivity/framework/src/android/net/NetworkAgent.java"
- line="1425"
+ line="1465"
column="22"/>
</issue>
@@ -360,8 +316,63 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/modules/Connectivity/framework/src/android/net/NetworkAgent.java"
- line="1422"
+ line="1466"
column="42"/>
</issue>
+ <issue
+ id="NewApi"
+ message="Class requires API level 31 (current min is 30): `android.telephony.data.NrQosSessionAttributes`"
+ errorLine1=" (NrQosSessionAttributes)attributes));"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/NetworkAgent.java"
+ line="1469"
+ column="22"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `new android.net.EthernetNetworkSpecifier`"
+ errorLine1=" return setNetworkSpecifier(new EthernetNetworkSpecifier(networkSpecifier));"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/NetworkRequest.java"
+ line="553"
+ column="48"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Cast from `EthernetNetworkSpecifier` to `NetworkSpecifier` requires API level 31 (current min is 30)"
+ errorLine1=" return setNetworkSpecifier(new EthernetNetworkSpecifier(networkSpecifier));"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/NetworkRequest.java"
+ line="553"
+ column="48"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.os.UserHandle#getUid`"
+ errorLine1=" final int start = user.getUid(0 /* appId */);"
+ errorLine2=" ~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/UidRange.java"
+ line="49"
+ column="32"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.os.UserHandle#getUid`"
+ errorLine1=" final int end = nextUser.getUid(0 /* appId */) - 1;"
+ errorLine2=" ~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/UidRange.java"
+ line="50"
+ column="34"/>
+ </issue>
+
</issues>
\ No newline at end of file
diff --git a/framework/src/android/net/BpfNetMapsUtils.java b/framework/src/android/net/BpfNetMapsUtils.java
index 11d610c..0be30bb 100644
--- a/framework/src/android/net/BpfNetMapsUtils.java
+++ b/framework/src/android/net/BpfNetMapsUtils.java
@@ -125,13 +125,11 @@
return sj.toString();
}
- public static final boolean PRE_T = !SdkLevel.isAtLeastT();
-
/**
* Throw UnsupportedOperationException if SdkLevel is before T.
*/
public static void throwIfPreT(final String msg) {
- if (PRE_T) {
+ if (!SdkLevel.isAtLeastT()) {
throw new UnsupportedOperationException(msg);
}
}
diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java
index efae754..140a631 100644
--- a/framework/src/android/net/NetworkCapabilities.java
+++ b/framework/src/android/net/NetworkCapabilities.java
@@ -20,6 +20,7 @@
import static com.android.net.module.util.BitUtils.appendStringRepresentationOfBitMaskToStringBuilder;
import static com.android.net.module.util.BitUtils.describeDifferences;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.LongDef;
import android.annotation.NonNull;
@@ -29,9 +30,6 @@
import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.net.ConnectivityManager.NetworkCallback;
-// Can't be imported because aconfig tooling doesn't exist on udc-mainline-prod yet
-// See inner class Flags which mimics this for the time being
-// import android.net.flags.Flags;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
@@ -130,6 +128,12 @@
public static class Flags {
static final String FLAG_FORBIDDEN_CAPABILITY =
"com.android.net.flags.forbidden_capability";
+ static final String FLAG_NET_CAPABILITY_LOCAL_NETWORK =
+ "com.android.net.flags.net_capability_local_network";
+ static final String REQUEST_RESTRICTED_WIFI =
+ "com.android.net.flags.request_restricted_wifi";
+ static final String SUPPORT_TRANSPORT_SATELLITE =
+ "com.android.net.flags.support_transport_satellite";
}
/**
@@ -716,17 +720,13 @@
public static final int NET_CAPABILITY_PRIORITIZE_BANDWIDTH = 35;
/**
- * This is a local network, e.g. a tethering downstream or a P2P direct network.
+ * Indicates that this network is a local network, e.g. thread network
*
- * <p>
- * Note that local networks are not sent to callbacks by default. To receive callbacks about
- * them, the {@link NetworkRequest} instance must be prepared to see them, either by
- * adding the capability with {@link NetworkRequest.Builder#addCapability}, by removing
- * this forbidden capability with {@link NetworkRequest.Builder#removeForbiddenCapability},
- * or by clearing all capabilites with {@link NetworkRequest.Builder#clearCapabilities()}.
- * </p>
- * @hide
+ * Apps that target an SDK before {@link Build.VERSION_CODES.VANILLA_ICE_CREAM} will not see
+ * networks with this capability unless they explicitly set the NET_CAPABILITY_LOCAL_NETWORK
+ * in their NetworkRequests.
*/
+ @FlaggedApi(Flags.FLAG_NET_CAPABILITY_LOCAL_NETWORK)
public static final int NET_CAPABILITY_LOCAL_NETWORK = 36;
private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_LOCAL_NETWORK;
@@ -1257,6 +1257,7 @@
TRANSPORT_TEST,
TRANSPORT_USB,
TRANSPORT_THREAD,
+ TRANSPORT_SATELLITE,
})
public @interface Transport { }
@@ -1313,10 +1314,16 @@
*/
public static final int TRANSPORT_THREAD = 9;
+ /**
+ * Indicates this network uses a Satellite transport.
+ */
+ @FlaggedApi(Flags.SUPPORT_TRANSPORT_SATELLITE)
+ public static final int TRANSPORT_SATELLITE = 10;
+
/** @hide */
public static final int MIN_TRANSPORT = TRANSPORT_CELLULAR;
/** @hide */
- public static final int MAX_TRANSPORT = TRANSPORT_THREAD;
+ public static final int MAX_TRANSPORT = TRANSPORT_SATELLITE;
private static final int ALL_VALID_TRANSPORTS;
static {
@@ -1343,6 +1350,7 @@
"TEST",
"USB",
"THREAD",
+ "SATELLITE",
};
/**
@@ -2794,10 +2802,9 @@
* receiver holds the NETWORK_FACTORY permission. In all other cases, it will be the empty set.
*
* @return
- * @hide
*/
@NonNull
- @SystemApi
+ @FlaggedApi(Flags.REQUEST_RESTRICTED_WIFI)
public Set<Integer> getSubscriptionIds() {
return new ArraySet<>(mSubIds);
}
diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java
index 653e41d..4de02ac 100644
--- a/framework/src/android/net/NetworkRequest.java
+++ b/framework/src/android/net/NetworkRequest.java
@@ -34,6 +34,7 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.TRANSPORT_TEST;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -145,6 +146,12 @@
* Look up the specific capability to learn whether its usage requires this self-certification.
*/
public class NetworkRequest implements Parcelable {
+
+ /** @hide */
+ public static class Flags {
+ static final String REQUEST_RESTRICTED_WIFI =
+ "com.android.net.flags.request_restricted_wifi";
+ }
/**
* The first requestId value that will be allocated.
* @hide only used by ConnectivityService.
@@ -630,10 +637,9 @@
* NETWORK_FACTORY permission.
*
* @param subIds A {@code Set} that represents subscription IDs.
- * @hide
*/
@NonNull
- @SystemApi
+ @FlaggedApi(Flags.REQUEST_RESTRICTED_WIFI)
public Builder setSubscriptionIds(@NonNull Set<Integer> subIds) {
mNetworkCapabilities.setSubscriptionIds(subIds);
return this;
@@ -890,4 +896,17 @@
// a new array.
return networkCapabilities.getTransportTypes();
}
+
+ /**
+ * Gets all the subscription ids set on this {@code NetworkRequest} instance.
+ *
+ * @return Set of Integer values for this instance.
+ */
+ @NonNull
+ @FlaggedApi(Flags.REQUEST_RESTRICTED_WIFI)
+ public Set<Integer> getSubscriptionIds() {
+ // No need to make a defensive copy here as NC#getSubscriptionIds() already returns
+ // a new set.
+ return networkCapabilities.getSubscriptionIds();
+ }
}
diff --git a/framework/src/android/net/NetworkUtils.java b/framework/src/android/net/NetworkUtils.java
index 785c029..18feb84 100644
--- a/framework/src/android/net/NetworkUtils.java
+++ b/framework/src/android/net/NetworkUtils.java
@@ -440,4 +440,7 @@
/** Returns whether the Linux Kernel is 64 bit */
public static native boolean isKernel64Bit();
+
+ /** Returns whether the Linux Kernel is x86 */
+ public static native boolean isKernelX86();
}
diff --git a/nearby/apex/Android.bp b/nearby/apex/Android.bp
index d7f063a..5fdf5c9 100644
--- a/nearby/apex/Android.bp
+++ b/nearby/apex/Android.bp
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/nearby/framework/Android.bp b/nearby/framework/Android.bp
index 4bb9efd..4be102c 100644
--- a/nearby/framework/Android.bp
+++ b/nearby/framework/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -49,6 +50,7 @@
"androidx.annotation_annotation",
"framework-annotations-lib",
"framework-bluetooth",
+ "framework-location.stubs.module_lib",
],
static_libs: [
"modules-utils-preconditions",
diff --git a/nearby/framework/java/android/nearby/INearbyManager.aidl b/nearby/framework/java/android/nearby/INearbyManager.aidl
index 7af271e..21ae0ac 100644
--- a/nearby/framework/java/android/nearby/INearbyManager.aidl
+++ b/nearby/framework/java/android/nearby/INearbyManager.aidl
@@ -20,6 +20,7 @@
import android.nearby.IScanListener;
import android.nearby.BroadcastRequestParcelable;
import android.nearby.ScanRequest;
+import android.nearby.PoweredOffFindingEphemeralId;
import android.nearby.aidl.IOffloadCallback;
/**
@@ -40,4 +41,10 @@
void stopBroadcast(in IBroadcastListener callback, String packageName, @nullable String attributionTag);
void queryOffloadCapability(in IOffloadCallback callback) ;
-}
\ No newline at end of file
+
+ void setPoweredOffFindingEphemeralIds(in List<PoweredOffFindingEphemeralId> eids);
+
+ void setPoweredOffModeEnabled(boolean enabled);
+
+ boolean getPoweredOffModeEnabled();
+}
diff --git a/nearby/framework/java/android/nearby/NearbyManager.java b/nearby/framework/java/android/nearby/NearbyManager.java
index 00f1c38..cae653d 100644
--- a/nearby/framework/java/android/nearby/NearbyManager.java
+++ b/nearby/framework/java/android/nearby/NearbyManager.java
@@ -18,6 +18,7 @@
import android.Manifest;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -25,9 +26,12 @@
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.bluetooth.BluetoothManager;
import android.content.Context;
+import android.location.LocationManager;
import android.nearby.aidl.IOffloadCallback;
import android.os.RemoteException;
+import android.os.SystemProperties;
import android.provider.Settings;
import android.util.Log;
@@ -37,6 +41,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
+import java.util.List;
import java.util.Objects;
import java.util.WeakHashMap;
import java.util.concurrent.Executor;
@@ -75,8 +80,51 @@
int ERROR = 2;
}
+ /**
+ * Return value of {@link #getPoweredOffFindingMode()} when this powered off finding is not
+ * supported the device.
+ */
+ @FlaggedApi("com.android.nearby.flags.powered_off_finding")
+ public static final int POWERED_OFF_FINDING_MODE_UNSUPPORTED = 0;
+
+ /**
+ * Return value of {@link #getPoweredOffFindingMode()} and argument of {@link
+ * #setPoweredOffFindingMode(int)} when powered off finding is supported but disabled. The
+ * device will not start to advertise when powered off.
+ */
+ @FlaggedApi("com.android.nearby.flags.powered_off_finding")
+ public static final int POWERED_OFF_FINDING_MODE_DISABLED = 1;
+
+ /**
+ * Return value of {@link #getPoweredOffFindingMode()} and argument of {@link
+ * #setPoweredOffFindingMode(int)} when powered off finding is enabled. The device will start to
+ * advertise when powered off.
+ */
+ @FlaggedApi("com.android.nearby.flags.powered_off_finding")
+ public static final int POWERED_OFF_FINDING_MODE_ENABLED = 2;
+
+ /**
+ * Powered off finding modes.
+ *
+ * @hide
+ */
+ @IntDef(
+ prefix = {"POWERED_OFF_FINDING_MODE"},
+ value = {
+ POWERED_OFF_FINDING_MODE_UNSUPPORTED,
+ POWERED_OFF_FINDING_MODE_DISABLED,
+ POWERED_OFF_FINDING_MODE_ENABLED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PoweredOffFindingMode {}
+
private static final String TAG = "NearbyManager";
+ private static final int POWERED_OFF_FINDING_EID_LENGTH = 20;
+
+ private static final String POWER_OFF_FINDING_SUPPORTED_PROPERTY =
+ "ro.bluetooth.finder.supported";
+
/**
* TODO(b/286137024): Remove this when CTS R5 is rolled out.
* Whether allows Fast Pair to scan.
@@ -456,4 +504,124 @@
"successfully %s Fast Pair scan", enable ? "enables" : "disables"));
}
+ /**
+ * Sets the precomputed EIDs for advertising when the phone is powered off. The Bluetooth
+ * controller will store these EIDs in its memory, and will start advertising them in Find My
+ * Device network EID frames when powered off, only if the powered off finding mode was
+ * previously enabled by calling {@link #setPoweredOffFindingMode(int)}.
+ *
+ * <p>The EIDs are cryptographic ephemeral identifiers that change periodically, based on the
+ * Android clock at the time of the shutdown. They are used as the public part of asymmetric key
+ * pairs. Members of the Find My Device network can use them to encrypt the location of where
+ * they sight the advertising device. Only someone in possession of the private key (the device
+ * owner or someone that the device owner shared the key with) can decrypt this encrypted
+ * location.
+ *
+ * <p>Android will typically call this method during the shutdown process. Even after the
+ * method was called, it is still possible to call {#link setPoweredOffFindingMode() to disable
+ * the advertisement, for example to temporarily disable it for a single shutdown.
+ *
+ * <p>If called more than once, the EIDs of the most recent call overrides the EIDs from any
+ * previous call.
+ *
+ * @throws IllegalArgumentException if the length of one of the EIDs is not 20 bytes
+ */
+ @FlaggedApi("com.android.nearby.flags.powered_off_finding")
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+ public void setPoweredOffFindingEphemeralIds(@NonNull List<byte[]> eids) {
+ Objects.requireNonNull(eids);
+ if (!isPoweredOffFindingSupported()) {
+ throw new UnsupportedOperationException(
+ "Powered off finding is not supported on this device");
+ }
+ List<PoweredOffFindingEphemeralId> ephemeralIdList = eids.stream().map(
+ eid -> {
+ Preconditions.checkArgument(eid.length == POWERED_OFF_FINDING_EID_LENGTH);
+ PoweredOffFindingEphemeralId ephemeralId = new PoweredOffFindingEphemeralId();
+ ephemeralId.bytes = eid;
+ return ephemeralId;
+ }).toList();
+ try {
+ mService.setPoweredOffFindingEphemeralIds(ephemeralIdList);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ }
+
+ /**
+ * Turns the powered off finding on or off. Power off finding will operate only if this method
+ * was called at least once since boot, and the value of the argument {@code
+ * poweredOffFindinMode} was {@link #POWERED_OFF_FINDING_MODE_ENABLED} the last time the method
+ * was called.
+ *
+ * <p>When an Android device with the powered off finding feature is turned off (either as part
+ * of a normal shutdown or due to dead battery), its Bluetooth chip starts to advertise Find My
+ * Device network EID frames with the EID payload that were provided by the last call to {@link
+ * #setPoweredOffFindingEphemeralIds(List)}. These EIDs can be sighted by other Android devices
+ * in BLE range that are part of the Find My Device network. The Android sighters use the EID to
+ * encrypt the location of the Android device and upload it to the server, in a way that only
+ * the owner of the advertising device, or people that the owner shared their encryption key
+ * with, can decrypt the location.
+ *
+ * @param poweredOffFindingMode {@link #POWERED_OFF_FINDING_MODE_ENABLED} or {@link
+ * #POWERED_OFF_FINDING_MODE_DISABLED}
+ *
+ * @throws IllegalStateException if called with {@link #POWERED_OFF_FINDING_MODE_ENABLED} when
+ * Bluetooth or location services are disabled
+ */
+ @FlaggedApi("com.android.nearby.flags.powered_off_finding")
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+ public void setPoweredOffFindingMode(@PoweredOffFindingMode int poweredOffFindingMode) {
+ Preconditions.checkArgument(
+ poweredOffFindingMode == POWERED_OFF_FINDING_MODE_ENABLED
+ || poweredOffFindingMode == POWERED_OFF_FINDING_MODE_DISABLED,
+ "invalid poweredOffFindingMode");
+ if (!isPoweredOffFindingSupported()) {
+ throw new UnsupportedOperationException(
+ "Powered off finding is not supported on this device");
+ }
+ if (poweredOffFindingMode == POWERED_OFF_FINDING_MODE_ENABLED) {
+ Preconditions.checkState(areLocationAndBluetoothEnabled(),
+ "Location services and Bluetooth must be on");
+ }
+ try {
+ mService.setPoweredOffModeEnabled(
+ poweredOffFindingMode == POWERED_OFF_FINDING_MODE_ENABLED);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the state of the powered off finding feature.
+ *
+ * <p>{@link #POWERED_OFF_FINDING_MODE_UNSUPPORTED} if the feature is not supported by the
+ * device, {@link #POWERED_OFF_FINDING_MODE_DISABLED} if this was the last value set by {@link
+ * #setPoweredOffFindingMode(int)} or if no value was set since boot, {@link
+ * #POWERED_OFF_FINDING_MODE_ENABLED} if this was the last value set by {@link
+ * #setPoweredOffFindingMode(int)}
+ */
+ @FlaggedApi("com.android.nearby.flags.powered_off_finding")
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+ public @PoweredOffFindingMode int getPoweredOffFindingMode() {
+ if (!isPoweredOffFindingSupported()) {
+ return POWERED_OFF_FINDING_MODE_UNSUPPORTED;
+ }
+ try {
+ return mService.getPoweredOffModeEnabled()
+ ? POWERED_OFF_FINDING_MODE_ENABLED : POWERED_OFF_FINDING_MODE_DISABLED;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private boolean isPoweredOffFindingSupported() {
+ return Boolean.parseBoolean(SystemProperties.get(POWER_OFF_FINDING_SUPPORTED_PROPERTY));
+ }
+
+ private boolean areLocationAndBluetoothEnabled() {
+ return mContext.getSystemService(BluetoothManager.class).getAdapter().isEnabled()
+ && mContext.getSystemService(LocationManager.class).isLocationEnabled();
+ }
}
diff --git a/nearby/framework/java/android/nearby/PoweredOffFindingEphemeralId.aidl b/nearby/framework/java/android/nearby/PoweredOffFindingEphemeralId.aidl
new file mode 100644
index 0000000..9f4bfef
--- /dev/null
+++ b/nearby/framework/java/android/nearby/PoweredOffFindingEphemeralId.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nearby;
+
+/**
+ * Find My Device network ephemeral ID for powered off finding.
+ *
+ * @hide
+ */
+parcelable PoweredOffFindingEphemeralId {
+ byte[20] bytes;
+}
diff --git a/nearby/service/Android.bp b/nearby/service/Android.bp
index 17b80b0..d34fd83 100644
--- a/nearby/service/Android.bp
+++ b/nearby/service/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/nearby/service/java/com/android/server/nearby/NearbyService.java b/nearby/service/java/com/android/server/nearby/NearbyService.java
index 3c183ec..1575f07 100644
--- a/nearby/service/java/com/android/server/nearby/NearbyService.java
+++ b/nearby/service/java/com/android/server/nearby/NearbyService.java
@@ -35,12 +35,14 @@
import android.nearby.INearbyManager;
import android.nearby.IScanListener;
import android.nearby.NearbyManager;
+import android.nearby.PoweredOffFindingEphemeralId;
import android.nearby.ScanRequest;
import android.nearby.aidl.IOffloadCallback;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.nearby.injector.Injector;
+import com.android.server.nearby.managers.BluetoothFinderManager;
import com.android.server.nearby.managers.BroadcastProviderManager;
import com.android.server.nearby.managers.DiscoveryManager;
import com.android.server.nearby.managers.DiscoveryProviderManager;
@@ -50,6 +52,8 @@
import com.android.server.nearby.util.permissions.BroadcastPermissions;
import com.android.server.nearby.util.permissions.DiscoveryPermissions;
+import java.util.List;
+
/** Service implementing nearby functionality. */
public class NearbyService extends INearbyManager.Stub {
public static final String TAG = "NearbyService";
@@ -79,6 +83,7 @@
};
private final DiscoveryManager mDiscoveryProviderManager;
private final BroadcastProviderManager mBroadcastProviderManager;
+ private final BluetoothFinderManager mBluetoothFinderManager;
public NearbyService(Context context) {
mContext = context;
@@ -90,6 +95,7 @@
mNearbyConfiguration.refactorDiscoveryManager()
? new DiscoveryProviderManager(context, mInjector)
: new DiscoveryProviderManagerLegacy(context, mInjector);
+ mBluetoothFinderManager = new BluetoothFinderManager();
}
@VisibleForTesting
@@ -148,6 +154,30 @@
mDiscoveryProviderManager.queryOffloadCapability(callback);
}
+ @Override
+ public void setPoweredOffFindingEphemeralIds(List<PoweredOffFindingEphemeralId> eids) {
+ // Permissions check
+ enforceBluetoothPrivilegedPermission(mContext);
+
+ mBluetoothFinderManager.sendEids(eids);
+ }
+
+ @Override
+ public void setPoweredOffModeEnabled(boolean enabled) {
+ // Permissions check
+ enforceBluetoothPrivilegedPermission(mContext);
+
+ mBluetoothFinderManager.setPoweredOffFinderMode(enabled);
+ }
+
+ @Override
+ public boolean getPoweredOffModeEnabled() {
+ // Permissions check
+ enforceBluetoothPrivilegedPermission(mContext);
+
+ return mBluetoothFinderManager.getPoweredOffFinderMode();
+ }
+
/**
* Called by the service initializer.
*
diff --git a/nearby/service/java/com/android/server/nearby/managers/BluetoothFinderManager.java b/nearby/service/java/com/android/server/nearby/managers/BluetoothFinderManager.java
new file mode 100644
index 0000000..63ff516
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/managers/BluetoothFinderManager.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.managers;
+
+import android.nearby.PoweredOffFindingEphemeralId;
+
+import java.util.List;
+
+/** Connects to {@link IBluetoothFinder} HAL and invokes its API. */
+// A placeholder implementation until the HAL API can be used.
+public class BluetoothFinderManager {
+
+ private boolean mPoweredOffFindingModeEnabled = false;
+
+ /** An empty implementation of the corresponding HAL API call. */
+ public void sendEids(List<PoweredOffFindingEphemeralId> eids) {}
+
+ /** A placeholder implementation of the corresponding HAL API call. */
+ public void setPoweredOffFinderMode(boolean enable) {
+ mPoweredOffFindingModeEnabled = enable;
+ }
+
+ /** A placeholder implementation of the corresponding HAL API call. */
+ public boolean getPoweredOffFinderMode() {
+ return mPoweredOffFindingModeEnabled;
+ }
+}
diff --git a/nearby/service/lint-baseline.xml b/nearby/service/lint-baseline.xml
index a4761ab..3477594 100644
--- a/nearby/service/lint-baseline.xml
+++ b/nearby/service/lint-baseline.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-dev" type="baseline" dependencies="true" variant="all" version="8.0.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
<issue
id="NewApi"
@@ -8,7 +8,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="packages/modules/Connectivity/nearby/service/java/com/android/server/nearby/provider/ChreCommunication.java"
- line="263"
+ line="289"
column="54"/>
</issue>
diff --git a/nearby/service/proto/Android.bp b/nearby/service/proto/Android.bp
index 1b00cf6..be5a0b3 100644
--- a/nearby/service/proto/Android.bp
+++ b/nearby/service/proto/Android.bp
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -41,4 +42,4 @@
apex_available: [
"com.android.tethering",
],
-}
\ No newline at end of file
+}
diff --git a/nearby/tests/cts/fastpair/Android.bp b/nearby/tests/cts/fastpair/Android.bp
index 4309d7e..8009303 100644
--- a/nearby/tests/cts/fastpair/Android.bp
+++ b/nearby/tests/cts/fastpair/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -33,6 +34,7 @@
"framework-bluetooth.stubs.module_lib",
"framework-configinfrastructure",
"framework-connectivity-t.impl",
+ "framework-location.stubs.module_lib",
],
srcs: ["src/**/*.java"],
test_suites: [
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java
index bc9691d..832ac03 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java
@@ -25,12 +25,14 @@
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
import android.app.UiAutomation;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager;
import android.bluetooth.cts.BTAdapterUtils;
import android.content.Context;
+import android.location.LocationManager;
import android.nearby.BroadcastCallback;
import android.nearby.BroadcastRequest;
import android.nearby.NearbyDevice;
@@ -42,6 +44,8 @@
import android.nearby.ScanCallback;
import android.nearby.ScanRequest;
import android.os.Build;
+import android.os.Process;
+import android.os.UserHandle;
import android.provider.DeviceConfig;
import androidx.annotation.NonNull;
@@ -50,6 +54,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SdkSuppress;
+import com.android.compatibility.common.util.SystemUtil;
import com.android.modules.utils.build.SdkLevel;
import org.junit.Before;
@@ -57,6 +62,7 @@
import org.junit.runner.RunWith;
import java.util.Collections;
+import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@@ -189,6 +195,92 @@
mScanCallback.onError(ERROR_UNSUPPORTED);
}
+ @Test
+ public void testsetPoweredOffFindingEphemeralIds() {
+ // Replace with minSdkVersion when Build.VERSION_CODES.VANILLA_ICE_CREAM can be used.
+ assumeTrue(SdkLevel.isAtLeastV());
+ // Only test supporting devices.
+ if (mNearbyManager.getPoweredOffFindingMode()
+ == NearbyManager.POWERED_OFF_FINDING_MODE_UNSUPPORTED) return;
+
+ mNearbyManager.setPoweredOffFindingEphemeralIds(List.of(new byte[20], new byte[20]));
+ }
+
+ @Test
+ public void testsetPoweredOffFindingEphemeralIds_noPrivilegedPermission() {
+ // Replace with minSdkVersion when Build.VERSION_CODES.VANILLA_ICE_CREAM can be used.
+ assumeTrue(SdkLevel.isAtLeastV());
+ // Only test supporting devices.
+ if (mNearbyManager.getPoweredOffFindingMode()
+ == NearbyManager.POWERED_OFF_FINDING_MODE_UNSUPPORTED) return;
+
+ mUiAutomation.dropShellPermissionIdentity();
+
+ assertThrows(SecurityException.class,
+ () -> mNearbyManager.setPoweredOffFindingEphemeralIds(List.of(new byte[20])));
+ }
+
+
+ @Test
+ public void testSetAndGetPoweredOffFindingMode_enabled() {
+ // Replace with minSdkVersion when Build.VERSION_CODES.VANILLA_ICE_CREAM can be used.
+ assumeTrue(SdkLevel.isAtLeastV());
+ // Only test supporting devices.
+ if (mNearbyManager.getPoweredOffFindingMode()
+ == NearbyManager.POWERED_OFF_FINDING_MODE_UNSUPPORTED) return;
+
+ enableLocation();
+ // enableLocation() has dropped shell permission identity.
+ mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_PRIVILEGED);
+
+ mNearbyManager.setPoweredOffFindingMode(
+ NearbyManager.POWERED_OFF_FINDING_MODE_ENABLED);
+ assertThat(mNearbyManager.getPoweredOffFindingMode())
+ .isEqualTo(NearbyManager.POWERED_OFF_FINDING_MODE_ENABLED);
+ }
+
+ @Test
+ public void testSetAndGetPoweredOffFindingMode_disabled() {
+ // Replace with minSdkVersion when Build.VERSION_CODES.VANILLA_ICE_CREAM can be used.
+ assumeTrue(SdkLevel.isAtLeastV());
+ // Only test supporting devices.
+ if (mNearbyManager.getPoweredOffFindingMode()
+ == NearbyManager.POWERED_OFF_FINDING_MODE_UNSUPPORTED) return;
+
+ mNearbyManager.setPoweredOffFindingMode(
+ NearbyManager.POWERED_OFF_FINDING_MODE_DISABLED);
+ assertThat(mNearbyManager.getPoweredOffFindingMode())
+ .isEqualTo(NearbyManager.POWERED_OFF_FINDING_MODE_DISABLED);
+ }
+
+ @Test
+ public void testSetPoweredOffFindingMode_noPrivilegedPermission() {
+ // Replace with minSdkVersion when Build.VERSION_CODES.VANILLA_ICE_CREAM can be used.
+ assumeTrue(SdkLevel.isAtLeastV());
+ // Only test supporting devices.
+ if (mNearbyManager.getPoweredOffFindingMode()
+ == NearbyManager.POWERED_OFF_FINDING_MODE_UNSUPPORTED) return;
+
+ enableLocation();
+ mUiAutomation.dropShellPermissionIdentity();
+
+ assertThrows(SecurityException.class, () -> mNearbyManager
+ .setPoweredOffFindingMode(NearbyManager.POWERED_OFF_FINDING_MODE_ENABLED));
+ }
+
+ @Test
+ public void testGetPoweredOffFindingMode_noPrivilegedPermission() {
+ // Replace with minSdkVersion when Build.VERSION_CODES.VANILLA_ICE_CREAM can be used.
+ assumeTrue(SdkLevel.isAtLeastV());
+ // Only test supporting devices.
+ if (mNearbyManager.getPoweredOffFindingMode()
+ == NearbyManager.POWERED_OFF_FINDING_MODE_UNSUPPORTED) return;
+
+ mUiAutomation.dropShellPermissionIdentity();
+
+ assertThrows(SecurityException.class, () -> mNearbyManager.getPoweredOffFindingMode());
+ }
+
private void enableBluetooth() {
BluetoothManager manager = mContext.getSystemService(BluetoothManager.class);
BluetoothAdapter bluetoothAdapter = manager.getAdapter();
@@ -197,6 +289,13 @@
}
}
+ private void enableLocation() {
+ LocationManager locationManager = mContext.getSystemService(LocationManager.class);
+ UserHandle user = Process.myUserHandle();
+ SystemUtil.runWithShellPermissionIdentity(
+ mUiAutomation, () -> locationManager.setLocationEnabledForUser(true, user));
+ }
+
private static class OffloadCallback implements Consumer<OffloadCapability> {
@Override
public void accept(OffloadCapability aBoolean) {
diff --git a/nearby/tests/integration/privileged/Android.bp b/nearby/tests/integration/privileged/Android.bp
index 9b6e488..5e64009 100644
--- a/nearby/tests/integration/privileged/Android.bp
+++ b/nearby/tests/integration/privileged/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/nearby/tests/integration/privileged/src/android/nearby/integration/privileged/NearbyManagerTest.kt b/nearby/tests/integration/privileged/src/android/nearby/integration/privileged/NearbyManagerTest.kt
index 506b4e2..b949720 100644
--- a/nearby/tests/integration/privileged/src/android/nearby/integration/privileged/NearbyManagerTest.kt
+++ b/nearby/tests/integration/privileged/src/android/nearby/integration/privileged/NearbyManagerTest.kt
@@ -29,6 +29,7 @@
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -96,4 +97,49 @@
)
nearbyManager.stopBroadcast(broadcastCallback)
}
+
+ /** Verify privileged app can set powered off finding ephemeral IDs without exception. */
+ @Test
+ fun testNearbyManagerSetPoweredOffFindingEphemeralIds_fromPrivilegedApp_succeed() {
+ val nearbyManager = appContext.getSystemService(Context.NEARBY_SERVICE) as NearbyManager
+ // Only test supporting devices.
+ if (nearbyManager.getPoweredOffFindingMode()
+ == NearbyManager.POWERED_OFF_FINDING_MODE_UNSUPPORTED) return
+
+ val eid = ByteArray(20)
+
+ nearbyManager.setPoweredOffFindingEphemeralIds(listOf(eid))
+ }
+
+ /**
+ * Verifies that [NearbyManager.setPoweredOffFindingEphemeralIds] checkes the ephemeral ID
+ * length.
+ */
+ @Test
+ fun testNearbyManagerSetPoweredOffFindingEphemeralIds_wrongSize_throwsException() {
+ val nearbyManager = appContext.getSystemService(Context.NEARBY_SERVICE) as NearbyManager
+ // Only test supporting devices.
+ if (nearbyManager.getPoweredOffFindingMode()
+ == NearbyManager.POWERED_OFF_FINDING_MODE_UNSUPPORTED) return
+
+ assertThrows(IllegalArgumentException::class.java) {
+ nearbyManager.setPoweredOffFindingEphemeralIds(listOf(ByteArray(21)))
+ }
+ assertThrows(IllegalArgumentException::class.java) {
+ nearbyManager.setPoweredOffFindingEphemeralIds(listOf(ByteArray(19)))
+ }
+ }
+
+ /** Verify privileged app can set and get powered off finding mode without exception. */
+ @Test
+ fun testNearbyManagerSetGetPoweredOffMode_fromPrivilegedApp_succeed() {
+ val nearbyManager = appContext.getSystemService(Context.NEARBY_SERVICE) as NearbyManager
+ // Only test supporting devices.
+ if (nearbyManager.getPoweredOffFindingMode()
+ == NearbyManager.POWERED_OFF_FINDING_MODE_UNSUPPORTED) return
+
+ nearbyManager.setPoweredOffFindingMode(NearbyManager.POWERED_OFF_FINDING_MODE_DISABLED)
+ assertThat(nearbyManager.getPoweredOffFindingMode())
+ .isEqualTo(NearbyManager.POWERED_OFF_FINDING_MODE_DISABLED)
+ }
}
diff --git a/nearby/tests/integration/untrusted/Android.bp b/nearby/tests/integration/untrusted/Android.bp
index 75f765b..e6259c5 100644
--- a/nearby/tests/integration/untrusted/Android.bp
+++ b/nearby/tests/integration/untrusted/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/nearby/tests/integration/untrusted/src/android/nearby/integration/untrusted/NearbyManagerTest.kt b/nearby/tests/integration/untrusted/src/android/nearby/integration/untrusted/NearbyManagerTest.kt
index 7bf9f63..015d022 100644
--- a/nearby/tests/integration/untrusted/src/android/nearby/integration/untrusted/NearbyManagerTest.kt
+++ b/nearby/tests/integration/untrusted/src/android/nearby/integration/untrusted/NearbyManagerTest.kt
@@ -30,12 +30,12 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.uiautomator.LogcatWaitMixin
import com.google.common.truth.Truth.assertThat
+import java.time.Duration
+import java.util.Calendar
import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import java.time.Duration
-import java.util.Calendar
@RunWith(AndroidJUnit4::class)
class NearbyManagerTest {
@@ -151,6 +151,46 @@
).isTrue()
}
+ /**
+ * Verify untrusted app can't set powered off finding ephemeral IDs because it needs
+ * BLUETOOTH_PRIVILEGED permission which is not for use by third-party applications.
+ */
+ @Test
+ fun testNearbyManagerSetPoweredOffFindingEphemeralIds_fromUnTrustedApp_throwsException() {
+ val nearbyManager = appContext.getSystemService(Context.NEARBY_SERVICE) as NearbyManager
+ val eid = ByteArray(20)
+
+ assertThrows(SecurityException::class.java) {
+ nearbyManager.setPoweredOffFindingEphemeralIds(listOf(eid))
+ }
+ }
+
+ /**
+ * Verify untrusted app can't set powered off finding mode because it needs BLUETOOTH_PRIVILEGED
+ * permission which is not for use by third-party applications.
+ */
+ @Test
+ fun testNearbyManagerSetPoweredOffFindingMode_fromUnTrustedApp_throwsException() {
+ val nearbyManager = appContext.getSystemService(Context.NEARBY_SERVICE) as NearbyManager
+
+ assertThrows(SecurityException::class.java) {
+ nearbyManager.setPoweredOffFindingMode(NearbyManager.POWERED_OFF_FINDING_MODE_ENABLED)
+ }
+ }
+
+ /**
+ * Verify untrusted app can't get powered off finding mode because it needs BLUETOOTH_PRIVILEGED
+ * permission which is not for use by third-party applications.
+ */
+ @Test
+ fun testNearbyManagerGetPoweredOffFindingMode_fromUnTrustedApp_throwsException() {
+ val nearbyManager = appContext.getSystemService(Context.NEARBY_SERVICE) as NearbyManager
+
+ assertThrows(SecurityException::class.java) {
+ nearbyManager.getPoweredOffFindingMode()
+ }
+ }
+
companion object {
private val WAIT_INVALID_OPERATIONS_LOGS_TIMEOUT = Duration.ofSeconds(5)
}
diff --git a/nearby/tests/unit/Android.bp b/nearby/tests/unit/Android.bp
index bbf42c7..2950568 100644
--- a/nearby/tests/unit/Android.bp
+++ b/nearby/tests/unit/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/netbpfload/Android.bp b/netbpfload/Android.bp
index 1f92374..b71890e 100644
--- a/netbpfload/Android.bp
+++ b/netbpfload/Android.bp
@@ -14,6 +14,10 @@
// limitations under the License.
//
+package {
+ default_team: "trendy_team_fwk_core_networking",
+}
+
cc_binary {
name: "netbpfload",
@@ -40,7 +44,7 @@
"com.android.tethering",
"//apex_available:platform",
],
- // really should be Android 14/U (34), but we cannot include binaries built
+ // really should be Android 13/T (33), but we cannot include binaries built
// against newer sdk in the apex, which still targets 30(R):
// module "netbpfload" variant "android_x86_apex30": should support
// min_sdk_version(30) for "com.android.tethering": newer SDK(34).
@@ -49,3 +53,15 @@
init_rc: ["netbpfload.rc"],
required: ["bpfloader"],
}
+
+// Versioned netbpfload init rc: init system will process it only on api T/33+ devices
+// Note: R[30] S[31] Sv2[32] T[33] U[34] V[35])
+//
+// For details of versioned rc files see:
+// https://android.googlesource.com/platform/system/core/+/HEAD/init/README.md#versioned-rc-files-within-apexs
+prebuilt_etc {
+ name: "netbpfload.mainline.rc",
+ src: "netbpfload.mainline.rc",
+ filename: "netbpfload.33rc",
+ installable: false,
+}
diff --git a/netbpfload/NetBpfLoad.cpp b/netbpfload/NetBpfLoad.cpp
index 6152287..2d8867e 100644
--- a/netbpfload/NetBpfLoad.cpp
+++ b/netbpfload/NetBpfLoad.cpp
@@ -169,15 +169,135 @@
return 0;
}
+#define APEX_MOUNT_POINT "/apex/com.android.tethering"
+const char * const platformBpfLoader = "/system/bin/bpfloader";
+const char * const platformNetBpfLoad = "/system/bin/netbpfload";
+const char * const apexNetBpfLoad = APEX_MOUNT_POINT "/bin/netbpfload";
+
+int logTetheringApexVersion(void) {
+ char * found_blockdev = NULL;
+ FILE * f = NULL;
+ char buf[4096];
+
+ f = fopen("/proc/mounts", "re");
+ if (!f) return 1;
+
+ // /proc/mounts format: block_device [space] mount_point [space] other stuff... newline
+ while (fgets(buf, sizeof(buf), f)) {
+ char * blockdev = buf;
+ char * space = strchr(blockdev, ' ');
+ if (!space) continue;
+ *space = '\0';
+ char * mntpath = space + 1;
+ space = strchr(mntpath, ' ');
+ if (!space) continue;
+ *space = '\0';
+ if (strcmp(mntpath, APEX_MOUNT_POINT)) continue;
+ found_blockdev = strdup(blockdev);
+ break;
+ }
+ fclose(f);
+ f = NULL;
+
+ if (!found_blockdev) return 2;
+ ALOGD("Found Tethering Apex mounted from blockdev %s", found_blockdev);
+
+ f = fopen("/proc/mounts", "re");
+ if (!f) { free(found_blockdev); return 3; }
+
+ while (fgets(buf, sizeof(buf), f)) {
+ char * blockdev = buf;
+ char * space = strchr(blockdev, ' ');
+ if (!space) continue;
+ *space = '\0';
+ char * mntpath = space + 1;
+ space = strchr(mntpath, ' ');
+ if (!space) continue;
+ *space = '\0';
+ if (strcmp(blockdev, found_blockdev)) continue;
+ if (strncmp(mntpath, APEX_MOUNT_POINT "@", strlen(APEX_MOUNT_POINT "@"))) continue;
+ char * at = strchr(mntpath, '@');
+ if (!at) continue;
+ char * ver = at + 1;
+ ALOGI("Tethering APEX version %s", ver);
+ }
+ fclose(f);
+ free(found_blockdev);
+ return 0;
+}
+
int main(int argc, char** argv, char * const envp[]) {
(void)argc;
android::base::InitLogging(argv, &android::base::KernelLogger);
- const int device_api_level = android_get_device_api_level();
- const bool isAtLeastU = (device_api_level >= __ANDROID_API_U__);
+ ALOGI("NetBpfLoad '%s' starting...", argv[0]);
- if (!android::bpf::isAtLeastKernelVersion(4, 19, 0)) {
- ALOGE("Android U QPR2 requires kernel 4.19.");
+ // true iff we are running from the module
+ const bool is_mainline = !strcmp(argv[0], apexNetBpfLoad);
+
+ // true iff we are running from the platform
+ const bool is_platform = !strcmp(argv[0], platformNetBpfLoad);
+
+ const int device_api_level = android_get_device_api_level();
+ const bool isAtLeastT = (device_api_level >= __ANDROID_API_T__);
+ const bool isAtLeastU = (device_api_level >= __ANDROID_API_U__);
+ const bool isAtLeastV = (device_api_level >= __ANDROID_API_V__);
+
+ // last in U QPR2 beta1
+ const bool has_platform_bpfloader_rc = exists("/system/etc/init/bpfloader.rc");
+ // first in U QPR2 beta~2
+ const bool has_platform_netbpfload_rc = exists("/system/etc/init/netbpfload.rc");
+
+ ALOGI("NetBpfLoad api:%d/%d kver:%07x platform:%d mainline:%d rc:%d%d",
+ android_get_application_target_sdk_version(), device_api_level,
+ android::bpf::kernelVersion(), is_platform, is_mainline,
+ has_platform_bpfloader_rc, has_platform_netbpfload_rc);
+
+ if (!is_platform && !is_mainline) {
+ ALOGE("Unable to determine if we're platform or mainline netbpfload.");
+ return 1;
+ }
+
+ if (is_platform) {
+ const char * args[] = { apexNetBpfLoad, NULL, };
+ execve(args[0], (char**)args, envp);
+ ALOGW("exec '%s' fail: %d[%s]", apexNetBpfLoad, errno, strerror(errno));
+ }
+
+ if (!has_platform_bpfloader_rc && !has_platform_netbpfload_rc) {
+ ALOGE("Unable to find platform's bpfloader & netbpfload init scripts.");
+ return 1;
+ }
+
+ if (has_platform_bpfloader_rc && has_platform_netbpfload_rc) {
+ ALOGE("Platform has *both* bpfloader & netbpfload init scripts.");
+ return 1;
+ }
+
+ logTetheringApexVersion();
+
+ if (is_mainline && has_platform_bpfloader_rc && !has_platform_netbpfload_rc) {
+ // Tethering apex shipped initrc file causes us to reach here
+ // but we're not ready to correctly handle anything before U QPR2
+ // in which the 'bpfloader' vs 'netbpfload' split happened
+ const char * args[] = { platformBpfLoader, NULL, };
+ execve(args[0], (char**)args, envp);
+ ALOGE("exec '%s' fail: %d[%s]", platformBpfLoader, errno, strerror(errno));
+ return 1;
+ }
+
+ if (isAtLeastT && !android::bpf::isAtLeastKernelVersion(4, 9, 0)) {
+ ALOGE("Android T requires kernel 4.9.");
+ return 1;
+ }
+
+ if (isAtLeastU && !android::bpf::isAtLeastKernelVersion(4, 14, 0)) {
+ ALOGE("Android U requires kernel 4.14.");
+ return 1;
+ }
+
+ if (isAtLeastV && !android::bpf::isAtLeastKernelVersion(4, 19, 0)) {
+ ALOGE("Android V requires kernel 4.19.");
return 1;
}
@@ -266,10 +386,8 @@
ALOGI("done, transferring control to platform bpfloader.");
- const char * args[] = { "/system/bin/bpfloader", NULL, };
- if (execve(args[0], (char**)args, envp)) {
- ALOGE("FATAL: execve('/system/bin/bpfloader'): %d[%s]", errno, strerror(errno));
- }
-
+ const char * args[] = { platformBpfLoader, NULL, };
+ execve(args[0], (char**)args, envp);
+ ALOGE("FATAL: execve('%s'): %d[%s]", platformBpfLoader, errno, strerror(errno));
return 1;
}
diff --git a/netbpfload/netbpfload.mainline.rc b/netbpfload/netbpfload.mainline.rc
new file mode 100644
index 0000000..0ac5de8
--- /dev/null
+++ b/netbpfload/netbpfload.mainline.rc
@@ -0,0 +1,8 @@
+service bpfloader /apex/com.android.tethering/bin/netbpfload
+ capabilities CHOWN SYS_ADMIN NET_ADMIN
+ group root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw system
+ user root
+ rlimit memlock 1073741824 1073741824
+ oneshot
+ reboot_on_failure reboot,bpfloader-failed
+ override
diff --git a/netd/Android.bp b/netd/Android.bp
index 3cdbc97..eedbdae 100644
--- a/netd/Android.bp
+++ b/netd/Android.bp
@@ -14,6 +14,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -58,15 +59,18 @@
cc_test {
name: "netd_updatable_unit_test",
defaults: ["netd_defaults"],
- test_suites: ["general-tests", "mts-tethering"],
+ test_suites: [
+ "general-tests",
+ "mts-tethering",
+ ],
test_config_template: ":net_native_test_config_template",
- require_root: true, // required by setrlimitForTest()
+ require_root: true, // required by setrlimitForTest()
header_libs: [
"bpf_connectivity_headers",
],
srcs: [
"BpfHandlerTest.cpp",
- "BpfBaseTest.cpp"
+ "BpfBaseTest.cpp",
],
static_libs: [
"libbase",
diff --git a/remoteauth/framework/Android.bp b/remoteauth/framework/Android.bp
index 71b621a..2f1737f 100644
--- a/remoteauth/framework/Android.bp
+++ b/remoteauth/framework/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/remoteauth/service/Android.bp b/remoteauth/service/Android.bp
index 8330efc..32ae54f 100644
--- a/remoteauth/service/Android.bp
+++ b/remoteauth/service/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/remoteauth/service/jni/Android.bp b/remoteauth/service/jni/Android.bp
index a95a8fb..c0ac779 100644
--- a/remoteauth/service/jni/Android.bp
+++ b/remoteauth/service/jni/Android.bp
@@ -1,4 +1,5 @@
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/remoteauth/service/jni/src/remoteauth_jni_android_platform.rs b/remoteauth/service/jni/src/remoteauth_jni_android_platform.rs
index 0a189f2..421fe7e 100644
--- a/remoteauth/service/jni/src/remoteauth_jni_android_platform.rs
+++ b/remoteauth/service/jni/src/remoteauth_jni_android_platform.rs
@@ -67,7 +67,7 @@
logger::init(
logger::Config::default()
.with_tag_on_device("remoteauth")
- .with_min_level(log::Level::Trace)
+ .with_max_level(log::LevelFilter::Trace)
.with_filter("trace,jni=info"),
);
}
diff --git a/remoteauth/service/jni/src/remoteauth_jni_android_protocol.rs b/remoteauth/service/jni/src/remoteauth_jni_android_protocol.rs
index ac2eb8c..e44ab8b 100644
--- a/remoteauth/service/jni/src/remoteauth_jni_android_protocol.rs
+++ b/remoteauth/service/jni/src/remoteauth_jni_android_protocol.rs
@@ -30,7 +30,7 @@
logger::init(
logger::Config::default()
.with_tag_on_device("remoteauth")
- .with_min_level(log::Level::Trace)
+ .with_max_level(log::LevelFilter::Trace)
.with_filter("trace,jni=info"),
);
get_boolean_result(native_init(env), "native_init")
diff --git a/remoteauth/tests/unit/Android.bp b/remoteauth/tests/unit/Android.bp
index 77e6f19..47b9e31 100644
--- a/remoteauth/tests/unit/Android.bp
+++ b/remoteauth/tests/unit/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/service-t/Android.bp b/service-t/Android.bp
index de879f3..19850fd 100644
--- a/service-t/Android.bp
+++ b/service-t/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_fwk_core_networking",
// See: http://go/android-license-faq
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -98,7 +99,7 @@
min_sdk_version: "21",
lint: {
error_checks: ["NewApi"],
- baseline_filename: "lint-baseline.xml",
+
},
srcs: [
"src/com/android/server/connectivity/mdns/**/*.java",
diff --git a/service-t/jni/com_android_server_net_NetworkStatsService.cpp b/service-t/jni/com_android_server_net_NetworkStatsService.cpp
index 028642f..c999398 100644
--- a/service-t/jni/com_android_server_net_NetworkStatsService.cpp
+++ b/service-t/jni/com_android_server_net_NetworkStatsService.cpp
@@ -34,11 +34,15 @@
using android::bpf::bpfGetUidStats;
using android::bpf::bpfGetIfaceStats;
+using android::bpf::bpfRegisterIface;
using android::bpf::NetworkTraceHandler;
namespace android {
static void nativeRegisterIface(JNIEnv* env, jclass clazz, jstring iface) {
+ ScopedUtfChars iface8(env, iface);
+ if (iface8.c_str() == nullptr) return;
+ bpfRegisterIface(iface8.c_str());
}
static jobject statsValueToEntry(JNIEnv* env, StatsValue* stats) {
@@ -48,8 +52,14 @@
return nullptr;
}
+ // Find the constructor.
+ jmethodID constructorID = env->GetMethodID(gEntryClass, "<init>", "()V");
+ if (constructorID == nullptr) {
+ return nullptr;
+ }
+
// Create a new instance of the Java class
- jobject result = env->AllocObject(gEntryClass);
+ jobject result = env->NewObject(gEntryClass, constructorID);
if (result == nullptr) {
return nullptr;
}
diff --git a/service-t/lint-baseline.xml b/service-t/lint-baseline.xml
index 38d3ab0..e4b92d6 100644
--- a/service-t/lint-baseline.xml
+++ b/service-t/lint-baseline.xml
@@ -1,104 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-dev" type="baseline" dependencies="true" variant="all" version="8.0.0-dev">
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.net.EthernetNetworkSpecifier#getInterfaceName`"
- errorLine1=" if (!((EthernetNetworkSpecifier) spec).getInterfaceName().matches(iface)) {"
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java"
- line="224"
- column="48"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.net.UnderlyingNetworkInfo#getInterface`"
- errorLine1=" delta.migrateTun(info.getOwnerUid(), info.getInterface(),"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service-t/src/com/android/server/net/NetworkStatsFactory.java"
- line="276"
- column="55"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.net.UnderlyingNetworkInfo#getOwnerUid`"
- errorLine1=" delta.migrateTun(info.getOwnerUid(), info.getInterface(),"
- errorLine2=" ~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service-t/src/com/android/server/net/NetworkStatsFactory.java"
- line="276"
- column="35"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.net.UnderlyingNetworkInfo#getUnderlyingInterfaces`"
- errorLine1=" info.getUnderlyingInterfaces());"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service-t/src/com/android/server/net/NetworkStatsFactory.java"
- line="277"
- column="26"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `java.net.InetAddress#parseNumericAddress`"
- errorLine1=" dnsAddresses.add(InetAddress.parseNumericAddress(address));"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service-t/src/com/android/server/ethernet/EthernetTracker.java"
- line="875"
- column="54"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `java.net.InetAddress#parseNumericAddress`"
- errorLine1=" staticIpConfigBuilder.setGateway(InetAddress.parseNumericAddress(value));"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service-t/src/com/android/server/ethernet/EthernetTracker.java"
- line="870"
- column="66"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
- errorLine1=" IoUtils.closeQuietly(os);"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service-t/src/com/android/server/net/NetworkStatsRecorder.java"
- line="556"
- column="25"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
- errorLine1=" IoUtils.closeQuietly(sockFd);"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service-t/src/com/android/server/IpSecService.java"
- line="1309"
- column="25"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
- errorLine1=" IoUtils.closeQuietly(mSocket);"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service-t/src/com/android/server/IpSecService.java"
- line="1034"
- column="21"/>
- </issue>
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
<issue
id="NewApi"
@@ -113,6 +14,17 @@
<issue
id="NewApi"
+ message="Cast from `EthernetNetworkSpecifier` to `NetworkSpecifier` requires API level 31 (current min is 30)"
+ errorLine1=" .setNetworkSpecifier(new EthernetNetworkSpecifier(ifaceName))"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java"
+ line="156"
+ column="38"/>
+ </issue>
+
+ <issue
+ id="NewApi"
message="Call requires API level 31 (current min is 30): `new android.net.EthernetNetworkSpecifier`"
errorLine1=" nc.setNetworkSpecifier(new EthernetNetworkSpecifier(iface));"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -124,39 +36,6 @@
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `new android.util.AtomicFile`"
- errorLine1=" mFile = new AtomicFile(new File(path), logger);"
- errorLine2=" ~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service-t/src/com/android/server/net/PersistentInt.java"
- line="53"
- column="17"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `new java.net.InetSocketAddress`"
- errorLine1=" super(handler, new RecvBuffer(buffer, new InetSocketAddress()));"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service-t/src/com/android/server/connectivity/mdns/MulticastPacketReader.java"
- line="66"
- column="47"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Cast from `EthernetNetworkSpecifier` to `NetworkSpecifier` requires API level 31 (current min is 30)"
- errorLine1=" .setNetworkSpecifier(new EthernetNetworkSpecifier(ifaceName))"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java"
- line="156"
- column="38"/>
- </issue>
-
- <issue
- id="NewApi"
message="Cast from `EthernetNetworkSpecifier` to `NetworkSpecifier` requires API level 31 (current min is 30)"
errorLine1=" nc.setNetworkSpecifier(new EthernetNetworkSpecifier(iface));"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -169,6 +48,28 @@
<issue
id="NewApi"
message="Class requires API level 31 (current min is 30): `android.net.EthernetNetworkSpecifier`"
+ errorLine1=" if (!(spec instanceof EthernetNetworkSpecifier)) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java"
+ line="221"
+ column="31"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.net.EthernetNetworkSpecifier#getInterfaceName`"
+ errorLine1=" if (!((EthernetNetworkSpecifier) spec).getInterfaceName().matches(iface)) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java"
+ line="224"
+ column="48"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Class requires API level 31 (current min is 30): `android.net.EthernetNetworkSpecifier`"
errorLine1=" if (!((EthernetNetworkSpecifier) spec).getInterfaceName().matches(iface)) {"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
<location
@@ -179,13 +80,178 @@
<issue
id="NewApi"
- message="Class requires API level 31 (current min is 30): `android.net.EthernetNetworkSpecifier`"
- errorLine1=" if (!(spec instanceof EthernetNetworkSpecifier)) {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `java.net.InetAddress#parseNumericAddress`"
+ errorLine1=" staticIpConfigBuilder.setGateway(InetAddress.parseNumericAddress(value));"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
- file="packages/modules/Connectivity/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java"
- line="221"
- column="31"/>
+ file="packages/modules/Connectivity/service-t/src/com/android/server/ethernet/EthernetTracker.java"
+ line="885"
+ column="66"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `java.net.InetAddress#parseNumericAddress`"
+ errorLine1=" dnsAddresses.add(InetAddress.parseNumericAddress(address));"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service-t/src/com/android/server/ethernet/EthernetTracker.java"
+ line="890"
+ column="54"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
+ errorLine1=" IoUtils.closeQuietly(mSocket);"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service-t/src/com/android/server/IpSecService.java"
+ line="1042"
+ column="21"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
+ errorLine1=" IoUtils.closeQuietly(sockFd);"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service-t/src/com/android/server/IpSecService.java"
+ line="1318"
+ column="25"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Field requires API level 31 (current min is 30): `android.system.OsConstants#UDP_ENCAP`"
+ errorLine1=" OsConstants.UDP_ENCAP,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service-t/src/com/android/server/IpSecService.java"
+ line="1326"
+ column="21"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Field requires API level 31 (current min is 30): `android.system.OsConstants#UDP_ENCAP_ESPINUDP`"
+ errorLine1=" OsConstants.UDP_ENCAP_ESPINUDP);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service-t/src/com/android/server/IpSecService.java"
+ line="1327"
+ column="21"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 33 (current min is 30): `BpfNetMaps`"
+ errorLine1=" return new BpfNetMaps(ctx);"
+ errorLine2=" ~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service-t/src/com/android/server/net/NetworkStatsFactory.java"
+ line="111"
+ column="20"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 33 (current min is 30): `swapActiveStatsMap`"
+ errorLine1=" mBpfNetMaps.swapActiveStatsMap();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service-t/src/com/android/server/net/NetworkStatsFactory.java"
+ line="185"
+ column="25"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.net.UnderlyingNetworkInfo#getInterface`"
+ errorLine1=" delta.migrateTun(info.getOwnerUid(), info.getInterface(),"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service-t/src/com/android/server/net/NetworkStatsFactory.java"
+ line="240"
+ column="55"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.net.UnderlyingNetworkInfo#getOwnerUid`"
+ errorLine1=" delta.migrateTun(info.getOwnerUid(), info.getInterface(),"
+ errorLine2=" ~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service-t/src/com/android/server/net/NetworkStatsFactory.java"
+ line="240"
+ column="35"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.net.UnderlyingNetworkInfo#getUnderlyingInterfaces`"
+ errorLine1=" info.getUnderlyingInterfaces());"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service-t/src/com/android/server/net/NetworkStatsFactory.java"
+ line="241"
+ column="26"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
+ errorLine1=" IoUtils.closeQuietly(os);"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service-t/src/com/android/server/net/NetworkStatsRecorder.java"
+ line="580"
+ column="25"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `newInstance`"
+ errorLine1=" opts = BroadcastOptionsShimImpl.newInstance("
+ errorLine2=" ~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service-t/src/com/android/server/net/NetworkStatsService.java"
+ line="562"
+ column="61"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `new android.util.AtomicFile`"
+ errorLine1=" mFile = new AtomicFile(new File(path), logger);"
+ errorLine2=" ~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service-t/src/com/android/server/net/PersistentInt.java"
+ line="53"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 33 (current min is 30): `addOrUpdateInterfaceAddress`"
+ errorLine1=" mCb.addOrUpdateInterfaceAddress(ifaddrMsg.index, la);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service-t/src/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitor.java"
+ line="69"
+ column="25"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 33 (current min is 30): `deleteInterfaceAddress`"
+ errorLine1=" mCb.deleteInterfaceAddress(ifaddrMsg.index, la);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service-t/src/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitor.java"
+ line="73"
+ column="21"/>
</issue>
</issues>
\ No newline at end of file
diff --git a/service-t/native/libs/libnetworkstats/Android.bp b/service-t/native/libs/libnetworkstats/Android.bp
index b9f3adb..c620634 100644
--- a/service-t/native/libs/libnetworkstats/Android.bp
+++ b/service-t/native/libs/libnetworkstats/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -57,9 +58,12 @@
cc_test {
name: "libnetworkstats_test",
- test_suites: ["general-tests", "mts-tethering"],
+ test_suites: [
+ "general-tests",
+ "mts-tethering",
+ ],
test_config_template: ":net_native_test_config_template",
- require_root: true, // required by setrlimitForTest()
+ require_root: true, // required by setrlimitForTest()
header_libs: ["bpf_connectivity_headers"],
srcs: [
"BpfNetworkStatsTest.cpp",
diff --git a/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp b/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
index 2af6928..d3e331e 100644
--- a/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
+++ b/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
@@ -40,8 +40,8 @@
using base::Result;
-const BpfMapRO<uint32_t, IfaceValue>& getIfaceIndexNameMap() {
- static BpfMapRO<uint32_t, IfaceValue> ifaceIndexNameMap(IFACE_INDEX_NAME_MAP_PATH);
+BpfMap<uint32_t, IfaceValue>& getIfaceIndexNameMap() {
+ static BpfMap<uint32_t, IfaceValue> ifaceIndexNameMap(IFACE_INDEX_NAME_MAP_PATH);
return ifaceIndexNameMap;
}
@@ -50,6 +50,25 @@
return ifaceStatsMap;
}
+Result<IfaceValue> ifindex2name(const uint32_t ifindex) {
+ Result<IfaceValue> v = getIfaceIndexNameMap().readValue(ifindex);
+ if (v.ok()) return v;
+ IfaceValue iv = {};
+ if (!if_indextoname(ifindex, iv.name)) return v;
+ getIfaceIndexNameMap().writeValue(ifindex, iv, BPF_ANY);
+ return iv;
+}
+
+void bpfRegisterIface(const char* iface) {
+ if (!iface) return;
+ if (strlen(iface) >= sizeof(IfaceValue)) return;
+ uint32_t ifindex = if_nametoindex(iface);
+ if (!ifindex) return;
+ IfaceValue ifname = {};
+ strlcpy(ifname.name, iface, sizeof(ifname.name));
+ getIfaceIndexNameMap().writeValue(ifindex, ifname, BPF_ANY);
+}
+
int bpfGetUidStatsInternal(uid_t uid, StatsValue* stats,
const BpfMapRO<uint32_t, StatsValue>& appUidStatsMap) {
auto statsEntry = appUidStatsMap.readValue(uid);
@@ -68,19 +87,19 @@
int bpfGetIfaceStatsInternal(const char* iface, StatsValue* stats,
const BpfMapRO<uint32_t, StatsValue>& ifaceStatsMap,
- const BpfMapRO<uint32_t, IfaceValue>& ifaceNameMap) {
+ const IfIndexToNameFunc ifindex2name) {
*stats = {};
int64_t unknownIfaceBytesTotal = 0;
const auto processIfaceStats =
- [iface, stats, &ifaceNameMap, &unknownIfaceBytesTotal](
+ [iface, stats, ifindex2name, &unknownIfaceBytesTotal](
const uint32_t& key,
const BpfMapRO<uint32_t, StatsValue>& ifaceStatsMap) -> Result<void> {
- char ifname[IFNAMSIZ];
- if (getIfaceNameFromMap(ifaceNameMap, ifaceStatsMap, key, ifname, key,
- &unknownIfaceBytesTotal)) {
+ Result<IfaceValue> ifname = ifindex2name(key);
+ if (!ifname.ok()) {
+ maybeLogUnknownIface(key, ifaceStatsMap, key, &unknownIfaceBytesTotal);
return Result<void>();
}
- if (!iface || !strcmp(iface, ifname)) {
+ if (!iface || !strcmp(iface, ifname.value().name)) {
Result<StatsValue> statsEntry = ifaceStatsMap.readValue(key);
if (!statsEntry.ok()) {
return statsEntry.error();
@@ -94,7 +113,7 @@
}
int bpfGetIfaceStats(const char* iface, StatsValue* stats) {
- return bpfGetIfaceStatsInternal(iface, stats, getIfaceStatsMap(), getIfaceIndexNameMap());
+ return bpfGetIfaceStatsInternal(iface, stats, getIfaceStatsMap(), ifindex2name);
}
int bpfGetIfIndexStatsInternal(uint32_t ifindex, StatsValue* stats,
@@ -113,9 +132,9 @@
}
stats_line populateStatsEntry(const StatsKey& statsKey, const StatsValue& statsEntry,
- const char* ifname) {
+ const IfaceValue& ifname) {
stats_line newLine;
- strlcpy(newLine.iface, ifname, sizeof(newLine.iface));
+ strlcpy(newLine.iface, ifname.name, sizeof(newLine.iface));
newLine.uid = (int32_t)statsKey.uid;
newLine.set = (int32_t)statsKey.counterSet;
newLine.tag = (int32_t)statsKey.tag;
@@ -128,22 +147,22 @@
int parseBpfNetworkStatsDetailInternal(std::vector<stats_line>& lines,
const BpfMapRO<StatsKey, StatsValue>& statsMap,
- const BpfMapRO<uint32_t, IfaceValue>& ifaceMap) {
+ const IfIndexToNameFunc ifindex2name) {
int64_t unknownIfaceBytesTotal = 0;
const auto processDetailUidStats =
- [&lines, &unknownIfaceBytesTotal, &ifaceMap](
+ [&lines, &unknownIfaceBytesTotal, &ifindex2name](
const StatsKey& key,
const BpfMapRO<StatsKey, StatsValue>& statsMap) -> Result<void> {
- char ifname[IFNAMSIZ];
- if (getIfaceNameFromMap(ifaceMap, statsMap, key.ifaceIndex, ifname, key,
- &unknownIfaceBytesTotal)) {
+ Result<IfaceValue> ifname = ifindex2name(key.ifaceIndex);
+ if (!ifname.ok()) {
+ maybeLogUnknownIface(key.ifaceIndex, statsMap, key, &unknownIfaceBytesTotal);
return Result<void>();
}
Result<StatsValue> statsEntry = statsMap.readValue(key);
if (!statsEntry.ok()) {
return base::ResultError(statsEntry.error().message(), statsEntry.error().code());
}
- stats_line newLine = populateStatsEntry(key, statsEntry.value(), ifname);
+ stats_line newLine = populateStatsEntry(key, statsEntry.value(), ifname.value());
lines.push_back(newLine);
if (newLine.tag) {
// account tagged traffic in the untagged stats (for historical reasons?)
@@ -202,7 +221,7 @@
// TODO: the above comment feels like it may be obsolete / out of date,
// since we no longer swap the map via netd binder rpc - though we do
// still swap it.
- int ret = parseBpfNetworkStatsDetailInternal(*lines, *inactiveStatsMap, getIfaceIndexNameMap());
+ int ret = parseBpfNetworkStatsDetailInternal(*lines, *inactiveStatsMap, ifindex2name);
if (ret) {
ALOGE("parse detail network stats failed: %s", strerror(errno));
return ret;
@@ -219,13 +238,14 @@
int parseBpfNetworkStatsDevInternal(std::vector<stats_line>& lines,
const BpfMapRO<uint32_t, StatsValue>& statsMap,
- const BpfMapRO<uint32_t, IfaceValue>& ifaceMap) {
+ const IfIndexToNameFunc ifindex2name) {
int64_t unknownIfaceBytesTotal = 0;
- const auto processDetailIfaceStats = [&lines, &unknownIfaceBytesTotal, &ifaceMap, &statsMap](
+ const auto processDetailIfaceStats = [&lines, &unknownIfaceBytesTotal, ifindex2name, &statsMap](
const uint32_t& key, const StatsValue& value,
const BpfMapRO<uint32_t, StatsValue>&) {
- char ifname[IFNAMSIZ];
- if (getIfaceNameFromMap(ifaceMap, statsMap, key, ifname, key, &unknownIfaceBytesTotal)) {
+ Result<IfaceValue> ifname = ifindex2name(key);
+ if (!ifname.ok()) {
+ maybeLogUnknownIface(key, statsMap, key, &unknownIfaceBytesTotal);
return Result<void>();
}
StatsKey fakeKey = {
@@ -233,7 +253,7 @@
.tag = (uint32_t)TAG_NONE,
.counterSet = (uint32_t)SET_ALL,
};
- lines.push_back(populateStatsEntry(fakeKey, value, ifname));
+ lines.push_back(populateStatsEntry(fakeKey, value, ifname.value()));
return Result<void>();
};
Result<void> res = statsMap.iterateWithValue(processDetailIfaceStats);
@@ -248,7 +268,7 @@
}
int parseBpfNetworkStatsDev(std::vector<stats_line>* lines) {
- return parseBpfNetworkStatsDevInternal(*lines, getIfaceStatsMap(), getIfaceIndexNameMap());
+ return parseBpfNetworkStatsDevInternal(*lines, getIfaceStatsMap(), ifindex2name);
}
void groupNetworkStats(std::vector<stats_line>& lines) {
diff --git a/service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp b/service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp
index bcc4550..484c166 100644
--- a/service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp
+++ b/service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp
@@ -77,6 +77,10 @@
BpfMap<uint32_t, IfaceValue> mFakeIfaceIndexNameMap;
BpfMap<uint32_t, StatsValue> mFakeIfaceStatsMap;
+ IfIndexToNameFunc mIfIndex2Name = [this](const uint32_t ifindex){
+ return mFakeIfaceIndexNameMap.readValue(ifindex);
+ };
+
void SetUp() {
ASSERT_EQ(0, setrlimitForTest());
@@ -228,7 +232,7 @@
populateFakeStats(TEST_UID1, 0, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
populateFakeStats(TEST_UID1, 0, IFACE_INDEX2, TEST_COUNTERSET1, value1, mFakeStatsMap);
populateFakeStats(TEST_UID2, 0, IFACE_INDEX3, TEST_COUNTERSET1, value1, mFakeStatsMap);
- ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mFakeIfaceIndexNameMap));
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mIfIndex2Name));
ASSERT_EQ((unsigned long)3, lines.size());
}
@@ -256,16 +260,15 @@
EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value1, BPF_ANY));
StatsValue result1 = {};
- ASSERT_EQ(0, bpfGetIfaceStatsInternal(IFACE_NAME1, &result1, mFakeIfaceStatsMap,
- mFakeIfaceIndexNameMap));
+ ASSERT_EQ(0,
+ bpfGetIfaceStatsInternal(IFACE_NAME1, &result1, mFakeIfaceStatsMap, mIfIndex2Name));
expectStatsEqual(value1, result1);
StatsValue result2 = {};
- ASSERT_EQ(0, bpfGetIfaceStatsInternal(IFACE_NAME2, &result2, mFakeIfaceStatsMap,
- mFakeIfaceIndexNameMap));
+ ASSERT_EQ(0,
+ bpfGetIfaceStatsInternal(IFACE_NAME2, &result2, mFakeIfaceStatsMap, mIfIndex2Name));
expectStatsEqual(value2, result2);
StatsValue totalResult = {};
- ASSERT_EQ(0, bpfGetIfaceStatsInternal(NULL, &totalResult, mFakeIfaceStatsMap,
- mFakeIfaceIndexNameMap));
+ ASSERT_EQ(0, bpfGetIfaceStatsInternal(NULL, &totalResult, mFakeIfaceStatsMap, mIfIndex2Name));
StatsValue totalValue = {
.rxPackets = TEST_PACKET0 * 2 + TEST_PACKET1,
.rxBytes = TEST_BYTES0 * 2 + TEST_BYTES1,
@@ -304,7 +307,7 @@
mFakeStatsMap);
populateFakeStats(TEST_UID2, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
std::vector<stats_line> lines;
- ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mFakeIfaceIndexNameMap));
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mIfIndex2Name));
ASSERT_EQ((unsigned long)7, lines.size());
}
@@ -324,7 +327,7 @@
populateFakeStats(TEST_UID1, 0, IFACE_INDEX1, TEST_COUNTERSET1, value1, mFakeStatsMap);
populateFakeStats(TEST_UID2, 0, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
std::vector<stats_line> lines;
- ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mFakeIfaceIndexNameMap));
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mIfIndex2Name));
ASSERT_EQ((unsigned long)4, lines.size());
}
@@ -352,18 +355,20 @@
.counterSet = TEST_COUNTERSET0,
.ifaceIndex = ifaceIndex,
};
- char ifname[IFNAMSIZ];
int64_t unknownIfaceBytesTotal = 0;
- ASSERT_EQ(-ENODEV, getIfaceNameFromMap(mFakeIfaceIndexNameMap, mFakeStatsMap, ifaceIndex,
- ifname, curKey, &unknownIfaceBytesTotal));
+ ASSERT_EQ(false, mFakeIfaceIndexNameMap.readValue(ifaceIndex).ok());
+ maybeLogUnknownIface(ifaceIndex, mFakeStatsMap, curKey, &unknownIfaceBytesTotal);
+
ASSERT_EQ(((int64_t)(TEST_BYTES0 * 20 + TEST_BYTES1 * 20)), unknownIfaceBytesTotal);
curKey.ifaceIndex = IFACE_INDEX2;
- ASSERT_EQ(-ENODEV, getIfaceNameFromMap(mFakeIfaceIndexNameMap, mFakeStatsMap, ifaceIndex,
- ifname, curKey, &unknownIfaceBytesTotal));
+
+ ASSERT_EQ(false, mFakeIfaceIndexNameMap.readValue(ifaceIndex).ok());
+ maybeLogUnknownIface(ifaceIndex, mFakeStatsMap, curKey, &unknownIfaceBytesTotal);
+
ASSERT_EQ(-1, unknownIfaceBytesTotal);
std::vector<stats_line> lines;
// TODO: find a way to test the total of unknown Iface Bytes go above limit.
- ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mFakeIfaceIndexNameMap));
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mIfIndex2Name));
ASSERT_EQ((unsigned long)1, lines.size());
expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, 0, lines.front());
}
@@ -394,8 +399,7 @@
ifaceStatsKey = IFACE_INDEX4;
EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value2, BPF_ANY));
std::vector<stats_line> lines;
- ASSERT_EQ(0,
- parseBpfNetworkStatsDevInternal(lines, mFakeIfaceStatsMap, mFakeIfaceIndexNameMap));
+ ASSERT_EQ(0, parseBpfNetworkStatsDevInternal(lines, mFakeIfaceStatsMap, mIfIndex2Name));
ASSERT_EQ((unsigned long)4, lines.size());
expectStatsLineEqual(value1, IFACE_NAME1, UID_ALL, SET_ALL, TAG_NONE, lines[0]);
@@ -439,13 +443,13 @@
std::vector<stats_line> lines;
// Test empty stats.
- ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mFakeIfaceIndexNameMap));
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mIfIndex2Name));
ASSERT_EQ((size_t) 0, lines.size());
lines.clear();
// Test 1 line stats.
populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
- ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mFakeIfaceIndexNameMap));
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mIfIndex2Name));
ASSERT_EQ((size_t) 2, lines.size()); // TEST_TAG != 0 -> 1 entry becomes 2 lines
expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, 0, lines[0]);
expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, TEST_TAG, lines[1]);
@@ -457,7 +461,7 @@
populateFakeStats(TEST_UID1, TEST_TAG + 1, IFACE_INDEX1, TEST_COUNTERSET0, value2,
mFakeStatsMap);
populateFakeStats(TEST_UID2, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
- ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mFakeIfaceIndexNameMap));
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mIfIndex2Name));
ASSERT_EQ((size_t) 9, lines.size());
lines.clear();
@@ -465,7 +469,7 @@
populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX3, TEST_COUNTERSET0, value1, mFakeStatsMap);
populateFakeStats(TEST_UID2, TEST_TAG, IFACE_INDEX3, TEST_COUNTERSET0, value1, mFakeStatsMap);
- ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mFakeIfaceIndexNameMap));
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mIfIndex2Name));
ASSERT_EQ((size_t) 9, lines.size());
// Verify Sorted & Grouped.
@@ -490,8 +494,7 @@
ifaceStatsKey = IFACE_INDEX3;
EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value1, BPF_ANY));
- ASSERT_EQ(0,
- parseBpfNetworkStatsDevInternal(lines, mFakeIfaceStatsMap, mFakeIfaceIndexNameMap));
+ ASSERT_EQ(0, parseBpfNetworkStatsDevInternal(lines, mFakeIfaceStatsMap, mIfIndex2Name));
ASSERT_EQ((size_t) 2, lines.size());
expectStatsLineEqual(value3, IFACE_NAME1, UID_ALL, SET_ALL, TAG_NONE, lines[0]);
@@ -532,7 +535,7 @@
// TODO: Mutate counterSet and enlarge TEST_MAP_SIZE if overflow on counterSet is possible.
std::vector<stats_line> lines;
- ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mFakeIfaceIndexNameMap));
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mIfIndex2Name));
ASSERT_EQ((size_t) 12, lines.size());
// Uid 0 first
diff --git a/service-t/native/libs/libnetworkstats/include/netdbpf/BpfNetworkStats.h b/service-t/native/libs/libnetworkstats/include/netdbpf/BpfNetworkStats.h
index 8058d05..59eb195 100644
--- a/service-t/native/libs/libnetworkstats/include/netdbpf/BpfNetworkStats.h
+++ b/service-t/native/libs/libnetworkstats/include/netdbpf/BpfNetworkStats.h
@@ -55,36 +55,27 @@
bool operator==(const stats_line& lhs, const stats_line& rhs);
bool operator<(const stats_line& lhs, const stats_line& rhs);
+// This mirrors BpfMap.h's:
+// Result<Value> readValue(const Key key) const
+// for a BpfMap<uint32_t, IfaceValue>
+using IfIndexToNameFunc = std::function<Result<IfaceValue>(const uint32_t)>;
+
// For test only
int bpfGetUidStatsInternal(uid_t uid, StatsValue* stats,
const BpfMapRO<uint32_t, StatsValue>& appUidStatsMap);
// For test only
int bpfGetIfaceStatsInternal(const char* iface, StatsValue* stats,
const BpfMapRO<uint32_t, StatsValue>& ifaceStatsMap,
- const BpfMapRO<uint32_t, IfaceValue>& ifaceNameMap);
+ const IfIndexToNameFunc ifindex2name);
// For test only
int bpfGetIfIndexStatsInternal(uint32_t ifindex, StatsValue* stats,
const BpfMapRO<uint32_t, StatsValue>& ifaceStatsMap);
// For test only
int parseBpfNetworkStatsDetailInternal(std::vector<stats_line>& lines,
const BpfMapRO<StatsKey, StatsValue>& statsMap,
- const BpfMapRO<uint32_t, IfaceValue>& ifaceMap);
+ const IfIndexToNameFunc ifindex2name);
// For test only
int cleanStatsMapInternal(const base::unique_fd& cookieTagMap, const base::unique_fd& tagStatsMap);
-// For test only
-template <class Key>
-int getIfaceNameFromMap(const BpfMapRO<uint32_t, IfaceValue>& ifaceMap,
- const BpfMapRO<Key, StatsValue>& statsMap,
- uint32_t ifaceIndex, char* ifname,
- const Key& curKey, int64_t* unknownIfaceBytesTotal) {
- auto iface = ifaceMap.readValue(ifaceIndex);
- if (!iface.ok()) {
- maybeLogUnknownIface(ifaceIndex, statsMap, curKey, unknownIfaceBytesTotal);
- return -ENODEV;
- }
- strlcpy(ifname, iface.value().name, sizeof(IfaceValue));
- return 0;
-}
template <class Key>
void maybeLogUnknownIface(int ifaceIndex, const BpfMapRO<Key, StatsValue>& statsMap,
@@ -112,8 +103,9 @@
// For test only
int parseBpfNetworkStatsDevInternal(std::vector<stats_line>& lines,
const BpfMapRO<uint32_t, StatsValue>& statsMap,
- const BpfMapRO<uint32_t, IfaceValue>& ifaceMap);
+ const IfIndexToNameFunc ifindex2name);
+void bpfRegisterIface(const char* iface);
int bpfGetUidStats(uid_t uid, StatsValue* stats);
int bpfGetIfaceStats(const char* iface, StatsValue* stats);
int bpfGetIfIndexStats(int ifindex, StatsValue* stats);
diff --git a/service-t/src/com/android/server/IpSecXfrmController.java b/service-t/src/com/android/server/IpSecXfrmController.java
index c8abd40..3cfbf83 100644
--- a/service-t/src/com/android/server/IpSecXfrmController.java
+++ b/service-t/src/com/android/server/IpSecXfrmController.java
@@ -15,6 +15,7 @@
*/
package com.android.server;
+import static com.android.net.module.util.netlink.NetlinkUtils.SOCKET_RECV_BUFSIZE;
import static com.android.net.module.util.netlink.xfrm.XfrmNetlinkMessage.IPPROTO_ESP;
import static com.android.net.module.util.netlink.xfrm.XfrmNetlinkMessage.NETLINK_XFRM;
import static com.android.net.module.util.netlink.xfrm.XfrmNetlinkMessage.XFRM_MSG_NEWSA;
@@ -106,7 +107,8 @@
public static class Dependencies {
/** Get a new XFRM netlink socket and connect it */
public FileDescriptor newNetlinkSocket() throws ErrnoException, SocketException {
- final FileDescriptor fd = NetlinkUtils.netlinkSocketForProto(NETLINK_XFRM);
+ final FileDescriptor fd =
+ NetlinkUtils.netlinkSocketForProto(NETLINK_XFRM, SOCKET_RECV_BUFSIZE);
NetlinkUtils.connectToKernel(fd);
return fd;
}
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index 9b2f80b..9ba49d2 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -26,8 +26,8 @@
import static android.net.nsd.NsdManager.MDNS_DISCOVERY_MANAGER_EVENT;
import static android.net.nsd.NsdManager.MDNS_SERVICE_EVENT;
import static android.net.nsd.NsdManager.RESOLVE_SERVICE_SUCCEEDED;
+import static android.net.nsd.NsdManager.SUBTYPE_LABEL_REGEX;
import static android.net.nsd.NsdManager.TYPE_REGEX;
-import static android.net.nsd.NsdManager.TYPE_SUBTYPE_LABEL_REGEX;
import static android.provider.DeviceConfig.NAMESPACE_TETHERING;
import static com.android.modules.utils.build.SdkLevel.isAtLeastU;
@@ -35,6 +35,8 @@
import static com.android.server.connectivity.mdns.MdnsAdvertiser.AdvertiserMetrics;
import static com.android.server.connectivity.mdns.MdnsConstants.NO_PACKET;
import static com.android.server.connectivity.mdns.MdnsRecord.MAX_LABEL_LENGTH;
+import static com.android.server.connectivity.mdns.MdnsSearchOptions.AGGRESSIVE_QUERY_MODE;
+import static com.android.server.connectivity.mdns.MdnsSearchOptions.PASSIVE_QUERY_MODE;
import static com.android.server.connectivity.mdns.util.MdnsUtils.Clock;
import android.annotation.NonNull;
@@ -54,6 +56,7 @@
import android.net.mdns.aidl.RegistrationInfo;
import android.net.mdns.aidl.ResolutionInfo;
import android.net.nsd.AdvertisingRequest;
+import android.net.nsd.DiscoveryRequest;
import android.net.nsd.INsdManager;
import android.net.nsd.INsdManagerCallback;
import android.net.nsd.INsdServiceConnector;
@@ -250,6 +253,8 @@
private final RemoteCallbackList<IOffloadEngine> mOffloadEngines =
new RemoteCallbackList<>();
+ @NonNull
+ private final MdnsFeatureFlags mMdnsFeatureFlags;
private static class OffloadEngineInfo {
@NonNull final String mInterfaceName;
@@ -742,8 +747,8 @@
switch (msg.what) {
case NsdManager.DISCOVER_SERVICES: {
if (DBG) Log.d(TAG, "Discover services");
- final ListenerArgs args = (ListenerArgs) msg.obj;
- clientInfo = mClients.get(args.connector);
+ final DiscoveryArgs discoveryArgs = (DiscoveryArgs) msg.obj;
+ clientInfo = mClients.get(discoveryArgs.connector);
// If the binder death notification for a INsdManagerCallback was received
// before any calls are received by NsdService, the clientInfo would be
// cleared and cause NPE. Add a null check here to prevent this corner case.
@@ -758,10 +763,10 @@
break;
}
- final NsdServiceInfo info = args.serviceInfo;
+ final DiscoveryRequest discoveryRequest = discoveryArgs.discoveryRequest;
transactionId = getUniqueId();
final Pair<String, List<String>> typeAndSubtype =
- parseTypeAndSubtype(info.getServiceType());
+ parseTypeAndSubtype(discoveryRequest.getServiceType());
final String serviceType = typeAndSubtype == null
? null : typeAndSubtype.first;
if (clientInfo.mUseJavaBackend
@@ -773,41 +778,55 @@
break;
}
+ String subtype = discoveryRequest.getSubtype();
+ if (subtype == null && !typeAndSubtype.second.isEmpty()) {
+ subtype = typeAndSubtype.second.get(0);
+ }
+
+ if (subtype != null && !checkSubtypeLabel(subtype)) {
+ clientInfo.onDiscoverServicesFailedImmediately(clientRequestId,
+ NsdManager.FAILURE_BAD_PARAMETERS, false /* isLegacy */);
+ break;
+ }
+
final String listenServiceType = serviceType + ".local";
maybeStartMonitoringSockets();
final MdnsListener listener = new DiscoveryListener(clientRequestId,
transactionId, listenServiceType);
final MdnsSearchOptions.Builder optionsBuilder =
MdnsSearchOptions.newBuilder()
- .setNetwork(info.getNetwork())
+ .setNetwork(discoveryRequest.getNetwork())
.setRemoveExpiredService(true)
- .setIsPassiveMode(true);
- if (!typeAndSubtype.second.isEmpty()) {
- // The parsing ensures subtype starts with an underscore.
+ .setQueryMode(
+ mMdnsFeatureFlags.isAggressiveQueryModeEnabled()
+ ? AGGRESSIVE_QUERY_MODE
+ : PASSIVE_QUERY_MODE);
+ if (subtype != null) {
+ // checkSubtypeLabels() ensures that subtypes start with '_' but
// MdnsSearchOptions expects the underscore to not be present.
- optionsBuilder.addSubtype(
- typeAndSubtype.second.get(0).substring(1));
+ optionsBuilder.addSubtype(subtype.substring(1));
}
mMdnsDiscoveryManager.registerListener(
listenServiceType, listener, optionsBuilder.build());
final ClientRequest request = storeDiscoveryManagerRequestMap(
clientRequestId, transactionId, listener, clientInfo,
- info.getNetwork());
- clientInfo.onDiscoverServicesStarted(clientRequestId, info, request);
+ discoveryRequest.getNetwork());
+ clientInfo.onDiscoverServicesStarted(
+ clientRequestId, discoveryRequest, request);
clientInfo.log("Register a DiscoveryListener " + transactionId
+ " for service type:" + listenServiceType);
} else {
maybeStartDaemon();
- if (discoverServices(transactionId, info)) {
+ if (discoverServices(transactionId, discoveryRequest)) {
if (DBG) {
Log.d(TAG, "Discover " + msg.arg2 + " " + transactionId
- + info.getServiceType());
+ + discoveryRequest.getServiceType());
}
final ClientRequest request = storeLegacyRequestMap(clientRequestId,
transactionId, clientInfo, msg.what,
mClock.elapsedRealtime());
clientInfo.onDiscoverServicesStarted(
- clientRequestId, info, request);
+ clientRequestId, discoveryRequest, request);
} else {
stopServiceDiscovery(transactionId);
clientInfo.onDiscoverServicesFailedImmediately(clientRequestId,
@@ -882,10 +901,18 @@
serviceType);
final String registerServiceType = typeSubtype == null
? null : typeSubtype.first;
+ final String hostname = serviceInfo.getHostname();
+ // Keep compatible with the legacy behavior: It's allowed to set host
+ // addresses for a service registration although the host addresses
+ // won't be registered. To register the addresses for a host, the
+ // hostname must be specified.
+ if (hostname == null) {
+ serviceInfo.setHostAddresses(Collections.emptyList());
+ }
if (clientInfo.mUseJavaBackend
|| mDeps.isMdnsAdvertiserEnabled(mContext)
|| useAdvertiserForType(registerServiceType)) {
- if (registerServiceType == null) {
+ if (serviceType != null && registerServiceType == null) {
Log.e(TAG, "Invalid service type: " + serviceType);
clientInfo.onRegisterServiceFailedImmediately(clientRequestId,
NsdManager.FAILURE_INTERNAL_ERROR, false /* isLegacy */);
@@ -908,14 +935,25 @@
} else {
transactionId = getUniqueId();
}
- serviceInfo.setServiceType(registerServiceType);
- serviceInfo.setServiceName(truncateServiceName(
- serviceInfo.getServiceName()));
+
+ if (registerServiceType != null) {
+ serviceInfo.setServiceType(registerServiceType);
+ serviceInfo.setServiceName(
+ truncateServiceName(serviceInfo.getServiceName()));
+ }
+
+ if (!checkHostname(hostname)) {
+ clientInfo.onRegisterServiceFailedImmediately(clientRequestId,
+ NsdManager.FAILURE_BAD_PARAMETERS, false /* isLegacy */);
+ break;
+ }
Set<String> subtypes = new ArraySet<>(serviceInfo.getSubtypes());
- for (String subType: typeSubtype.second) {
- if (!TextUtils.isEmpty(subType)) {
- subtypes.add(subType);
+ if (typeSubtype != null && typeSubtype.second != null) {
+ for (String subType : typeSubtype.second) {
+ if (!TextUtils.isEmpty(subType)) {
+ subtypes.add(subType);
+ }
}
}
subtypes = dedupSubtypeLabels(subtypes);
@@ -932,7 +970,7 @@
MdnsAdvertisingOptions.newBuilder().setIsOnlyUpdate(
isUpdateOnly).build();
mAdvertiser.addOrUpdateService(transactionId, serviceInfo,
- mdnsAdvertisingOptions);
+ mdnsAdvertisingOptions, clientInfo.mUid);
storeAdvertiserRequestMap(clientRequestId, transactionId, clientInfo,
serviceInfo.getNetwork());
} else {
@@ -1031,7 +1069,9 @@
transactionId, resolveServiceType);
final MdnsSearchOptions options = MdnsSearchOptions.newBuilder()
.setNetwork(info.getNetwork())
- .setIsPassiveMode(true)
+ .setQueryMode(mMdnsFeatureFlags.isAggressiveQueryModeEnabled()
+ ? AGGRESSIVE_QUERY_MODE
+ : PASSIVE_QUERY_MODE)
.setResolveInstanceName(info.getServiceName())
.setRemoveExpiredService(true)
.build();
@@ -1129,7 +1169,9 @@
transactionId, resolveServiceType);
final MdnsSearchOptions options = MdnsSearchOptions.newBuilder()
.setNetwork(info.getNetwork())
- .setIsPassiveMode(true)
+ .setQueryMode(mMdnsFeatureFlags.isAggressiveQueryModeEnabled()
+ ? AGGRESSIVE_QUERY_MODE
+ : PASSIVE_QUERY_MODE)
.setResolveInstanceName(info.getServiceName())
.setRemoveExpiredService(true)
.build();
@@ -1522,6 +1564,7 @@
Log.e(TAG, "Invalid attribute", e);
}
}
+ info.setHostname(getHostname(serviceInfo));
final List<InetAddress> addresses = getInetAddresses(serviceInfo);
if (addresses.size() != 0) {
info.setHostAddresses(addresses);
@@ -1558,6 +1601,7 @@
}
}
+ info.setHostname(getHostname(serviceInfo));
final List<InetAddress> addresses = getInetAddresses(serviceInfo);
info.setHostAddresses(addresses);
clientInfo.onServiceUpdated(clientRequestId, info, request);
@@ -1604,6 +1648,16 @@
return addresses;
}
+ @NonNull
+ private static String getHostname(@NonNull MdnsServiceInfo serviceInfo) {
+ String[] hostname = serviceInfo.getHostName();
+ // Strip the "local" top-level domain.
+ if (hostname.length >= 2 && hostname[hostname.length - 1].equals("local")) {
+ hostname = Arrays.copyOf(hostname, hostname.length - 1);
+ }
+ return String.join(".", hostname);
+ }
+
private static void setServiceNetworkForCallback(NsdServiceInfo info, int netId, int ifaceIdx) {
switch (netId) {
case NETID_UNSET:
@@ -1689,9 +1743,24 @@
return new Pair<>(queryType, Collections.emptyList());
}
+ /**
+ * Checks if the hostname is valid.
+ *
+ * <p>For now NsdService only allows single-label hostnames conforming to RFC 1035. In other
+ * words, the hostname should be at most 63 characters long and it only contains letters, digits
+ * and hyphens.
+ */
+ public static boolean checkHostname(@Nullable String hostname) {
+ if (hostname == null) {
+ return true;
+ }
+ String HOSTNAME_REGEX = "^[a-zA-Z]([a-zA-Z0-9-_]{0,61}[a-zA-Z0-9])?$";
+ return Pattern.compile(HOSTNAME_REGEX).matcher(hostname).matches();
+ }
+
/** Returns {@code true} if {@code subtype} is a valid DNS-SD subtype label. */
private static boolean checkSubtypeLabel(String subtype) {
- return Pattern.compile("^" + TYPE_SUBTYPE_LABEL_REGEX + "$").matcher(subtype).matches();
+ return Pattern.compile("^" + SUBTYPE_LABEL_REGEX + "$").matcher(subtype).matches();
}
@VisibleForTesting
@@ -1728,7 +1797,7 @@
am.addOnUidImportanceListener(new UidImportanceListener(handler),
mRunningAppActiveImportanceCutoff);
- final MdnsFeatureFlags flags = new MdnsFeatureFlags.Builder()
+ mMdnsFeatureFlags = new MdnsFeatureFlags.Builder()
.setIsMdnsOffloadFeatureEnabled(mDeps.isTetheringFeatureNotChickenedOut(
mContext, MdnsFeatureFlags.NSD_FORCE_DISABLE_MDNS_OFFLOAD))
.setIncludeInetAddressRecordsInProbing(mDeps.isFeatureEnabled(
@@ -1741,17 +1810,21 @@
mContext, MdnsFeatureFlags.NSD_KNOWN_ANSWER_SUPPRESSION))
.setIsUnicastReplyEnabled(mDeps.isFeatureEnabled(
mContext, MdnsFeatureFlags.NSD_UNICAST_REPLY_ENABLED))
+ .setIsAggressiveQueryModeEnabled(mDeps.isFeatureEnabled(
+ mContext, MdnsFeatureFlags.NSD_AGGRESSIVE_QUERY_MODE))
.setOverrideProvider(flag -> mDeps.isFeatureEnabled(
mContext, FORCE_ENABLE_FLAG_FOR_TEST_PREFIX + flag))
.build();
mMdnsSocketClient =
new MdnsMultinetworkSocketClient(handler.getLooper(), mMdnsSocketProvider,
- LOGGER.forSubComponent("MdnsMultinetworkSocketClient"), flags);
+ LOGGER.forSubComponent("MdnsMultinetworkSocketClient"), mMdnsFeatureFlags);
mMdnsDiscoveryManager = deps.makeMdnsDiscoveryManager(new ExecutorProvider(),
- mMdnsSocketClient, LOGGER.forSubComponent("MdnsDiscoveryManager"), flags);
+ mMdnsSocketClient, LOGGER.forSubComponent("MdnsDiscoveryManager"),
+ mMdnsFeatureFlags);
handler.post(() -> mMdnsSocketClient.setCallback(mMdnsDiscoveryManager));
mAdvertiser = deps.makeMdnsAdvertiser(handler.getLooper(), mMdnsSocketProvider,
- new AdvertiserCallback(), LOGGER.forSubComponent("MdnsAdvertiser"), flags);
+ new AdvertiserCallback(), LOGGER.forSubComponent("MdnsAdvertiser"),
+ mMdnsFeatureFlags, mContext);
mClock = deps.makeClock();
}
@@ -1807,13 +1880,6 @@
}
/**
- * @see DeviceConfigUtils#isTrunkStableFeatureEnabled
- */
- public boolean isTrunkStableFeatureEnabled(String feature) {
- return DeviceConfigUtils.isTrunkStableFeatureEnabled(feature);
- }
-
- /**
* @see MdnsDiscoveryManager
*/
public MdnsDiscoveryManager makeMdnsDiscoveryManager(
@@ -1830,8 +1896,8 @@
public MdnsAdvertiser makeMdnsAdvertiser(
@NonNull Looper looper, @NonNull MdnsSocketProvider socketProvider,
@NonNull MdnsAdvertiser.AdvertiserCallback cb, @NonNull SharedLog sharedLog,
- MdnsFeatureFlags featureFlags) {
- return new MdnsAdvertiser(looper, socketProvider, cb, sharedLog, featureFlags);
+ MdnsFeatureFlags featureFlags, Context context) {
+ return new MdnsAdvertiser(looper, socketProvider, cb, sharedLog, featureFlags, context);
}
/**
@@ -2017,9 +2083,10 @@
final int clientRequestId = getClientRequestIdOrLog(clientInfo, transactionId);
if (clientRequestId < 0) return;
- // onRegisterServiceSucceeded only has the service name in its info. This aligns with
- // historical behavior.
+ // onRegisterServiceSucceeded only has the service name and hostname in its info. This
+ // aligns with historical behavior.
final NsdServiceInfo cbInfo = new NsdServiceInfo(registeredInfo.getServiceName(), null);
+ cbInfo.setHostname(registeredInfo.getHostname());
final ClientRequest request = clientInfo.mClientRequests.get(clientRequestId);
clientInfo.onRegisterServiceSucceeded(clientRequestId, cbInfo, request);
}
@@ -2114,12 +2181,22 @@
}
}
+ private static final class DiscoveryArgs {
+ public final NsdServiceConnector connector;
+ public final DiscoveryRequest discoveryRequest;
+ DiscoveryArgs(NsdServiceConnector connector, DiscoveryRequest discoveryRequest) {
+ this.connector = connector;
+ this.discoveryRequest = discoveryRequest;
+ }
+ }
+
private class NsdServiceConnector extends INsdServiceConnector.Stub
implements IBinder.DeathRecipient {
@Override
public void registerService(int listenerKey, AdvertisingRequest advertisingRequest)
throws RemoteException {
+ NsdManager.checkServiceInfoForRegistration(advertisingRequest.getServiceInfo());
mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
NsdManager.REGISTER_SERVICE, 0, listenerKey,
new AdvertisingArgs(this, advertisingRequest)
@@ -2134,10 +2211,10 @@
}
@Override
- public void discoverServices(int listenerKey, NsdServiceInfo serviceInfo) {
+ public void discoverServices(int listenerKey, DiscoveryRequest discoveryRequest) {
mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
NsdManager.DISCOVER_SERVICES, 0, listenerKey,
- new ListenerArgs(this, serviceInfo)));
+ new DiscoveryArgs(this, discoveryRequest)));
}
@Override
@@ -2224,7 +2301,7 @@
permissionsList.add(DEVICE_POWER);
}
- if (PermissionUtils.checkAnyPermissionOf(context,
+ if (PermissionUtils.hasAnyPermissionOf(context,
permissionsList.toArray(new String[0]))) {
return;
}
@@ -2276,15 +2353,15 @@
return mMDnsManager.stopOperation(transactionId);
}
- private boolean discoverServices(int transactionId, NsdServiceInfo serviceInfo) {
+ private boolean discoverServices(int transactionId, DiscoveryRequest discoveryRequest) {
if (mMDnsManager == null) {
Log.wtf(TAG, "discoverServices: mMDnsManager is null");
return false;
}
- final String type = serviceInfo.getServiceType();
- final int discoverInterface = getNetworkInterfaceIndex(serviceInfo);
- if (serviceInfo.getNetwork() != null && discoverInterface == IFACE_IDX_ANY) {
+ final String type = discoveryRequest.getServiceType();
+ final int discoverInterface = getNetworkInterfaceIndex(discoveryRequest);
+ if (discoveryRequest.getNetwork() != null && discoverInterface == IFACE_IDX_ANY) {
Log.e(TAG, "Interface to discover service on not found");
return false;
}
@@ -2334,7 +2411,26 @@
}
return IFACE_IDX_ANY;
}
+ return getNetworkInterfaceIndex(network);
+ }
+ /**
+ * Returns the interface to use to discover a service on a specific network, or {@link
+ * IFACE_IDX_ANY} if no network is specified.
+ */
+ private int getNetworkInterfaceIndex(DiscoveryRequest discoveryRequest) {
+ final Network network = discoveryRequest.getNetwork();
+ if (network == null) {
+ return IFACE_IDX_ANY;
+ }
+ return getNetworkInterfaceIndex(network);
+ }
+
+ /**
+ * Returns the interface of a specific network, or {@link IFACE_IDX_ANY} if no interface is
+ * associated with {@code network}.
+ */
+ private int getNetworkInterfaceIndex(@NonNull Network network) {
String interfaceName = getNetworkInterfaceName(network);
if (interfaceName == null) {
return IFACE_IDX_ANY;
@@ -2402,7 +2498,7 @@
@Override
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
- if (!PermissionUtils.checkDumpPermission(mContext, TAG, writer)) return;
+ if (!PermissionUtils.hasDumpPermission(mContext, TAG, writer)) return;
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
// Dump state machine logs
@@ -2520,7 +2616,15 @@
/* Information tracked per client */
private class ClientInfo {
- private static final int MAX_LIMIT = 10;
+ /**
+ * Maximum number of requests (callbacks) for a client.
+ *
+ * 200 listeners should be more than enough for most use-cases: even if a client tries to
+ * file callbacks for every service on a local network, there are generally much less than
+ * 200 devices on a local network (a /24 only allows 255 IPv4 devices), and while some
+ * devices may have multiple services, many devices do not advertise any.
+ */
+ private static final int MAX_LIMIT = 200;
private final INsdManagerCallback mCb;
/* Remembers a resolved service until getaddrinfo completes */
private NsdServiceInfo mResolvedService;
@@ -2709,12 +2813,12 @@
&& !(request instanceof AdvertiserClientRequest);
}
- void onDiscoverServicesStarted(int listenerKey, NsdServiceInfo info,
+ void onDiscoverServicesStarted(int listenerKey, DiscoveryRequest discoveryRequest,
ClientRequest request) {
mMetrics.reportServiceDiscoveryStarted(
isLegacyClientRequest(request), request.mTransactionId);
try {
- mCb.onDiscoverServicesStarted(listenerKey, info);
+ mCb.onDiscoverServicesStarted(listenerKey, discoveryRequest);
} catch (RemoteException e) {
Log.e(TAG, "Error calling onDiscoverServicesStarted", e);
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
index 135d957..0b60572 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
@@ -17,11 +17,14 @@
package com.android.server.connectivity.mdns;
import static com.android.server.connectivity.mdns.MdnsConstants.NO_PACKET;
+import static com.android.server.connectivity.mdns.MdnsInterfaceAdvertiser.CONFLICT_HOST;
+import static com.android.server.connectivity.mdns.MdnsInterfaceAdvertiser.CONFLICT_SERVICE;
import static com.android.server.connectivity.mdns.MdnsRecord.MAX_LABEL_LENGTH;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresApi;
+import android.content.Context;
import android.net.LinkAddress;
import android.net.Network;
import android.net.nsd.NsdManager;
@@ -30,13 +33,16 @@
import android.net.nsd.OffloadServiceInfo;
import android.os.Build;
import android.os.Looper;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import android.util.SparseArray;
+import com.android.connectivity.resources.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.SharedLog;
+import com.android.server.connectivity.ConnectivityResources;
import com.android.server.connectivity.mdns.util.MdnsUtils;
import java.util.ArrayList;
@@ -84,6 +90,7 @@
private final Map<String, List<OffloadServiceInfoWrapper>> mInterfaceOffloadServices =
new ArrayMap<>();
private final MdnsFeatureFlags mMdnsFeatureFlags;
+ private final Map<String, Integer> mServiceTypeToOffloadPriority;
/**
* Dependencies for {@link MdnsAdvertiser}, useful for testing.
@@ -147,7 +154,9 @@
mSharedLog.wtf("Register succeeded for unknown registration");
return;
}
- if (mMdnsFeatureFlags.mIsMdnsOffloadFeatureEnabled) {
+ if (mMdnsFeatureFlags.mIsMdnsOffloadFeatureEnabled
+ // TODO: Enable offload when the serviceInfo contains a custom host.
+ && TextUtils.isEmpty(registration.getServiceInfo().getHostname())) {
final String interfaceName = advertiser.getSocketInterfaceName();
final List<OffloadServiceInfoWrapper> existingOffloadServiceInfoWrappers =
mInterfaceOffloadServices.computeIfAbsent(interfaceName,
@@ -175,8 +184,11 @@
}
@Override
- public void onServiceConflict(@NonNull MdnsInterfaceAdvertiser advertiser, int serviceId) {
- mSharedLog.i("Found conflict, restarted probing for service " + serviceId);
+ public void onServiceConflict(@NonNull MdnsInterfaceAdvertiser advertiser, int serviceId,
+ int conflictType) {
+ mSharedLog.i("Found conflict, restarted probing for service "
+ + serviceId + " "
+ + conflictType);
final Registration registration = mRegistrations.get(serviceId);
if (registration == null) return;
@@ -201,10 +213,22 @@
return;
}
- // Conflict was found during probing; rename once to find a name that has no conflict
- registration.updateForConflict(
- registration.makeNewServiceInfoForConflict(1 /* renameCount */),
- 1 /* renameCount */);
+ if ((conflictType & CONFLICT_SERVICE) != 0) {
+ // Service conflict was found during probing; rename once to find a name that has no
+ // conflict
+ registration.updateForServiceConflict(
+ registration.makeNewServiceInfoForServiceConflict(1 /* renameCount */),
+ 1 /* renameCount */);
+ }
+
+ if ((conflictType & CONFLICT_HOST) != 0) {
+ // Host conflict was found during probing; rename once to find a name that has no
+ // conflict
+ registration.updateForHostConflict(
+ registration.makeNewServiceInfoForHostConflict(1 /* renameCount */),
+ 1 /* renameCount */);
+ }
+
registration.mConflictDuringProbingCount++;
// Keep renaming if the new name conflicts in local registrations
@@ -227,23 +251,54 @@
}
};
- private boolean hasAnyConflict(
+ private boolean hasAnyServiceConflict(
@NonNull BiPredicate<Network, InterfaceAdvertiserRequest> applicableAdvertiserFilter,
- @NonNull NsdServiceInfo newInfo) {
- return any(mAdvertiserRequests, (network, adv) ->
- applicableAdvertiserFilter.test(network, adv) && adv.hasConflict(newInfo));
+ @NonNull NsdServiceInfo newInfo,
+ @NonNull Registration originalRegistration) {
+ return any(
+ mAdvertiserRequests,
+ (network, adv) ->
+ applicableAdvertiserFilter.test(network, adv)
+ && adv.hasServiceConflict(newInfo, originalRegistration));
+ }
+
+ private boolean hasAnyHostConflict(
+ @NonNull BiPredicate<Network, InterfaceAdvertiserRequest> applicableAdvertiserFilter,
+ @NonNull NsdServiceInfo newInfo,
+ int clientUid) {
+ // Check if it conflicts with custom hosts.
+ if (any(
+ mAdvertiserRequests,
+ (network, adv) ->
+ applicableAdvertiserFilter.test(network, adv)
+ && adv.hasHostConflict(newInfo, clientUid))) {
+ return true;
+ }
+ // Check if it conflicts with the default hostname.
+ return MdnsUtils.equalsIgnoreDnsCase(newInfo.getHostname(), mDeviceHostName[0]);
}
private void updateRegistrationUntilNoConflict(
@NonNull BiPredicate<Network, InterfaceAdvertiserRequest> applicableAdvertiserFilter,
@NonNull Registration registration) {
- int renameCount = 0;
NsdServiceInfo newInfo = registration.getServiceInfo();
- while (hasAnyConflict(applicableAdvertiserFilter, newInfo)) {
- renameCount++;
- newInfo = registration.makeNewServiceInfoForConflict(renameCount);
+
+ int renameServiceCount = 0;
+ while (hasAnyServiceConflict(applicableAdvertiserFilter, newInfo, registration)) {
+ renameServiceCount++;
+ newInfo = registration.makeNewServiceInfoForServiceConflict(renameServiceCount);
}
- registration.updateForConflict(newInfo, renameCount);
+ registration.updateForServiceConflict(newInfo, renameServiceCount);
+
+ if (!TextUtils.isEmpty(registration.getServiceInfo().getHostname())) {
+ int renameHostCount = 0;
+ while (hasAnyHostConflict(
+ applicableAdvertiserFilter, newInfo, registration.mClientUid)) {
+ renameHostCount++;
+ newInfo = registration.makeNewServiceInfoForHostConflict(renameHostCount);
+ }
+ registration.updateForHostConflict(newInfo, renameHostCount);
+ }
}
private void maybeSendOffloadStop(final String interfaceName, int serviceId) {
@@ -322,17 +377,34 @@
/**
* Return whether using the proposed new {@link NsdServiceInfo} to add a registration would
- * cause a conflict in this {@link InterfaceAdvertiserRequest}.
+ * cause a conflict of the service in this {@link InterfaceAdvertiserRequest}.
*/
- boolean hasConflict(@NonNull NsdServiceInfo newInfo) {
- return getConflictingService(newInfo) >= 0;
+ boolean hasServiceConflict(
+ @NonNull NsdServiceInfo newInfo, @NonNull Registration originalRegistration) {
+ return getConflictingRegistrationDueToService(newInfo, originalRegistration) >= 0;
}
/**
- * Get the ID of a conflicting service, or -1 if none.
+ * Return whether using the proposed new {@link NsdServiceInfo} to add a registration would
+ * cause a conflict of the host in this {@link InterfaceAdvertiserRequest}.
+ *
+ * @param clientUid UID of the user who wants to advertise the serviceInfo.
*/
- int getConflictingService(@NonNull NsdServiceInfo info) {
+ boolean hasHostConflict(@NonNull NsdServiceInfo newInfo, int clientUid) {
+ return getConflictingRegistrationDueToHost(newInfo, clientUid) >= 0;
+ }
+
+ /** Get the ID of a conflicting registration due to service, or -1 if none. */
+ int getConflictingRegistrationDueToService(
+ @NonNull NsdServiceInfo info, @NonNull Registration originalRegistration) {
+ if (TextUtils.isEmpty(info.getServiceName())) {
+ return -1;
+ }
for (int i = 0; i < mPendingRegistrations.size(); i++) {
+ // Never conflict with itself
+ if (mPendingRegistrations.valueAt(i) == originalRegistration) {
+ continue;
+ }
final NsdServiceInfo other = mPendingRegistrations.valueAt(i).getServiceInfo();
if (MdnsUtils.equalsIgnoreDnsCase(info.getServiceName(), other.getServiceName())
&& MdnsUtils.equalsIgnoreDnsCase(info.getServiceType(),
@@ -344,9 +416,34 @@
}
/**
+ * Get the ID of a conflicting registration due to host, or -1 if none.
+ *
+ * <p>It's valid that multiple registrations from the same user are using the same hostname.
+ *
+ * <p>If there's already another registration with the same hostname requested by another
+ * user, this is considered a conflict.
+ */
+ int getConflictingRegistrationDueToHost(@NonNull NsdServiceInfo info, int clientUid) {
+ if (TextUtils.isEmpty(info.getHostname())) {
+ return -1;
+ }
+ for (int i = 0; i < mPendingRegistrations.size(); i++) {
+ final Registration otherRegistration = mPendingRegistrations.valueAt(i);
+ final NsdServiceInfo otherInfo = otherRegistration.getServiceInfo();
+ if (clientUid != otherRegistration.mClientUid
+ && MdnsUtils.equalsIgnoreDnsCase(
+ info.getHostname(), otherInfo.getHostname())) {
+ return mPendingRegistrations.keyAt(i);
+ }
+ }
+ return -1;
+ }
+
+ /**
* Add a service to advertise.
*
- * Conflicts must be checked via {@link #getConflictingService} before attempting to add.
+ * <p>Conflicts must be checked via {@link #getConflictingRegistrationDueToService} and
+ * {@link #getConflictingRegistrationDueToHost} before attempting to add.
*/
void addService(int id, @NonNull Registration registration) {
mPendingRegistrations.put(id, registration);
@@ -480,27 +577,35 @@
}
private static class Registration {
- @NonNull
- final String mOriginalName;
+ @Nullable
+ final String mOriginalServiceName;
+ @Nullable
+ final String mOriginalHostname;
boolean mNotifiedRegistrationSuccess;
- private int mConflictCount;
+ private int mServiceNameConflictCount;
+ private int mHostnameConflictCount;
@NonNull
private NsdServiceInfo mServiceInfo;
+ final int mClientUid;
int mConflictDuringProbingCount;
int mConflictAfterProbingCount;
- private Registration(@NonNull NsdServiceInfo serviceInfo) {
- this.mOriginalName = serviceInfo.getServiceName();
+
+ private Registration(@NonNull NsdServiceInfo serviceInfo, int clientUid) {
+ this.mOriginalServiceName = serviceInfo.getServiceName();
+ this.mOriginalHostname = serviceInfo.getHostname();
this.mServiceInfo = serviceInfo;
+ this.mClientUid = clientUid;
}
- /**
- * Matches between the NsdServiceInfo in the Registration and the provided argument.
- */
- public boolean matches(@Nullable NsdServiceInfo newInfo) {
- return Objects.equals(newInfo.getServiceName(), mOriginalName) && Objects.equals(
- newInfo.getServiceType(), mServiceInfo.getServiceType()) && Objects.equals(
- newInfo.getNetwork(), mServiceInfo.getNetwork());
+ /** Check if the new {@link NsdServiceInfo} doesn't update any data other than subtypes. */
+ public boolean isSubtypeOnlyUpdate(@NonNull NsdServiceInfo newInfo) {
+ return Objects.equals(newInfo.getServiceName(), mOriginalServiceName)
+ && Objects.equals(newInfo.getServiceType(), mServiceInfo.getServiceType())
+ && newInfo.getPort() == mServiceInfo.getPort()
+ && Objects.equals(newInfo.getHostname(), mOriginalHostname)
+ && Objects.equals(newInfo.getHostAddresses(), mServiceInfo.getHostAddresses())
+ && Objects.equals(newInfo.getNetwork(), mServiceInfo.getNetwork());
}
/**
@@ -517,8 +622,19 @@
* @param newInfo New service info to use.
* @param renameCount How many renames were done before reaching the current name.
*/
- private void updateForConflict(@NonNull NsdServiceInfo newInfo, int renameCount) {
- mConflictCount += renameCount;
+ private void updateForServiceConflict(@NonNull NsdServiceInfo newInfo, int renameCount) {
+ mServiceNameConflictCount += renameCount;
+ mServiceInfo = newInfo;
+ }
+
+ /**
+ * Update the registration to use a different host name, after a conflict was found.
+ *
+ * @param newInfo New service info to use.
+ * @param renameCount How many renames were done before reaching the current name.
+ */
+ private void updateForHostConflict(@NonNull NsdServiceInfo newInfo, int renameCount) {
+ mHostnameConflictCount += renameCount;
mServiceInfo = newInfo;
}
@@ -534,7 +650,7 @@
* @param renameCount How much to increase the number suffix for this conflict.
*/
@NonNull
- public NsdServiceInfo makeNewServiceInfoForConflict(int renameCount) {
+ public NsdServiceInfo makeNewServiceInfoForServiceConflict(int renameCount) {
// In case of conflict choose a different service name. After the first conflict use
// "Name (2)", then "Name (3)" etc.
// TODO: use a hidden method in NsdServiceInfo once MdnsAdvertiser is moved to service-t
@@ -543,13 +659,40 @@
return newInfo;
}
+ /**
+ * Make a new hostname for the registration, after a conflict was found.
+ *
+ * <p>If a name conflict was found during probing or because different advertising requests
+ * used the same name, the registration is attempted again with a new name (here using a
+ * number suffix, -1, -2, etc). Registration success is notified once probing succeeds with
+ * a new name.
+ *
+ * @param renameCount How much to increase the number suffix for this conflict.
+ */
+ @NonNull
+ public NsdServiceInfo makeNewServiceInfoForHostConflict(int renameCount) {
+ // In case of conflict choose a different hostname. After the first conflict use
+ // "Name-2", then "Name-3" etc.
+ final NsdServiceInfo newInfo = new NsdServiceInfo(mServiceInfo);
+ newInfo.setHostname(getUpdatedHostname(renameCount));
+ return newInfo;
+ }
+
private String getUpdatedServiceName(int renameCount) {
- final String suffix = " (" + (mConflictCount + renameCount + 1) + ")";
- final String truncatedServiceName = MdnsUtils.truncateServiceName(mOriginalName,
+ final String suffix = " (" + (mServiceNameConflictCount + renameCount + 1) + ")";
+ final String truncatedServiceName = MdnsUtils.truncateServiceName(mOriginalServiceName,
MAX_LABEL_LENGTH - suffix.length());
return truncatedServiceName + suffix;
}
+ private String getUpdatedHostname(int renameCount) {
+ final String suffix = "-" + (mHostnameConflictCount + renameCount + 1);
+ final String truncatedHostname =
+ MdnsUtils.truncateServiceName(
+ mOriginalHostname, MAX_LABEL_LENGTH - suffix.length());
+ return truncatedHostname + suffix;
+ }
+
@NonNull
public NsdServiceInfo getServiceInfo() {
return mServiceInfo;
@@ -621,14 +764,16 @@
public MdnsAdvertiser(@NonNull Looper looper, @NonNull MdnsSocketProvider socketProvider,
@NonNull AdvertiserCallback cb, @NonNull SharedLog sharedLog,
- @NonNull MdnsFeatureFlags mDnsFeatureFlags) {
- this(looper, socketProvider, cb, new Dependencies(), sharedLog, mDnsFeatureFlags);
+ @NonNull MdnsFeatureFlags mDnsFeatureFlags, @NonNull Context context) {
+ this(looper, socketProvider, cb, new Dependencies(), sharedLog, mDnsFeatureFlags,
+ context);
}
@VisibleForTesting
MdnsAdvertiser(@NonNull Looper looper, @NonNull MdnsSocketProvider socketProvider,
@NonNull AdvertiserCallback cb, @NonNull Dependencies deps,
- @NonNull SharedLog sharedLog, @NonNull MdnsFeatureFlags mDnsFeatureFlags) {
+ @NonNull SharedLog sharedLog, @NonNull MdnsFeatureFlags mDnsFeatureFlags,
+ @NonNull Context context) {
mLooper = looper;
mCb = cb;
mSocketProvider = socketProvider;
@@ -636,6 +781,31 @@
mDeviceHostName = deps.generateHostname();
mSharedLog = sharedLog;
mMdnsFeatureFlags = mDnsFeatureFlags;
+ final ConnectivityResources res = new ConnectivityResources(context);
+ mServiceTypeToOffloadPriority = parseOffloadPriorityList(
+ res.get().getStringArray(R.array.config_nsdOffloadServicesPriority), sharedLog);
+ }
+
+ private static Map<String, Integer> parseOffloadPriorityList(
+ @NonNull String[] resValues, SharedLog sharedLog) {
+ final Map<String, Integer> priorities = new ArrayMap<>(resValues.length);
+ for (String entry : resValues) {
+ final String[] priorityAndType = entry.split(":", 2);
+ if (priorityAndType.length != 2) {
+ sharedLog.wtf("Invalid config_nsdOffloadServicesPriority ignored: " + entry);
+ continue;
+ }
+
+ final int priority;
+ try {
+ priority = Integer.parseInt(priorityAndType[0]);
+ } catch (NumberFormatException e) {
+ sharedLog.wtf("Invalid priority in config_nsdOffloadServicesPriority: " + entry);
+ continue;
+ }
+ priorities.put(MdnsUtils.toDnsLowerCase(priorityAndType[1]), priority);
+ }
+ return priorities;
}
private void checkThread() {
@@ -650,9 +820,10 @@
* @param id A unique ID for the service.
* @param service The service info to advertise.
* @param advertisingOptions The advertising options.
+ * @param clientUid The UID who wants to advertise the service.
*/
public void addOrUpdateService(int id, NsdServiceInfo service,
- MdnsAdvertisingOptions advertisingOptions) {
+ MdnsAdvertisingOptions advertisingOptions, int clientUid) {
checkThread();
final Registration existingRegistration = mRegistrations.get(id);
final Network network = service.getNetwork();
@@ -664,7 +835,7 @@
mCb.onRegisterServiceFailed(id, NsdManager.FAILURE_INTERNAL_ERROR);
return;
}
- if (!(existingRegistration.matches(service))) {
+ if (!(existingRegistration.isSubtypeOnlyUpdate(service))) {
mSharedLog.e("Update request can only update subType, serviceInfo: " + service
+ ", existing serviceInfo: " + existingRegistration.getServiceInfo());
mCb.onRegisterServiceFailed(id, NsdManager.FAILURE_INTERNAL_ERROR);
@@ -684,7 +855,7 @@
}
mSharedLog.i("Adding service " + service + " with ID " + id + " and subtypes "
+ subtypes + " advertisingOptions " + advertisingOptions);
- registration = new Registration(service);
+ registration = new Registration(service, clientUid);
final BiPredicate<Network, InterfaceAdvertiserRequest> checkConflictFilter;
if (network == null) {
// If registering on all networks, no advertiser must have conflicts
@@ -777,16 +948,17 @@
private OffloadServiceInfoWrapper createOffloadService(int serviceId,
@NonNull Registration registration, byte[] rawOffloadPacket) {
final NsdServiceInfo nsdServiceInfo = registration.getServiceInfo();
+ final Integer mapPriority = mServiceTypeToOffloadPriority.get(
+ MdnsUtils.toDnsLowerCase(nsdServiceInfo.getServiceType()));
+ // Higher values of priority are less prioritized
+ final int priority = mapPriority == null ? Integer.MAX_VALUE : mapPriority;
final OffloadServiceInfo offloadServiceInfo = new OffloadServiceInfo(
new OffloadServiceInfo.Key(nsdServiceInfo.getServiceName(),
nsdServiceInfo.getServiceType()),
new ArrayList<>(nsdServiceInfo.getSubtypes()),
String.join(".", mDeviceHostName),
rawOffloadPacket,
- // TODO: define overlayable resources in
- // ServiceConnectivityResources that set the priority based on
- // service type.
- 0 /* priority */,
+ priority,
// TODO: set the offloadType based on the callback timing.
OffloadEngine.OFFLOAD_TYPE_REPLY);
return new OffloadServiceInfoWrapper(serviceId, offloadServiceInfo);
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java b/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
index 766f999..1d6039c 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
@@ -16,18 +16,19 @@
package com.android.server.connectivity.mdns;
-import static com.android.server.connectivity.mdns.util.MdnsUtils.ensureRunningOnHandlerThread;
-
import android.Manifest.permission;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.Looper;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
+import androidx.annotation.GuardedBy;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.net.module.util.SharedLog;
import com.android.server.connectivity.mdns.util.MdnsUtils;
@@ -36,6 +37,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.Executor;
/**
* This class keeps tracking the set of registered {@link MdnsServiceBrowserListener} instances, and
@@ -50,11 +52,13 @@
@NonNull private final SharedLog sharedLog;
@NonNull private final PerSocketServiceTypeClients perSocketServiceTypeClients;
- @NonNull private final Handler handler;
- @Nullable private final HandlerThread handlerThread;
- @NonNull private final MdnsServiceCache serviceCache;
+ @NonNull private final DiscoveryExecutor discoveryExecutor;
@NonNull private final MdnsFeatureFlags mdnsFeatureFlags;
+ // Only accessed on the handler thread, initialized before first use
+ @Nullable
+ private MdnsServiceCache serviceCache;
+
private static class PerSocketServiceTypeClients {
private final ArrayMap<Pair<String, SocketKey>, MdnsServiceTypeClient> clients =
new ArrayMap<>();
@@ -125,33 +129,82 @@
this.sharedLog = sharedLog;
this.perSocketServiceTypeClients = new PerSocketServiceTypeClients();
this.mdnsFeatureFlags = mdnsFeatureFlags;
- if (socketClient.getLooper() != null) {
- this.handlerThread = null;
- this.handler = new Handler(socketClient.getLooper());
- this.serviceCache = new MdnsServiceCache(socketClient.getLooper(), mdnsFeatureFlags);
- } else {
- this.handlerThread = new HandlerThread(MdnsDiscoveryManager.class.getSimpleName());
- this.handlerThread.start();
- this.handler = new Handler(handlerThread.getLooper());
- this.serviceCache = new MdnsServiceCache(handlerThread.getLooper(), mdnsFeatureFlags);
- }
+ this.discoveryExecutor = new DiscoveryExecutor(socketClient.getLooper());
}
- private void checkAndRunOnHandlerThread(@NonNull Runnable function) {
- if (this.handlerThread == null) {
- function.run();
- } else {
+ private static class DiscoveryExecutor implements Executor {
+ private final HandlerThread handlerThread;
+
+ @GuardedBy("pendingTasks")
+ @Nullable private Handler handler;
+ @GuardedBy("pendingTasks")
+ @NonNull private final ArrayList<Runnable> pendingTasks = new ArrayList<>();
+
+ DiscoveryExecutor(@Nullable Looper defaultLooper) {
+ if (defaultLooper != null) {
+ this.handlerThread = null;
+ synchronized (pendingTasks) {
+ this.handler = new Handler(defaultLooper);
+ }
+ } else {
+ this.handlerThread = new HandlerThread(MdnsDiscoveryManager.class.getSimpleName()) {
+ @Override
+ protected void onLooperPrepared() {
+ synchronized (pendingTasks) {
+ handler = new Handler(getLooper());
+ for (Runnable pendingTask : pendingTasks) {
+ handler.post(pendingTask);
+ }
+ pendingTasks.clear();
+ }
+ }
+ };
+ this.handlerThread.start();
+ }
+ }
+
+ public void checkAndRunOnHandlerThread(@NonNull Runnable function) {
+ if (this.handlerThread == null) {
+ // Callers are expected to already be running on the handler when a defaultLooper
+ // was provided
+ function.run();
+ } else {
+ execute(function);
+ }
+ }
+
+ @Override
+ public void execute(Runnable function) {
+ final Handler handler;
+ synchronized (pendingTasks) {
+ if (this.handler == null) {
+ pendingTasks.add(function);
+ return;
+ } else {
+ handler = this.handler;
+ }
+ }
handler.post(function);
}
+
+ void shutDown() {
+ if (this.handlerThread != null) {
+ this.handlerThread.quitSafely();
+ }
+ }
+
+ void ensureRunningOnHandlerThread() {
+ synchronized (pendingTasks) {
+ MdnsUtils.ensureRunningOnHandlerThread(handler);
+ }
+ }
}
/**
* Do the cleanup of the MdnsDiscoveryManager
*/
public void shutDown() {
- if (this.handlerThread != null) {
- this.handlerThread.quitSafely();
- }
+ discoveryExecutor.shutDown();
}
/**
@@ -169,7 +222,7 @@
@NonNull MdnsServiceBrowserListener listener,
@NonNull MdnsSearchOptions searchOptions) {
sharedLog.i("Registering listener for serviceType: " + serviceType);
- checkAndRunOnHandlerThread(() ->
+ discoveryExecutor.checkAndRunOnHandlerThread(() ->
handleRegisterListener(serviceType, listener, searchOptions));
}
@@ -191,7 +244,7 @@
new MdnsSocketClientBase.SocketCreationCallback() {
@Override
public void onSocketCreated(@NonNull SocketKey socketKey) {
- ensureRunningOnHandlerThread(handler);
+ discoveryExecutor.ensureRunningOnHandlerThread();
// All listeners of the same service types shares the same
// MdnsServiceTypeClient.
MdnsServiceTypeClient serviceTypeClient =
@@ -206,7 +259,7 @@
@Override
public void onSocketDestroyed(@NonNull SocketKey socketKey) {
- ensureRunningOnHandlerThread(handler);
+ discoveryExecutor.ensureRunningOnHandlerThread();
final MdnsServiceTypeClient serviceTypeClient =
perSocketServiceTypeClients.get(serviceType, socketKey);
if (serviceTypeClient == null) return;
@@ -229,7 +282,8 @@
public void unregisterListener(
@NonNull String serviceType, @NonNull MdnsServiceBrowserListener listener) {
sharedLog.i("Unregistering listener for serviceType:" + serviceType);
- checkAndRunOnHandlerThread(() -> handleUnregisterListener(serviceType, listener));
+ discoveryExecutor.checkAndRunOnHandlerThread(() ->
+ handleUnregisterListener(serviceType, listener));
}
private void handleUnregisterListener(
@@ -260,7 +314,7 @@
@Override
public void onResponseReceived(@NonNull MdnsPacket packet, @NonNull SocketKey socketKey) {
- checkAndRunOnHandlerThread(() ->
+ discoveryExecutor.checkAndRunOnHandlerThread(() ->
handleOnResponseReceived(packet, socketKey));
}
@@ -282,7 +336,7 @@
@Override
public void onFailedToParseMdnsResponse(int receivedPacketNumber, int errorCode,
@NonNull SocketKey socketKey) {
- checkAndRunOnHandlerThread(() ->
+ discoveryExecutor.checkAndRunOnHandlerThread(() ->
handleOnFailedToParseMdnsResponse(receivedPacketNumber, errorCode, socketKey));
}
@@ -296,12 +350,17 @@
@VisibleForTesting
MdnsServiceTypeClient createServiceTypeClient(@NonNull String serviceType,
@NonNull SocketKey socketKey) {
+ discoveryExecutor.ensureRunningOnHandlerThread();
sharedLog.log("createServiceTypeClient for type:" + serviceType + " " + socketKey);
final String tag = serviceType + "-" + socketKey.getNetwork()
+ "/" + socketKey.getInterfaceIndex();
+ final Looper looper = Looper.myLooper();
+ if (serviceCache == null) {
+ serviceCache = new MdnsServiceCache(looper, mdnsFeatureFlags);
+ }
return new MdnsServiceTypeClient(
serviceType, socketClient,
executorProvider.newServiceTypeClientSchedulerExecutor(), socketKey,
- sharedLog.forSubComponent(tag), handler.getLooper(), serviceCache);
+ sharedLog.forSubComponent(tag), looper, serviceCache);
}
}
\ No newline at end of file
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java b/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
index 9466162..fe9bbba 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
@@ -57,6 +57,11 @@
*/
public static final String NSD_UNICAST_REPLY_ENABLED = "nsd_unicast_reply_enabled";
+ /**
+ * A feature flag to control whether the aggressive query mode should be enabled.
+ */
+ public static final String NSD_AGGRESSIVE_QUERY_MODE = "nsd_aggressive_query_mode";
+
// Flag for offload feature
public final boolean mIsMdnsOffloadFeatureEnabled;
@@ -75,6 +80,9 @@
// Flag to enable replying unicast to queries requesting unicast replies
public final boolean mIsUnicastReplyEnabled;
+ // Flag for aggressive query mode
+ public final boolean mIsAggressiveQueryModeEnabled;
+
@Nullable
private final FlagOverrideProvider mOverrideProvider;
@@ -103,6 +111,13 @@
}
/**
+ * Indicates whether {@link #NSD_AGGRESSIVE_QUERY_MODE} is enabled, including for testing.
+ */
+ public boolean isAggressiveQueryModeEnabled() {
+ return mIsAggressiveQueryModeEnabled || isForceEnabledForTest(NSD_AGGRESSIVE_QUERY_MODE);
+ }
+
+ /**
* The constructor for {@link MdnsFeatureFlags}.
*/
public MdnsFeatureFlags(boolean isOffloadFeatureEnabled,
@@ -111,6 +126,7 @@
boolean isLabelCountLimitEnabled,
boolean isKnownAnswerSuppressionEnabled,
boolean isUnicastReplyEnabled,
+ boolean isAggressiveQueryModeEnabled,
@Nullable FlagOverrideProvider overrideProvider) {
mIsMdnsOffloadFeatureEnabled = isOffloadFeatureEnabled;
mIncludeInetAddressRecordsInProbing = includeInetAddressRecordsInProbing;
@@ -118,6 +134,7 @@
mIsLabelCountLimitEnabled = isLabelCountLimitEnabled;
mIsKnownAnswerSuppressionEnabled = isKnownAnswerSuppressionEnabled;
mIsUnicastReplyEnabled = isUnicastReplyEnabled;
+ mIsAggressiveQueryModeEnabled = isAggressiveQueryModeEnabled;
mOverrideProvider = overrideProvider;
}
@@ -136,6 +153,7 @@
private boolean mIsLabelCountLimitEnabled;
private boolean mIsKnownAnswerSuppressionEnabled;
private boolean mIsUnicastReplyEnabled;
+ private boolean mIsAggressiveQueryModeEnabled;
private FlagOverrideProvider mOverrideProvider;
/**
@@ -148,6 +166,7 @@
mIsLabelCountLimitEnabled = true; // Default enabled.
mIsKnownAnswerSuppressionEnabled = false;
mIsUnicastReplyEnabled = true;
+ mIsAggressiveQueryModeEnabled = false;
mOverrideProvider = null;
}
@@ -224,6 +243,16 @@
}
/**
+ * Set whether the aggressive query mode is enabled.
+ *
+ * @see #NSD_AGGRESSIVE_QUERY_MODE
+ */
+ public Builder setIsAggressiveQueryModeEnabled(boolean isAggressiveQueryModeEnabled) {
+ mIsAggressiveQueryModeEnabled = isAggressiveQueryModeEnabled;
+ return this;
+ }
+
+ /**
* Builds a {@link MdnsFeatureFlags} with the arguments supplied to this builder.
*/
public MdnsFeatureFlags build() {
@@ -233,6 +262,7 @@
mIsLabelCountLimitEnabled,
mIsKnownAnswerSuppressionEnabled,
mIsUnicastReplyEnabled,
+ mIsAggressiveQueryModeEnabled,
mOverrideProvider);
}
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
index 730bd7e..aa51c41 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
@@ -22,10 +22,12 @@
import android.annotation.Nullable;
import android.annotation.RequiresApi;
import android.net.LinkAddress;
+import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
+import android.util.ArraySet;
import com.android.internal.annotations.VisibleForTesting;
import com.android.net.module.util.HexDump;
@@ -37,6 +39,7 @@
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.List;
+import java.util.Map;
import java.util.Set;
/**
@@ -44,6 +47,9 @@
*/
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
public class MdnsInterfaceAdvertiser implements MulticastPacketReader.PacketHandler {
+ public static final int CONFLICT_SERVICE = 1 << 0;
+ public static final int CONFLICT_HOST = 1 << 1;
+
private static final boolean DBG = MdnsAdvertiser.DBG;
@VisibleForTesting
public static final long EXIT_ANNOUNCEMENT_DELAY_MS = 100L;
@@ -85,10 +91,15 @@
/**
* Called by the advertiser when a conflict was found, during or after probing.
*
- * If a conflict is found during probing, the {@link #renameServiceForConflict} must be
+ * <p>If a conflict is found during probing, the {@link #renameServiceForConflict} must be
* called to restart probing and attempt registration with a different name.
+ *
+ * <p>{@code conflictType} is a bitmap telling which part of the service is conflicting. See
+ * {@link MdnsInterfaceAdvertiser#CONFLICT_SERVICE} and {@link
+ * MdnsInterfaceAdvertiser#CONFLICT_HOST}.
*/
- void onServiceConflict(@NonNull MdnsInterfaceAdvertiser advertiser, int serviceId);
+ void onServiceConflict(
+ @NonNull MdnsInterfaceAdvertiser advertiser, int serviceId, int conflictType);
/**
* Called by the advertiser when it destroyed itself.
@@ -336,6 +347,7 @@
final MdnsProber.ProbingInfo probingInfo = mRecordRepository.setServiceProbing(serviceId);
if (probingInfo == null) return false;
+ mAnnouncer.stop(serviceId);
mProber.restartForConflict(probingInfo);
return true;
}
@@ -384,8 +396,16 @@
+ packet.additionalRecords.size() + " additional from " + srcCopy);
}
- for (int conflictServiceId : mRecordRepository.getConflictingServices(packet)) {
- mCbHandler.post(() -> mCb.onServiceConflict(this, conflictServiceId));
+ Map<Integer, Integer> conflictingServices =
+ mRecordRepository.getConflictingServices(packet);
+
+ for (Map.Entry<Integer, Integer> entry : conflictingServices.entrySet()) {
+ int serviceId = entry.getKey();
+ int conflictType = entry.getValue();
+ mCbHandler.post(
+ () -> {
+ mCb.onServiceConflict(this, serviceId, conflictType);
+ });
}
// Even in case of conflict, add replies for other services. But in general conflicts would
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java b/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
index 78c3082..fb45454 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
@@ -19,6 +19,8 @@
import static com.android.server.connectivity.mdns.MdnsConstants.IPV4_SOCKET_ADDR;
import static com.android.server.connectivity.mdns.MdnsConstants.IPV6_SOCKET_ADDR;
import static com.android.server.connectivity.mdns.MdnsConstants.NO_PACKET;
+import static com.android.server.connectivity.mdns.MdnsInterfaceAdvertiser.CONFLICT_HOST;
+import static com.android.server.connectivity.mdns.MdnsInterfaceAdvertiser.CONFLICT_SERVICE;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -28,6 +30,8 @@
import android.os.Build;
import android.os.Looper;
import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.SparseArray;
@@ -54,6 +58,8 @@
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
/**
* A repository of records advertised through {@link MdnsInterfaceAdvertiser}.
@@ -158,11 +164,13 @@
public final List<RecordInfo<?>> allRecords;
@NonNull
public final List<RecordInfo<MdnsPointerRecord>> ptrRecords;
- @NonNull
+ @Nullable
public final RecordInfo<MdnsServiceRecord> srvRecord;
- @NonNull
+ @Nullable
public final RecordInfo<MdnsTextRecord> txtRecord;
@NonNull
+ public final List<RecordInfo<MdnsInetAddressRecord>> addressRecords;
+ @NonNull
public final NsdServiceInfo serviceInfo;
/**
@@ -202,65 +210,96 @@
int repliedServiceCount, int sentPacketCount, boolean exiting, boolean isProbing) {
this.serviceInfo = serviceInfo;
- final String[] serviceType = splitServiceType(serviceInfo);
- final String[] serviceName = splitFullyQualifiedName(serviceInfo, serviceType);
+ final boolean hasService = !TextUtils.isEmpty(serviceInfo.getServiceType());
+ final boolean hasCustomHost = !TextUtils.isEmpty(serviceInfo.getHostname());
+ final String[] hostname =
+ hasCustomHost
+ ? new String[] {serviceInfo.getHostname(), LOCAL_TLD}
+ : deviceHostname;
+ final ArrayList<RecordInfo<?>> allRecords = new ArrayList<>(5);
- // Service PTR records
- ptrRecords = new ArrayList<>(serviceInfo.getSubtypes().size() + 1);
- ptrRecords.add(new RecordInfo<>(
- serviceInfo,
- new MdnsPointerRecord(
- serviceType,
- 0L /* receiptTimeMillis */,
- false /* cacheFlush */,
- NON_NAME_RECORDS_TTL_MILLIS,
- serviceName),
- true /* sharedName */));
- for (String subtype : serviceInfo.getSubtypes()) {
+ if (hasService) {
+ final String[] serviceType = splitServiceType(serviceInfo);
+ final String[] serviceName = splitFullyQualifiedName(serviceInfo, serviceType);
+ // Service PTR records
+ ptrRecords = new ArrayList<>(serviceInfo.getSubtypes().size() + 1);
ptrRecords.add(new RecordInfo<>(
- serviceInfo,
- new MdnsPointerRecord(
- MdnsUtils.constructFullSubtype(serviceType, subtype),
- 0L /* receiptTimeMillis */,
- false /* cacheFlush */,
- NON_NAME_RECORDS_TTL_MILLIS,
- serviceName),
- true /* sharedName */));
+ serviceInfo,
+ new MdnsPointerRecord(
+ serviceType,
+ 0L /* receiptTimeMillis */,
+ false /* cacheFlush */,
+ NON_NAME_RECORDS_TTL_MILLIS,
+ serviceName),
+ true /* sharedName */));
+ for (String subtype : serviceInfo.getSubtypes()) {
+ ptrRecords.add(new RecordInfo<>(
+ serviceInfo,
+ new MdnsPointerRecord(
+ MdnsUtils.constructFullSubtype(serviceType, subtype),
+ 0L /* receiptTimeMillis */,
+ false /* cacheFlush */,
+ NON_NAME_RECORDS_TTL_MILLIS,
+ serviceName),
+ true /* sharedName */));
+ }
+
+ srvRecord = new RecordInfo<>(
+ serviceInfo,
+ new MdnsServiceRecord(serviceName,
+ 0L /* receiptTimeMillis */,
+ true /* cacheFlush */,
+ NAME_RECORDS_TTL_MILLIS,
+ 0 /* servicePriority */, 0 /* serviceWeight */,
+ serviceInfo.getPort(),
+ hostname),
+ false /* sharedName */);
+
+ txtRecord = new RecordInfo<>(
+ serviceInfo,
+ new MdnsTextRecord(serviceName,
+ 0L /* receiptTimeMillis */,
+ // Service name is verified unique after probing
+ true /* cacheFlush */,
+ NON_NAME_RECORDS_TTL_MILLIS,
+ attrsToTextEntries(serviceInfo.getAttributes())),
+ false /* sharedName */);
+
+ allRecords.addAll(ptrRecords);
+ allRecords.add(srvRecord);
+ allRecords.add(txtRecord);
+ // Service type enumeration record (RFC6763 9.)
+ allRecords.add(new RecordInfo<>(
+ serviceInfo,
+ new MdnsPointerRecord(
+ DNS_SD_SERVICE_TYPE,
+ 0L /* receiptTimeMillis */,
+ false /* cacheFlush */,
+ NON_NAME_RECORDS_TTL_MILLIS,
+ serviceType),
+ true /* sharedName */));
+ } else {
+ ptrRecords = Collections.emptyList();
+ srvRecord = null;
+ txtRecord = null;
}
- srvRecord = new RecordInfo<>(
- serviceInfo,
- new MdnsServiceRecord(serviceName,
- 0L /* receiptTimeMillis */,
- true /* cacheFlush */,
- NAME_RECORDS_TTL_MILLIS, 0 /* servicePriority */, 0 /* serviceWeight */,
- serviceInfo.getPort(),
- deviceHostname),
- false /* sharedName */);
-
- txtRecord = new RecordInfo<>(
- serviceInfo,
- new MdnsTextRecord(serviceName,
- 0L /* receiptTimeMillis */,
- true /* cacheFlush */, // Service name is verified unique after probing
- NON_NAME_RECORDS_TTL_MILLIS,
- attrsToTextEntries(serviceInfo.getAttributes())),
- false /* sharedName */);
-
- final ArrayList<RecordInfo<?>> allRecords = new ArrayList<>(5);
- allRecords.addAll(ptrRecords);
- allRecords.add(srvRecord);
- allRecords.add(txtRecord);
- // Service type enumeration record (RFC6763 9.)
- allRecords.add(new RecordInfo<>(
- serviceInfo,
- new MdnsPointerRecord(
- DNS_SD_SERVICE_TYPE,
- 0L /* receiptTimeMillis */,
- false /* cacheFlush */,
- NON_NAME_RECORDS_TTL_MILLIS,
- serviceType),
- true /* sharedName */));
+ if (hasCustomHost) {
+ addressRecords = new ArrayList<>(serviceInfo.getHostAddresses().size());
+ for (InetAddress address : serviceInfo.getHostAddresses()) {
+ addressRecords.add(new RecordInfo<>(
+ serviceInfo,
+ new MdnsInetAddressRecord(hostname,
+ 0L /* receiptTimeMillis */,
+ true /* cacheFlush */,
+ NAME_RECORDS_TTL_MILLIS,
+ address),
+ false /* sharedName */));
+ }
+ allRecords.addAll(addressRecords);
+ } else {
+ addressRecords = Collections.emptyList();
+ }
this.allRecords = Collections.unmodifiableList(allRecords);
this.repliedServiceCount = repliedServiceCount;
@@ -368,32 +407,38 @@
/**
* @return The ID of the service identified by its name, or -1 if none.
*/
- private int getServiceByName(@NonNull String serviceName) {
+ private int getServiceByName(@Nullable String serviceName) {
+ if (TextUtils.isEmpty(serviceName)) {
+ return -1;
+ }
for (int i = 0; i < mServices.size(); i++) {
final ServiceRegistration registration = mServices.valueAt(i);
- if (MdnsUtils.equalsIgnoreDnsCase(serviceName,
- registration.serviceInfo.getServiceName())) {
+ if (MdnsUtils.equalsIgnoreDnsCase(
+ serviceName, registration.serviceInfo.getServiceName())) {
return mServices.keyAt(i);
}
}
return -1;
}
- private MdnsProber.ProbingInfo makeProbingInfo(int serviceId,
- @NonNull MdnsServiceRecord srvRecord,
- @NonNull List<MdnsInetAddressRecord> inetAddressRecords) {
+ private MdnsProber.ProbingInfo makeProbingInfo(
+ int serviceId, ServiceRegistration registration) {
final List<MdnsRecord> probingRecords = new ArrayList<>();
// Probe with cacheFlush cleared; it is set when announcing, as it was verified unique:
// RFC6762 10.2
- probingRecords.add(new MdnsServiceRecord(srvRecord.getName(),
- 0L /* receiptTimeMillis */,
- false /* cacheFlush */,
- srvRecord.getTtl(),
- srvRecord.getServicePriority(), srvRecord.getServiceWeight(),
- srvRecord.getServicePort(),
- srvRecord.getServiceHost()));
+ if (registration.srvRecord != null) {
+ MdnsServiceRecord srvRecord = registration.srvRecord.record;
+ probingRecords.add(new MdnsServiceRecord(srvRecord.getName(),
+ 0L /* receiptTimeMillis */,
+ false /* cacheFlush */,
+ srvRecord.getTtl(),
+ srvRecord.getServicePriority(), srvRecord.getServiceWeight(),
+ srvRecord.getServicePort(),
+ srvRecord.getServiceHost()));
+ }
- for (MdnsInetAddressRecord inetAddressRecord : inetAddressRecords) {
+ for (MdnsInetAddressRecord inetAddressRecord :
+ makeProbingInetAddressRecords(registration.serviceInfo)) {
probingRecords.add(new MdnsInetAddressRecord(inetAddressRecord.getName(),
0L /* receiptTimeMillis */,
false /* cacheFlush */,
@@ -510,6 +555,9 @@
public MdnsReplyInfo getReply(MdnsPacket packet, InetSocketAddress src) {
final long now = SystemClock.elapsedRealtime();
+ // TODO: b/322142420 - Set<RecordInfo<?>> may contain duplicate records wrapped in different
+ // RecordInfo<?>s when custom host is enabled.
+
// Use LinkedHashSet for preserving the insert order of the RRs, so that RRs of the same
// service or host are grouped together (which is more developer-friendly).
final Set<RecordInfo<?>> answerInfo = new LinkedHashSet<>();
@@ -520,8 +568,10 @@
for (MdnsRecord question : packet.questions) {
// Add answers from general records
if (addReplyFromService(question, mGeneralRecords, null /* servicePtrRecord */,
- null /* serviceSrvRecord */, null /* serviceTxtRecord */, replyUnicastEnabled,
- now, answerInfo, additionalAnswerInfo, Collections.emptyList())) {
+ null /* serviceSrvRecord */, null /* serviceTxtRecord */,
+ null /* hostname */,
+ replyUnicastEnabled, now, answerInfo, additionalAnswerInfo,
+ Collections.emptyList())) {
replyUnicast &= question.isUnicastReplyRequested();
}
@@ -530,7 +580,9 @@
final ServiceRegistration registration = mServices.valueAt(i);
if (registration.exiting || registration.isProbing) continue;
if (addReplyFromService(question, registration.allRecords, registration.ptrRecords,
- registration.srvRecord, registration.txtRecord, replyUnicastEnabled, now,
+ registration.srvRecord, registration.txtRecord,
+ registration.serviceInfo.getHostname(),
+ replyUnicastEnabled, now,
answerInfo, additionalAnswerInfo, packet.answers)) {
replyUnicast &= question.isUnicastReplyRequested();
registration.repliedServiceCount++;
@@ -548,7 +600,12 @@
final List<MdnsRecord> additionalAnswerRecords =
new ArrayList<>(additionalAnswerInfo.size());
for (RecordInfo<?> info : additionalAnswerInfo) {
- additionalAnswerRecords.add(info.record);
+ // Different RecordInfos may contain the same record.
+ // For example, when there are multiple services referring to the same custom host,
+ // there are multiple RecordInfos containing the same address record.
+ if (!additionalAnswerRecords.contains(info.record)) {
+ additionalAnswerRecords.add(info.record);
+ }
}
// RFC6762 6.1: negative responses
@@ -618,7 +675,10 @@
if (!replyUnicast) {
info.lastAdvertisedTimeMs = info.lastSentTimeMs;
}
- answerRecords.add(info.record);
+ // Different RecordInfos may the contain the same record
+ if (!answerRecords.contains(info.record)) {
+ answerRecords.add(info.record);
+ }
}
return new MdnsReplyInfo(answerRecords, additionalAnswerRecords, delayMs, dest, src,
@@ -642,6 +702,7 @@
@Nullable List<RecordInfo<MdnsPointerRecord>> servicePtrRecords,
@Nullable RecordInfo<MdnsServiceRecord> serviceSrvRecord,
@Nullable RecordInfo<MdnsTextRecord> serviceTxtRecord,
+ @Nullable String hostname,
boolean replyUnicastEnabled, long now, @NonNull Set<RecordInfo<?>> answerInfo,
@NonNull Set<RecordInfo<?>> additionalAnswerInfo,
@NonNull List<MdnsRecord> knownAnswerRecords) {
@@ -735,11 +796,7 @@
// RFC6763 12.1&.2: if including PTR or SRV record, include the address records it names
if (hasDnsSdPtrRecordAnswer || hasDnsSdSrvRecordAnswer) {
- for (RecordInfo<?> record : mGeneralRecords) {
- if (record.record instanceof MdnsInetAddressRecord) {
- additionalAnswerInfo.add(record);
- }
- }
+ additionalAnswerInfo.addAll(getInetAddressRecordsForHostname(hostname));
}
return true;
}
@@ -853,29 +910,46 @@
MdnsProber.ProbingInfo probeSuccessInfo)
throws IOException {
- final ServiceRegistration registration = mServices.get(probeSuccessInfo.getServiceId());
- if (registration == null) throw new IOException(
- "Service is not registered: " + probeSuccessInfo.getServiceId());
+ int serviceId = probeSuccessInfo.getServiceId();
+ final ServiceRegistration registration = mServices.get(serviceId);
+ if (registration == null) {
+ throw new IOException("Service is not registered: " + serviceId);
+ }
registration.setProbing(false);
- final ArrayList<MdnsRecord> answers = new ArrayList<>();
+ final Set<MdnsRecord> answersSet = new LinkedHashSet<>();
final ArrayList<MdnsRecord> additionalAnswers = new ArrayList<>();
- // Interface address records in general records
- for (RecordInfo<?> record : mGeneralRecords) {
- answers.add(record.record);
+ // When using default host, add interface address records from general records
+ if (TextUtils.isEmpty(registration.serviceInfo.getHostname())) {
+ for (RecordInfo<?> record : mGeneralRecords) {
+ answersSet.add(record.record);
+ }
+ } else {
+ // TODO: b/321617573 - include PTR records for addresses
+ // The custom host may have more addresses in other registrations
+ forEachActiveServiceRegistrationWithHostname(
+ registration.serviceInfo.getHostname(),
+ (id, otherRegistration) -> {
+ if (otherRegistration.isProbing) {
+ return;
+ }
+ for (RecordInfo<?> addressRecordInfo : otherRegistration.addressRecords) {
+ answersSet.add(addressRecordInfo.record);
+ }
+ });
}
// All service records
for (RecordInfo<?> info : registration.allRecords) {
- answers.add(info.record);
+ answersSet.add(info.record);
}
addNsecRecordsForUniqueNames(additionalAnswers,
mGeneralRecords.iterator(), registration.allRecords.iterator());
- return new MdnsAnnouncer.AnnouncementInfo(probeSuccessInfo.getServiceId(),
- answers, additionalAnswers);
+ return new MdnsAnnouncer.AnnouncementInfo(
+ probeSuccessInfo.getServiceId(), new ArrayList<>(answersSet), additionalAnswers);
}
/**
@@ -894,8 +968,13 @@
for (RecordInfo<MdnsPointerRecord> ptrRecord : registration.ptrRecords) {
answers.add(ptrRecord.record);
}
- answers.add(registration.srvRecord.record);
- answers.add(registration.txtRecord.record);
+ if (registration.srvRecord != null) {
+ answers.add(registration.srvRecord.record);
+ }
+ if (registration.txtRecord != null) {
+ answers.add(registration.txtRecord.record);
+ }
+ // TODO: Support custom host. It currently only supports default host.
for (RecordInfo<?> record : mGeneralRecords) {
if (record.record instanceof MdnsInetAddressRecord) {
answers.add(record.record);
@@ -910,70 +989,181 @@
Collections.emptyList() /* additionalRecords */);
}
+ /** Check if the record is in any service registration */
+ private boolean hasInetAddressRecord(@NonNull MdnsInetAddressRecord record) {
+ for (int i = 0; i < mServices.size(); i++) {
+ final ServiceRegistration registration = mServices.valueAt(i);
+ if (registration.exiting) continue;
+
+ for (RecordInfo<MdnsInetAddressRecord> localRecord : registration.addressRecords) {
+ if (Objects.equals(localRecord.record, record)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
/**
* Get the service IDs of services conflicting with a received packet.
+ *
+ * <p>It returns a Map of service ID => conflict type. Conflict type is a bitmap telling which
+ * part of the service is conflicting. See {@link MdnsInterfaceAdvertiser#CONFLICT_SERVICE} and
+ * {@link MdnsInterfaceAdvertiser#CONFLICT_HOST}.
*/
- public Set<Integer> getConflictingServices(MdnsPacket packet) {
+ public Map<Integer, Integer> getConflictingServices(MdnsPacket packet) {
// Avoid allocating a new set for each incoming packet: use an empty set by default.
- Set<Integer> conflicting = Collections.emptySet();
+ Map<Integer, Integer> conflicting = Collections.emptyMap();
for (MdnsRecord record : packet.answers) {
for (int i = 0; i < mServices.size(); i++) {
final ServiceRegistration registration = mServices.valueAt(i);
if (registration.exiting) continue;
- // Only look for conflicts in service name, as a different service name can be used
- // if there is a conflict, but there is nothing actionable if any other conflict
- // happens. In fact probing is only done for the service name in the SRV record.
- // This means only SRV and TXT records need to be checked.
- final RecordInfo<MdnsServiceRecord> srvRecord = registration.srvRecord;
- if (!MdnsUtils.equalsDnsLabelIgnoreDnsCase(record.getName(),
- srvRecord.record.getName())) {
- continue;
+ int conflictType = 0;
+
+ if (conflictForService(record, registration)) {
+ conflictType |= CONFLICT_SERVICE;
}
- // As per RFC6762 9., it's fine if the "conflict" is an identical record with same
- // data.
- if (record instanceof MdnsServiceRecord) {
- final MdnsServiceRecord local = srvRecord.record;
- final MdnsServiceRecord other = (MdnsServiceRecord) record;
- // Note "equals" does not consider TTL or receipt time, as intended here
- if (Objects.equals(local, other)) {
- continue;
+ if (conflictForHost(record, registration)) {
+ conflictType |= CONFLICT_HOST;
+ }
+
+ if (conflictType != 0) {
+ if (conflicting.isEmpty()) {
+ // Conflict was found: use a mutable set
+ conflicting = new ArrayMap<>();
}
+ final int serviceId = mServices.keyAt(i);
+ conflicting.put(serviceId, conflictType);
}
-
- if (record instanceof MdnsTextRecord) {
- final MdnsTextRecord local = registration.txtRecord.record;
- final MdnsTextRecord other = (MdnsTextRecord) record;
- if (Objects.equals(local, other)) {
- continue;
- }
- }
-
- if (conflicting.size() == 0) {
- // Conflict was found: use a mutable set
- conflicting = new ArraySet<>();
- }
- final int serviceId = mServices.keyAt(i);
- conflicting.add(serviceId);
}
}
return conflicting;
}
- private List<MdnsInetAddressRecord> makeProbingInetAddressRecords() {
- final List<MdnsInetAddressRecord> records = new ArrayList<>();
- if (mMdnsFeatureFlags.mIncludeInetAddressRecordsInProbing) {
- for (RecordInfo<?> record : mGeneralRecords) {
- if (record.record instanceof MdnsInetAddressRecord) {
- records.add((MdnsInetAddressRecord) record.record);
- }
+
+ private static boolean conflictForService(
+ @NonNull MdnsRecord record, @NonNull ServiceRegistration registration) {
+ if (registration.srvRecord == null) {
+ return false;
+ }
+
+ final RecordInfo<MdnsServiceRecord> srvRecord = registration.srvRecord;
+ if (!MdnsUtils.equalsDnsLabelIgnoreDnsCase(record.getName(), srvRecord.record.getName())) {
+ return false;
+ }
+
+ // As per RFC6762 9., it's fine if the "conflict" is an identical record with same
+ // data.
+ if (record instanceof MdnsServiceRecord) {
+ final MdnsServiceRecord local = srvRecord.record;
+ final MdnsServiceRecord other = (MdnsServiceRecord) record;
+ // Note "equals" does not consider TTL or receipt time, as intended here
+ if (Objects.equals(local, other)) {
+ return false;
}
}
+
+ if (record instanceof MdnsTextRecord) {
+ final MdnsTextRecord local = registration.txtRecord.record;
+ final MdnsTextRecord other = (MdnsTextRecord) record;
+ if (Objects.equals(local, other)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean conflictForHost(
+ @NonNull MdnsRecord record, @NonNull ServiceRegistration registration) {
+ // Only custom hosts are checked. When using the default host, the hostname is derived from
+ // a UUID and it's supposed to be unique.
+ if (registration.serviceInfo.getHostname() == null) {
+ return false;
+ }
+
+ // The record's name cannot be registered by NsdManager so it's not a conflict.
+ if (record.getName().length != 2 || !record.getName()[1].equals(LOCAL_TLD)) {
+ return false;
+ }
+
+ // Different names. There won't be a conflict.
+ if (!MdnsUtils.equalsIgnoreDnsCase(
+ record.getName()[0], registration.serviceInfo.getHostname())) {
+ return false;
+ }
+
+ // If this registration has any address record and there's no identical record in the
+ // repository, it's a conflict. There will be no conflict if no registration has addresses
+ // for that hostname.
+ if (record instanceof MdnsInetAddressRecord) {
+ if (!registration.addressRecords.isEmpty()) {
+ return !hasInetAddressRecord((MdnsInetAddressRecord) record);
+ }
+ }
+
+ return false;
+ }
+
+ private List<RecordInfo<MdnsInetAddressRecord>> getInetAddressRecordsForHostname(
+ @Nullable String hostname) {
+ List<RecordInfo<MdnsInetAddressRecord>> records = new ArrayList<>();
+ if (TextUtils.isEmpty(hostname)) {
+ forEachAddressRecord(mGeneralRecords, records::add);
+ } else {
+ forEachActiveServiceRegistrationWithHostname(
+ hostname,
+ (id, service) -> {
+ if (service.isProbing) return;
+ records.addAll(service.addressRecords);
+ });
+ }
return records;
}
+ private List<MdnsInetAddressRecord> makeProbingInetAddressRecords(
+ @NonNull NsdServiceInfo serviceInfo) {
+ final List<MdnsInetAddressRecord> records = new ArrayList<>();
+ if (TextUtils.isEmpty(serviceInfo.getHostname())) {
+ if (mMdnsFeatureFlags.mIncludeInetAddressRecordsInProbing) {
+ forEachAddressRecord(mGeneralRecords, r -> records.add(r.record));
+ }
+ } else {
+ forEachActiveServiceRegistrationWithHostname(
+ serviceInfo.getHostname(),
+ (id, service) -> {
+ for (RecordInfo<MdnsInetAddressRecord> recordInfo :
+ service.addressRecords) {
+ records.add(recordInfo.record);
+ }
+ });
+ }
+ return records;
+ }
+
+ private static void forEachAddressRecord(
+ List<RecordInfo<?>> records, Consumer<RecordInfo<MdnsInetAddressRecord>> consumer) {
+ for (RecordInfo<?> record : records) {
+ if (record.record instanceof MdnsInetAddressRecord) {
+ consumer.accept((RecordInfo<MdnsInetAddressRecord>) record);
+ }
+ }
+ }
+
+ private void forEachActiveServiceRegistrationWithHostname(
+ @NonNull String hostname, BiConsumer<Integer, ServiceRegistration> consumer) {
+ for (int i = 0; i < mServices.size(); ++i) {
+ int id = mServices.keyAt(i);
+ ServiceRegistration service = mServices.valueAt(i);
+ if (service.exiting) continue;
+ if (MdnsUtils.equalsIgnoreDnsCase(service.serviceInfo.getHostname(), hostname)) {
+ consumer.accept(id, service);
+ }
+ }
+ }
+
/**
* (Re)set a service to the probing state.
* @return The {@link MdnsProber.ProbingInfo} to send for probing.
@@ -984,8 +1174,8 @@
if (registration == null) return null;
registration.setProbing(true);
- return makeProbingInfo(
- serviceId, registration.srvRecord.record, makeProbingInetAddressRecords());
+
+ return makeProbingInfo(serviceId, registration);
}
/**
@@ -1021,8 +1211,7 @@
final ServiceRegistration newService = new ServiceRegistration(mDeviceHostname, newInfo,
existing.repliedServiceCount, existing.sentPacketCount);
mServices.put(serviceId, newService);
- return makeProbingInfo(
- serviceId, newService.srvRecord.record, makeProbingInetAddressRecords());
+ return makeProbingInfo(serviceId, newService);
}
/**
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsSearchOptions.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSearchOptions.java
index 63835d9..086094b 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsSearchOptions.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSearchOptions.java
@@ -39,6 +39,15 @@
* @hide
*/
public class MdnsSearchOptions implements Parcelable {
+ // Passive query mode scans less frequently in order to conserve battery and produce less
+ // network traffic.
+ public static final int PASSIVE_QUERY_MODE = 0;
+ // Active query mode scans frequently.
+ public static final int ACTIVE_QUERY_MODE = 1;
+ // Aggressive query mode scans more frequently than the active mode at first, and sends both
+ // unicast and multicast queries simultaneously, but in long sessions it eventually sends as
+ // many queries as the PASSIVE mode.
+ public static final int AGGRESSIVE_QUERY_MODE = 2;
/** @hide */
public static final Parcelable.Creator<MdnsSearchOptions> CREATOR =
@@ -47,7 +56,7 @@
public MdnsSearchOptions createFromParcel(Parcel source) {
return new MdnsSearchOptions(
source.createStringArrayList(),
- source.readInt() == 1,
+ source.readInt(),
source.readInt() == 1,
source.readParcelable(null),
source.readString(),
@@ -64,7 +73,7 @@
private final List<String> subtypes;
@Nullable
private final String resolveInstanceName;
- private final boolean isPassiveMode;
+ private final int queryMode;
private final boolean onlyUseIpv6OnIpv6OnlyNetworks;
private final int numOfQueriesBeforeBackoff;
private final boolean removeExpiredService;
@@ -74,7 +83,7 @@
/** Parcelable constructs for a {@link MdnsSearchOptions}. */
MdnsSearchOptions(
List<String> subtypes,
- boolean isPassiveMode,
+ int queryMode,
boolean removeExpiredService,
@Nullable Network network,
@Nullable String resolveInstanceName,
@@ -84,7 +93,7 @@
if (subtypes != null) {
this.subtypes.addAll(subtypes);
}
- this.isPassiveMode = isPassiveMode;
+ this.queryMode = queryMode;
this.onlyUseIpv6OnIpv6OnlyNetworks = onlyUseIpv6OnIpv6OnlyNetworks;
this.numOfQueriesBeforeBackoff = numOfQueriesBeforeBackoff;
this.removeExpiredService = removeExpiredService;
@@ -111,11 +120,10 @@
}
/**
- * @return {@code true} if the passive mode is used. The passive mode scans less frequently in
- * order to conserve battery and produce less network traffic.
+ * @return the current query mode.
*/
- public boolean isPassiveMode() {
- return isPassiveMode;
+ public int getQueryMode() {
+ return queryMode;
}
/**
@@ -166,7 +174,7 @@
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeStringList(subtypes);
- out.writeInt(isPassiveMode ? 1 : 0);
+ out.writeInt(queryMode);
out.writeInt(removeExpiredService ? 1 : 0);
out.writeParcelable(mNetwork, 0);
out.writeString(resolveInstanceName);
@@ -177,7 +185,7 @@
/** A builder to create {@link MdnsSearchOptions}. */
public static final class Builder {
private final Set<String> subtypes;
- private boolean isPassiveMode = true;
+ private int queryMode = PASSIVE_QUERY_MODE;
private boolean onlyUseIpv6OnIpv6OnlyNetworks = false;
private int numOfQueriesBeforeBackoff = 3;
private boolean removeExpiredService;
@@ -212,14 +220,12 @@
}
/**
- * Sets if the passive mode scan should be used. The passive mode scans less frequently in
- * order to conserve battery and produce less network traffic.
+ * Sets which query mode should be used.
*
- * @param isPassiveMode If set to {@code true}, passive mode will be used. If set to {@code
- * false}, active mode will be used.
+ * @param queryMode the query mode should be used.
*/
- public Builder setIsPassiveMode(boolean isPassiveMode) {
- this.isPassiveMode = isPassiveMode;
+ public Builder setQueryMode(int queryMode) {
+ this.queryMode = queryMode;
return this;
}
@@ -276,7 +282,7 @@
public MdnsSearchOptions build() {
return new MdnsSearchOptions(
new ArrayList<>(subtypes),
- isPassiveMode,
+ queryMode,
removeExpiredService,
mNetwork,
resolveInstanceName,
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
index df0a040..4cb88b4 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
@@ -39,6 +39,7 @@
import java.net.Inet6Address;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
@@ -81,7 +82,7 @@
notifyRemovedServiceToListeners(previousResponse, "Service record expired");
}
};
- private final ArrayMap<MdnsServiceBrowserListener, MdnsSearchOptions> listeners =
+ private final ArrayMap<MdnsServiceBrowserListener, ListenerInfo> listeners =
new ArrayMap<>();
private final boolean removeServiceAfterTtlExpires =
MdnsConfigs.removeServiceAfterTtlExpires();
@@ -95,6 +96,32 @@
private long currentSessionId = 0;
private long lastSentTime;
+ private static class ListenerInfo {
+ @NonNull
+ final MdnsSearchOptions searchOptions;
+ final Set<String> discoveredServiceNames;
+
+ ListenerInfo(@NonNull MdnsSearchOptions searchOptions,
+ @Nullable ListenerInfo previousInfo) {
+ this.searchOptions = searchOptions;
+ this.discoveredServiceNames = previousInfo == null
+ ? MdnsUtils.newSet() : previousInfo.discoveredServiceNames;
+ }
+
+ /**
+ * Set the given service name as discovered.
+ *
+ * @return true if the service name was not discovered before.
+ */
+ boolean setServiceDiscovered(@NonNull String serviceName) {
+ return discoveredServiceNames.add(MdnsUtils.toDnsLowerCase(serviceName));
+ }
+
+ void unsetServiceDiscovered(@NonNull String serviceName) {
+ discoveredServiceNames.remove(MdnsUtils.toDnsLowerCase(serviceName));
+ }
+ }
+
private class QueryTaskHandler extends Handler {
QueryTaskHandler(Looper looper) {
super(looper);
@@ -113,6 +140,7 @@
// before sending the query, it needs to be called just before sending it.
final List<MdnsResponse> servicesToResolve = makeResponsesForResolve(socketKey);
final QueryTask queryTask = new QueryTask(taskArgs, servicesToResolve,
+ getAllDiscoverySubtypes(),
servicesToResolve.size() < listeners.size() /* sendDiscoveryQueries */);
executor.submit(queryTask);
break;
@@ -311,12 +339,16 @@
ensureRunningOnHandlerThread(handler);
this.searchOptions = searchOptions;
boolean hadReply = false;
- if (listeners.put(listener, searchOptions) == null) {
+ final ListenerInfo existingInfo = listeners.get(listener);
+ final ListenerInfo listenerInfo = new ListenerInfo(searchOptions, existingInfo);
+ listeners.put(listener, listenerInfo);
+ if (existingInfo == null) {
for (MdnsResponse existingResponse : serviceCache.getCachedServices(cacheKey)) {
if (!responseMatchesOptions(existingResponse, searchOptions)) continue;
final MdnsServiceInfo info =
buildMdnsServiceInfoFromResponse(existingResponse, serviceTypeLabels);
listener.onServiceNameDiscovered(info, true /* isServiceFromCache */);
+ listenerInfo.setServiceDiscovered(info.getServiceInstanceName());
if (existingResponse.isComplete()) {
listener.onServiceFound(info, true /* isServiceFromCache */);
hadReply = true;
@@ -329,8 +361,7 @@
// Keep tracking the ScheduledFuture for the task so we can cancel it if caller is not
// interested anymore.
final QueryTaskConfig taskConfig = new QueryTaskConfig(
- searchOptions.getSubtypes(),
- searchOptions.isPassiveMode(),
+ searchOptions.getQueryMode(),
searchOptions.onlyUseIpv6OnIpv6OnlyNetworks(),
searchOptions.numOfQueriesBeforeBackoff(),
socketKey);
@@ -357,6 +388,7 @@
final QueryTask queryTask = new QueryTask(
mdnsQueryScheduler.scheduleFirstRun(taskConfig, now,
minRemainingTtl, currentSessionId), servicesToResolve,
+ getAllDiscoverySubtypes(),
servicesToResolve.size() < listeners.size() /* sendDiscoveryQueries */);
executor.submit(queryTask);
}
@@ -364,6 +396,15 @@
serviceCache.registerServiceExpiredCallback(cacheKey, serviceExpiredCallback);
}
+ private Set<String> getAllDiscoverySubtypes() {
+ final Set<String> subtypes = MdnsUtils.newSet();
+ for (int i = 0; i < listeners.size(); i++) {
+ final MdnsSearchOptions listenerOptions = listeners.valueAt(i).searchOptions;
+ subtypes.addAll(listenerOptions.getSubtypes());
+ }
+ return subtypes;
+ }
+
/**
* Get the executor service.
*/
@@ -480,9 +521,10 @@
private void notifyRemovedServiceToListeners(@NonNull MdnsResponse response,
@NonNull String message) {
for (int i = 0; i < listeners.size(); i++) {
- if (!responseMatchesOptions(response, listeners.valueAt(i))) continue;
+ if (!responseMatchesOptions(response, listeners.valueAt(i).searchOptions)) continue;
final MdnsServiceBrowserListener listener = listeners.keyAt(i);
if (response.getServiceInstanceName() != null) {
+ listeners.valueAt(i).unsetServiceDiscovered(response.getServiceInstanceName());
final MdnsServiceInfo serviceInfo = buildMdnsServiceInfoFromResponse(
response, serviceTypeLabels);
if (response.isComplete()) {
@@ -511,10 +553,9 @@
final MdnsResponse currentResponse =
serviceCache.getCachedService(serviceInstanceName, cacheKey);
- boolean newServiceFound = false;
+ final boolean newInCache = currentResponse == null;
boolean serviceBecomesComplete = false;
- if (currentResponse == null) {
- newServiceFound = true;
+ if (newInCache) {
if (serviceInstanceName != null) {
serviceCache.addOrUpdateService(cacheKey, response);
}
@@ -525,25 +566,28 @@
serviceBecomesComplete = !before && after;
}
sharedLog.i(String.format(
- "Handling response from service: %s, newServiceFound: %b, serviceBecomesComplete:"
+ "Handling response from service: %s, newInCache: %b, serviceBecomesComplete:"
+ " %b, responseIsComplete: %b",
- serviceInstanceName, newServiceFound, serviceBecomesComplete,
+ serviceInstanceName, newInCache, serviceBecomesComplete,
response.isComplete()));
MdnsServiceInfo serviceInfo =
buildMdnsServiceInfoFromResponse(response, serviceTypeLabels);
for (int i = 0; i < listeners.size(); i++) {
- if (!responseMatchesOptions(response, listeners.valueAt(i))) continue;
+ // If a service stops matching the options (currently can only happen if it loses a
+ // subtype), service lost callbacks should also be sent; this is not done today as
+ // only expiration of SRV records is used, not PTR records used for subtypes, so
+ // services never lose PTR record subtypes.
+ if (!responseMatchesOptions(response, listeners.valueAt(i).searchOptions)) continue;
final MdnsServiceBrowserListener listener = listeners.keyAt(i);
+ final ListenerInfo listenerInfo = listeners.valueAt(i);
+ final boolean newServiceFound = listenerInfo.setServiceDiscovered(serviceInstanceName);
if (newServiceFound) {
sharedLog.log("onServiceNameDiscovered: " + serviceInfo);
listener.onServiceNameDiscovered(serviceInfo, false /* isServiceFromCache */);
}
if (response.isComplete()) {
- // There is a bug here: the newServiceFound is global right now. The state needs
- // to be per listener because of the responseMatchesOptions() filter.
- // Otherwise, it won't handle the subType update properly.
if (newServiceFound || serviceBecomesComplete) {
sharedLog.log("onServiceFound: " + serviceInfo);
listener.onServiceFound(serviceInfo, false /* isServiceFromCache */);
@@ -579,7 +623,7 @@
private List<MdnsResponse> makeResponsesForResolve(@NonNull SocketKey socketKey) {
final List<MdnsResponse> resolveResponses = new ArrayList<>();
for (int i = 0; i < listeners.size(); i++) {
- final String resolveName = listeners.valueAt(i).getResolveInstanceName();
+ final String resolveName = listeners.valueAt(i).searchOptions.getResolveInstanceName();
if (resolveName == null) {
continue;
}
@@ -631,11 +675,15 @@
private class QueryTask implements Runnable {
private final MdnsQueryScheduler.ScheduledQueryTaskArgs taskArgs;
private final List<MdnsResponse> servicesToResolve = new ArrayList<>();
+ private final List<String> subtypes = new ArrayList<>();
private final boolean sendDiscoveryQueries;
QueryTask(@NonNull MdnsQueryScheduler.ScheduledQueryTaskArgs taskArgs,
- @NonNull List<MdnsResponse> servicesToResolve, boolean sendDiscoveryQueries) {
+ @NonNull Collection<MdnsResponse> servicesToResolve,
+ @NonNull Collection<String> subtypes,
+ boolean sendDiscoveryQueries) {
this.taskArgs = taskArgs;
this.servicesToResolve.addAll(servicesToResolve);
+ this.subtypes.addAll(subtypes);
this.sendDiscoveryQueries = sendDiscoveryQueries;
}
@@ -648,7 +696,7 @@
socketClient,
createMdnsPacketWriter(),
serviceType,
- taskArgs.config.subtypes,
+ subtypes,
taskArgs.config.expectUnicastResponse,
taskArgs.config.transactionId,
taskArgs.config.socketKey,
@@ -660,7 +708,7 @@
.call();
} catch (RuntimeException e) {
sharedLog.e(String.format("Failed to run EnqueueMdnsQueryCallable for subtype: %s",
- TextUtils.join(",", taskArgs.config.subtypes)), e);
+ TextUtils.join(",", subtypes)), e);
result = Pair.create(INVALID_TRANSACTION_ID, new ArrayList<>());
}
dependencies.sendMessage(
diff --git a/service-t/src/com/android/server/connectivity/mdns/QueryTaskConfig.java b/service-t/src/com/android/server/connectivity/mdns/QueryTaskConfig.java
index 19282b0..0894166 100644
--- a/service-t/src/com/android/server/connectivity/mdns/QueryTaskConfig.java
+++ b/service-t/src/com/android/server/connectivity/mdns/QueryTaskConfig.java
@@ -16,15 +16,14 @@
package com.android.server.connectivity.mdns;
+import static com.android.server.connectivity.mdns.MdnsSearchOptions.AGGRESSIVE_QUERY_MODE;
+import static com.android.server.connectivity.mdns.MdnsSearchOptions.PASSIVE_QUERY_MODE;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
/**
* A configuration for the PeriodicalQueryTask that contains parameters to build a query packet.
* Call to getConfigForNextRun returns a config that can be used to build the next query task.
@@ -33,19 +32,26 @@
private static final int INITIAL_TIME_BETWEEN_BURSTS_MS =
(int) MdnsConfigs.initialTimeBetweenBurstsMs();
- private static final int TIME_BETWEEN_BURSTS_MS = (int) MdnsConfigs.timeBetweenBurstsMs();
+ private static final int MAX_TIME_BETWEEN_ACTIVE_PASSIVE_BURSTS_MS =
+ (int) MdnsConfigs.timeBetweenBurstsMs();
private static final int QUERIES_PER_BURST = (int) MdnsConfigs.queriesPerBurst();
private static final int TIME_BETWEEN_QUERIES_IN_BURST_MS =
(int) MdnsConfigs.timeBetweenQueriesInBurstMs();
private static final int QUERIES_PER_BURST_PASSIVE_MODE =
(int) MdnsConfigs.queriesPerBurstPassive();
private static final int UNSIGNED_SHORT_MAX_VALUE = 65536;
- // The following fields are used by QueryTask so we need to test them.
@VisibleForTesting
- final List<String> subtypes;
+ // RFC 6762 5.2: The interval between the first two queries MUST be at least one second.
+ static final int INITIAL_AGGRESSIVE_TIME_BETWEEN_BURSTS_MS = 1000;
+ @VisibleForTesting
+ // Basically this tries to send one query per typical DTIM interval 100ms, to maximize the
+ // chances that a query will be received if devices are using a DTIM multiplier (in which case
+ // they only listen once every [multiplier] DTIM intervals).
+ static final int TIME_BETWEEN_RETRANSMISSION_QUERIES_IN_BURST_MS = 100;
+ static final int MAX_TIME_BETWEEN_AGGRESSIVE_BURSTS_MS = 60000;
private final boolean alwaysAskForUnicastResponse =
MdnsConfigs.alwaysAskForUnicastResponseInEachBurst();
- private final boolean usePassiveMode;
+ private final int queryMode;
final boolean onlyUseIpv6OnIpv6OnlyNetworks;
private final int numOfQueriesBeforeBackoff;
@VisibleForTesting
@@ -65,8 +71,7 @@
boolean expectUnicastResponse, boolean isFirstBurst, int burstCounter,
int queriesPerBurst, int timeBetweenBurstsInMs,
long delayUntilNextTaskWithoutBackoffMs) {
- this.subtypes = new ArrayList<>(other.subtypes);
- this.usePassiveMode = other.usePassiveMode;
+ this.queryMode = other.queryMode;
this.onlyUseIpv6OnIpv6OnlyNetworks = other.onlyUseIpv6OnIpv6OnlyNetworks;
this.numOfQueriesBeforeBackoff = other.numOfQueriesBeforeBackoff;
this.transactionId = transactionId;
@@ -79,36 +84,72 @@
this.queryCount = queryCount;
this.socketKey = other.socketKey;
}
- QueryTaskConfig(@NonNull Collection<String> subtypes,
- boolean usePassiveMode,
+
+ QueryTaskConfig(int queryMode,
boolean onlyUseIpv6OnIpv6OnlyNetworks,
int numOfQueriesBeforeBackoff,
@Nullable SocketKey socketKey) {
- this.usePassiveMode = usePassiveMode;
+ this.queryMode = queryMode;
this.onlyUseIpv6OnIpv6OnlyNetworks = onlyUseIpv6OnIpv6OnlyNetworks;
this.numOfQueriesBeforeBackoff = numOfQueriesBeforeBackoff;
- this.subtypes = new ArrayList<>(subtypes);
this.queriesPerBurst = QUERIES_PER_BURST;
this.burstCounter = 0;
this.transactionId = 1;
this.expectUnicastResponse = true;
this.isFirstBurst = true;
// Config the scan frequency based on the scan mode.
- if (this.usePassiveMode) {
+ if (this.queryMode == AGGRESSIVE_QUERY_MODE) {
+ this.timeBetweenBurstsInMs = INITIAL_AGGRESSIVE_TIME_BETWEEN_BURSTS_MS;
+ this.delayUntilNextTaskWithoutBackoffMs =
+ TIME_BETWEEN_RETRANSMISSION_QUERIES_IN_BURST_MS;
+ } else if (this.queryMode == PASSIVE_QUERY_MODE) {
// In passive scan mode, sends a single burst of QUERIES_PER_BURST queries, and then
// in each TIME_BETWEEN_BURSTS interval, sends QUERIES_PER_BURST_PASSIVE_MODE
// queries.
- this.timeBetweenBurstsInMs = TIME_BETWEEN_BURSTS_MS;
+ this.timeBetweenBurstsInMs = MAX_TIME_BETWEEN_ACTIVE_PASSIVE_BURSTS_MS;
+ this.delayUntilNextTaskWithoutBackoffMs = TIME_BETWEEN_QUERIES_IN_BURST_MS;
} else {
// In active scan mode, sends a burst of QUERIES_PER_BURST queries,
// TIME_BETWEEN_QUERIES_IN_BURST_MS apart, then waits for the scan interval, and
// then repeats. The scan interval starts as INITIAL_TIME_BETWEEN_BURSTS_MS and
// doubles until it maxes out at TIME_BETWEEN_BURSTS_MS.
this.timeBetweenBurstsInMs = INITIAL_TIME_BETWEEN_BURSTS_MS;
+ this.delayUntilNextTaskWithoutBackoffMs = TIME_BETWEEN_QUERIES_IN_BURST_MS;
}
this.socketKey = socketKey;
this.queryCount = 0;
- this.delayUntilNextTaskWithoutBackoffMs = TIME_BETWEEN_QUERIES_IN_BURST_MS;
+ }
+
+ long getDelayUntilNextTaskWithoutBackoff(boolean isFirstQueryInBurst,
+ boolean isLastQueryInBurst) {
+ if (isFirstQueryInBurst && queryMode == AGGRESSIVE_QUERY_MODE) {
+ return 0;
+ }
+ if (isLastQueryInBurst) {
+ return timeBetweenBurstsInMs;
+ }
+ return queryMode == AGGRESSIVE_QUERY_MODE
+ ? TIME_BETWEEN_RETRANSMISSION_QUERIES_IN_BURST_MS
+ : TIME_BETWEEN_QUERIES_IN_BURST_MS;
+ }
+
+ boolean getNextExpectUnicastResponse(boolean isLastQueryInBurst) {
+ if (!isLastQueryInBurst) {
+ return false;
+ }
+ if (queryMode == AGGRESSIVE_QUERY_MODE) {
+ return true;
+ }
+ return alwaysAskForUnicastResponse;
+ }
+
+ int getNextTimeBetweenBurstsMs(boolean isLastQueryInBurst) {
+ if (!isLastQueryInBurst) {
+ return timeBetweenBurstsInMs;
+ }
+ final int maxTimeBetweenBursts = queryMode == AGGRESSIVE_QUERY_MODE
+ ? MAX_TIME_BETWEEN_AGGRESSIVE_BURSTS_MS : MAX_TIME_BETWEEN_ACTIVE_PASSIVE_BURSTS_MS;
+ return Math.min(timeBetweenBurstsInMs * 2, maxTimeBetweenBursts);
}
/**
@@ -120,43 +161,26 @@
if (newTransactionId > UNSIGNED_SHORT_MAX_VALUE) {
newTransactionId = 1;
}
- boolean newExpectUnicastResponse = false;
- boolean newIsFirstBurst = isFirstBurst;
+
int newQueriesPerBurst = queriesPerBurst;
int newBurstCounter = burstCounter + 1;
- long newDelayUntilNextTaskWithoutBackoffMs = delayUntilNextTaskWithoutBackoffMs;
- int newTimeBetweenBurstsInMs = timeBetweenBurstsInMs;
- // Only the first query expects uni-cast response.
- if (newBurstCounter == queriesPerBurst) {
+ final boolean isFirstQueryInBurst = newBurstCounter == 1;
+ final boolean isLastQueryInBurst = newBurstCounter == queriesPerBurst;
+ boolean newIsFirstBurst = isFirstBurst && !isLastQueryInBurst;
+ if (isLastQueryInBurst) {
newBurstCounter = 0;
-
- if (alwaysAskForUnicastResponse) {
- newExpectUnicastResponse = true;
- }
// In passive scan mode, sends a single burst of QUERIES_PER_BURST queries, and
// then in each TIME_BETWEEN_BURSTS interval, sends QUERIES_PER_BURST_PASSIVE_MODE
// queries.
- if (isFirstBurst) {
- newIsFirstBurst = false;
- if (usePassiveMode) {
- newQueriesPerBurst = QUERIES_PER_BURST_PASSIVE_MODE;
- }
+ if (isFirstBurst && queryMode == PASSIVE_QUERY_MODE) {
+ newQueriesPerBurst = QUERIES_PER_BURST_PASSIVE_MODE;
}
- // In active scan mode, sends a burst of QUERIES_PER_BURST queries,
- // TIME_BETWEEN_QUERIES_IN_BURST_MS apart, then waits for the scan interval, and
- // then repeats. The scan interval starts as INITIAL_TIME_BETWEEN_BURSTS_MS and
- // doubles until it maxes out at TIME_BETWEEN_BURSTS_MS.
- newDelayUntilNextTaskWithoutBackoffMs = timeBetweenBurstsInMs;
- if (timeBetweenBurstsInMs < TIME_BETWEEN_BURSTS_MS) {
- newTimeBetweenBurstsInMs = Math.min(timeBetweenBurstsInMs * 2,
- TIME_BETWEEN_BURSTS_MS);
- }
- } else {
- newDelayUntilNextTaskWithoutBackoffMs = TIME_BETWEEN_QUERIES_IN_BURST_MS;
}
+
return new QueryTaskConfig(this, newQueryCount, newTransactionId,
- newExpectUnicastResponse, newIsFirstBurst, newBurstCounter, newQueriesPerBurst,
- newTimeBetweenBurstsInMs, newDelayUntilNextTaskWithoutBackoffMs);
+ getNextExpectUnicastResponse(isLastQueryInBurst), newIsFirstBurst, newBurstCounter,
+ newQueriesPerBurst, getNextTimeBetweenBurstsMs(isLastQueryInBurst),
+ getDelayUntilNextTaskWithoutBackoff(isFirstQueryInBurst, isLastQueryInBurst));
}
/**
diff --git a/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java b/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
index 8fc8114..d553210 100644
--- a/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
+++ b/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
@@ -86,7 +86,10 @@
/**
* Compare two strings by DNS case-insensitive lowercase.
*/
- public static boolean equalsIgnoreDnsCase(@NonNull String a, @NonNull String b) {
+ public static boolean equalsIgnoreDnsCase(@Nullable String a, @Nullable String b) {
+ if (a == null || b == null) {
+ return a == null && b == null;
+ }
if (a.length() != b.length()) return false;
for (int i = 0; i < a.length(); i++) {
if (toDnsLowerCase(a.charAt(i)) != toDnsLowerCase(b.charAt(i))) {
diff --git a/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java b/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
index e7af569..b8689d6 100644
--- a/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
+++ b/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
@@ -19,6 +19,7 @@
import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
import static android.net.NetworkCapabilities.TRANSPORT_TEST;
+import android.annotation.CheckResult;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -72,8 +73,9 @@
methodName + " is only available on automotive devices.");
}
- private boolean checkUseRestrictedNetworksPermission() {
- return PermissionUtils.checkAnyPermissionOf(mContext,
+ @CheckResult
+ private boolean hasUseRestrictedNetworksPermission() {
+ return PermissionUtils.hasAnyPermissionOf(mContext,
android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS);
}
@@ -92,7 +94,7 @@
@Override
public String[] getAvailableInterfaces() throws RemoteException {
PermissionUtils.enforceAccessNetworkStatePermission(mContext, TAG);
- return mTracker.getClientModeInterfaces(checkUseRestrictedNetworksPermission());
+ return mTracker.getClientModeInterfaces(hasUseRestrictedNetworksPermission());
}
/**
@@ -146,7 +148,7 @@
public void addListener(IEthernetServiceListener listener) throws RemoteException {
Objects.requireNonNull(listener, "listener must not be null");
PermissionUtils.enforceAccessNetworkStatePermission(mContext, TAG);
- mTracker.addListener(listener, checkUseRestrictedNetworksPermission());
+ mTracker.addListener(listener, hasUseRestrictedNetworksPermission());
}
/**
@@ -187,7 +189,7 @@
@Override
protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
- if (!PermissionUtils.checkDumpPermission(mContext, TAG, pw)) return;
+ if (!PermissionUtils.hasDumpPermission(mContext, TAG, pw)) return;
pw.println("Current Ethernet state: ");
pw.increaseIndent();
diff --git a/service-t/src/com/android/server/net/BpfInterfaceMapHelper.java b/service-t/src/com/android/server/net/BpfInterfaceMapHelper.java
new file mode 100644
index 0000000..3c95b8e
--- /dev/null
+++ b/service-t/src/com/android/server/net/BpfInterfaceMapHelper.java
@@ -0,0 +1,101 @@
+/*
+ * 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.os.Build;
+import android.system.ErrnoException;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.BpfDump;
+import com.android.net.module.util.BpfMap;
+import com.android.net.module.util.IBpfMap;
+import com.android.net.module.util.Struct.S32;
+
+/**
+ * Monitor interface added (without removed) and right interface name and its index to bpf map.
+ */
+@RequiresApi(Build.VERSION_CODES.TIRAMISU)
+public class BpfInterfaceMapHelper {
+ private static final String TAG = BpfInterfaceMapHelper.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<S32, InterfaceMapValue> mIndexToIfaceBpfMap;
+
+ public BpfInterfaceMapHelper() {
+ this(new Dependencies());
+ }
+
+ @VisibleForTesting
+ public BpfInterfaceMapHelper(Dependencies deps) {
+ mIndexToIfaceBpfMap = deps.getInterfaceMap();
+ }
+
+ /**
+ * Dependencies of BpfInerfaceMapUpdater, for injection in tests.
+ */
+ @VisibleForTesting
+ public static class Dependencies {
+ /** Create BpfMap for updating interface and index mapping. */
+ public IBpfMap<S32, InterfaceMapValue> getInterfaceMap() {
+ try {
+ return new BpfMap<>(IFACE_INDEX_NAME_MAP_PATH,
+ S32.class, InterfaceMapValue.class);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Cannot create interface map: " + e);
+ return null;
+ }
+ }
+ }
+
+ /** get interface name by interface index from bpf map */
+ public String getIfNameByIndex(final int index) {
+ try {
+ final InterfaceMapValue value = mIndexToIfaceBpfMap.getValue(new S32(index));
+ if (value == null) {
+ Log.e(TAG, "No if name entry for index " + index);
+ return null;
+ }
+ return value.getInterfaceNameString();
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Failed to get entry for index " + index + ": " + e);
+ return null;
+ }
+ }
+
+ /**
+ * Dump BPF map
+ *
+ * @param pw print writer
+ */
+ public void dump(final IndentingPrintWriter pw) {
+ pw.println("BPF map status:");
+ pw.increaseIndent();
+ BpfDump.dumpMapStatus(mIndexToIfaceBpfMap, pw, "IfaceIndexNameMap",
+ IFACE_INDEX_NAME_MAP_PATH);
+ pw.decreaseIndent();
+ pw.println("BPF map content:");
+ pw.increaseIndent();
+ BpfDump.dumpMap(mIndexToIfaceBpfMap, pw, "IfaceIndexNameMap",
+ (key, value) -> "ifaceIndex=" + key.val
+ + " ifaceName=" + value.getInterfaceNameString());
+ pw.decreaseIndent();
+ }
+}
diff --git a/service-t/src/com/android/server/net/BpfInterfaceMapUpdater.java b/service-t/src/com/android/server/net/BpfInterfaceMapUpdater.java
deleted file mode 100644
index 4ec1562..0000000
--- a/service-t/src/com/android/server/net/BpfInterfaceMapUpdater.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * 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.IndentingPrintWriter;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
-import com.android.net.module.util.BpfDump;
-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.S32;
-
-/**
- * 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<S32, InterfaceMapValue> mIndexToIfaceBpfMap;
- 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;
- mIndexToIfaceBpfMap = 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<S32, InterfaceMapValue> getInterfaceMap() {
- try {
- return new BpfMap<>(IFACE_INDEX_NAME_MAP_PATH,
- S32.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 (mIndexToIfaceBpfMap == 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 {
- mIndexToIfaceBpfMap.updateEntry(new S32(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));
- }
- }
-
- /** get interface name by interface index from bpf map */
- public String getIfNameByIndex(final int index) {
- try {
- final InterfaceMapValue value = mIndexToIfaceBpfMap.getValue(new S32(index));
- if (value == null) {
- Log.e(TAG, "No if name entry for index " + index);
- return null;
- }
- return value.getInterfaceNameString();
- } catch (ErrnoException e) {
- Log.e(TAG, "Failed to get entry for index " + index + ": " + e);
- return null;
- }
- }
-
- /**
- * Dump BPF map
- *
- * @param pw print writer
- */
- public void dump(final IndentingPrintWriter pw) {
- pw.println("BPF map status:");
- pw.increaseIndent();
- BpfDump.dumpMapStatus(mIndexToIfaceBpfMap, pw, "IfaceIndexNameMap",
- IFACE_INDEX_NAME_MAP_PATH);
- pw.decreaseIndent();
- pw.println("BPF map content:");
- pw.increaseIndent();
- BpfDump.dumpMap(mIndexToIfaceBpfMap, pw, "IfaceIndexNameMap",
- (key, value) -> "ifaceIndex=" + key.val
- + " ifaceName=" + value.getInterfaceNameString());
- pw.decreaseIndent();
- }
-}
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index 7b24315..80c4033 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -476,7 +476,7 @@
private final LocationPermissionChecker mLocationPermissionChecker;
@NonNull
- private final BpfInterfaceMapUpdater mInterfaceMapUpdater;
+ private final BpfInterfaceMapHelper mInterfaceMapHelper;
@Nullable
private final SkDestroyListener mSkDestroyListener;
@@ -628,8 +628,7 @@
mContentObserver = mDeps.makeContentObserver(mHandler, mSettings,
mNetworkStatsSubscriptionsMonitor);
mLocationPermissionChecker = mDeps.makeLocationPermissionChecker(mContext);
- mInterfaceMapUpdater = mDeps.makeBpfInterfaceMapUpdater(mContext, mHandler);
- mInterfaceMapUpdater.start();
+ mInterfaceMapHelper = mDeps.makeBpfInterfaceMapHelper();
mUidCounterSetMap = mDeps.getUidCounterSetMap();
mCookieTagMap = mDeps.getCookieTagMap();
mStatsMapA = mDeps.getStatsMapA();
@@ -798,11 +797,10 @@
return new LocationPermissionChecker(context);
}
- /** Create BpfInterfaceMapUpdater to update bpf interface map. */
+ /** Create BpfInterfaceMapHelper to update bpf interface map. */
@NonNull
- public BpfInterfaceMapUpdater makeBpfInterfaceMapUpdater(
- @NonNull Context ctx, @NonNull Handler handler) {
- return new BpfInterfaceMapUpdater(ctx, handler);
+ public BpfInterfaceMapHelper makeBpfInterfaceMapHelper() {
+ return new BpfInterfaceMapHelper();
}
/** Get counter sets map for each UID. */
@@ -1463,7 +1461,7 @@
private int restrictFlagsForCaller(int flags, @Nullable String callingPackage) {
// All non-privileged callers are not allowed to turn off POLL_ON_OPEN.
- final boolean isPrivileged = PermissionUtils.checkAnyPermissionOf(mContext,
+ final boolean isPrivileged = PermissionUtils.hasAnyPermissionOf(mContext,
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
android.Manifest.permission.NETWORK_STACK);
if (!isPrivileged) {
@@ -2669,7 +2667,7 @@
@Override
protected void dump(FileDescriptor fd, PrintWriter rawWriter, String[] args) {
- if (!PermissionUtils.checkDumpPermission(mContext, TAG, rawWriter)) return;
+ if (!PermissionUtils.hasDumpPermission(mContext, TAG, rawWriter)) return;
long duration = DateUtils.DAY_IN_MILLIS;
final HashSet<String> argSet = new HashSet<String>();
@@ -2889,9 +2887,9 @@
}
pw.println();
- pw.println("InterfaceMapUpdater:");
+ pw.println("InterfaceMapHelper:");
pw.increaseIndent();
- mInterfaceMapUpdater.dump(pw);
+ mInterfaceMapHelper.dump(pw);
pw.decreaseIndent();
pw.println();
@@ -3038,7 +3036,7 @@
BpfDump.dumpMap(statsMap, pw, mapName,
"ifaceIndex ifaceName tag_hex uid_int cnt_set rxBytes rxPackets txBytes txPackets",
(key, value) -> {
- final String ifName = mInterfaceMapUpdater.getIfNameByIndex(key.ifaceIndex);
+ final String ifName = mInterfaceMapHelper.getIfNameByIndex(key.ifaceIndex);
return key.ifaceIndex + " "
+ (ifName != null ? ifName : "unknown") + " "
+ "0x" + Long.toHexString(key.tag) + " "
@@ -3056,7 +3054,7 @@
BpfDump.dumpMap(mIfaceStatsMap, pw, "mIfaceStatsMap",
"ifaceIndex ifaceName rxBytes rxPackets txBytes txPackets",
(key, value) -> {
- final String ifName = mInterfaceMapUpdater.getIfNameByIndex(key.val);
+ final String ifName = mInterfaceMapHelper.getIfNameByIndex(key.val);
return key.val + " "
+ (ifName != null ? ifName : "unknown") + " "
+ value.rxBytes + " "
diff --git a/service/Android.bp b/service/Android.bp
index 7c5da0d..403ba7d 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_fwk_core_networking",
// See: http://go/android-license-faq
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -70,9 +71,6 @@
apex_available: [
"com.android.tethering",
],
- lint: {
- baseline_filename: "lint-baseline.xml",
- },
}
// The library name match the service-connectivity jarjar rules that put the JNI utils in the
@@ -181,6 +179,8 @@
"unsupportedappusage",
"ServiceConnectivityResources",
"framework-statsd",
+ "framework-permission",
+ "framework-permission-s",
],
static_libs: [
// Do not add libs here if they are already included
@@ -188,7 +188,7 @@
"androidx.annotation_annotation",
"connectivity-net-module-utils-bpf",
"connectivity_native_aidl_interface-lateststable-java",
- "dnsresolver_aidl_interface-V13-java",
+ "dnsresolver_aidl_interface-V14-java",
"modules-utils-shell-command-handler",
"net-utils-device-common",
"net-utils-device-common-ip",
@@ -204,8 +204,8 @@
"com.android.tethering",
],
lint: {
- strict_updatability_linting: true,
baseline_filename: "lint-baseline.xml",
+
},
visibility: [
"//packages/modules/Connectivity/service-t",
@@ -231,7 +231,7 @@
],
lint: {
strict_updatability_linting: true,
- baseline_filename: "lint-baseline.xml",
+
},
}
@@ -267,6 +267,8 @@
"framework-tethering.impl",
"framework-wifi",
"libprotobuf-java-nano",
+ "framework-permission",
+ "framework-permission-s",
],
jarjar_rules: ":connectivity-jarjar-rules",
apex_available: [
@@ -275,9 +277,6 @@
optimize: {
proguard_flags_files: ["proguard.flags"],
},
- lint: {
- strict_updatability_linting: true,
- },
}
// A special library created strictly for use by the tests as they need the
@@ -290,18 +289,12 @@
java_library {
name: "service-connectivity-for-tests",
defaults: ["service-connectivity-defaults"],
- lint: {
- baseline_filename: "lint-baseline.xml",
- },
}
java_library {
name: "service-connectivity",
defaults: ["service-connectivity-defaults"],
installable: true,
- lint: {
- baseline_filename: "lint-baseline.xml",
- },
}
java_library_static {
@@ -316,9 +309,6 @@
],
static_libs: ["ConnectivityServiceprotos"],
apex_available: ["com.android.tethering"],
- lint: {
- baseline_filename: "lint-baseline.xml",
- },
}
genrule {
diff --git a/service/ServiceConnectivityResources/Android.bp b/service/ServiceConnectivityResources/Android.bp
index 2260596..2621256 100644
--- a/service/ServiceConnectivityResources/Android.bp
+++ b/service/ServiceConnectivityResources/Android.bp
@@ -16,6 +16,7 @@
// APK to hold all the wifi overlayable resources.
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/service/ServiceConnectivityResources/OWNERS b/service/ServiceConnectivityResources/OWNERS
new file mode 100644
index 0000000..df41ff2
--- /dev/null
+++ b/service/ServiceConnectivityResources/OWNERS
@@ -0,0 +1,2 @@
+per-file res/values/config_thread.xml = file:platform/packages/modules/Connectivity:main:/thread/OWNERS
+per-file res/values/overlayable.xml = file:platform/packages/modules/Connectivity:main:/thread/OWNERS
diff --git a/service/ServiceConnectivityResources/res/values/config.xml b/service/ServiceConnectivityResources/res/values/config.xml
index 6f9d46f..2d3647a 100644
--- a/service/ServiceConnectivityResources/res/values/config.xml
+++ b/service/ServiceConnectivityResources/res/values/config.xml
@@ -128,6 +128,13 @@
<string-array translatable="false" name="config_networkNotifySwitches">
</string-array>
+ <!-- An array of priorities of service types for services to be offloaded via
+ NsdManager#registerOffloadEngine.
+ Format is [priority int]:[service type], for example: "0:_testservice._tcp"
+ -->
+ <string-array translatable="false" name="config_nsdOffloadServicesPriority">
+ </string-array>
+
<!-- Whether to use an ongoing notification for signing in to captive portals, instead of a
notification that can be dismissed. -->
<bool name="config_ongoingSignInNotification">false</bool>
diff --git a/service/ServiceConnectivityResources/res/values/overlayable.xml b/service/ServiceConnectivityResources/res/values/overlayable.xml
index 1c07599..f2c4d91 100644
--- a/service/ServiceConnectivityResources/res/values/overlayable.xml
+++ b/service/ServiceConnectivityResources/res/values/overlayable.xml
@@ -31,6 +31,7 @@
<item type="integer" name="config_networkWakeupPacketMask"/>
<item type="integer" name="config_networkNotifySwitchType"/>
<item type="array" name="config_networkNotifySwitches"/>
+ <item type="array" name="config_nsdOffloadServicesPriority"/>
<item type="bool" name="config_ongoingSignInNotification"/>
<item type="bool" name="config_autoCancelNetworkNotifications"/>
<item type="bool" name="config_notifyNoInternetAsDialogWhenHighPriority"/>
diff --git a/service/libconnectivity/Android.bp b/service/libconnectivity/Android.bp
index e063af7..3a72134 100644
--- a/service/libconnectivity/Android.bp
+++ b/service/libconnectivity/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_fwk_core_networking",
// See: http://go/android-license-faq
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/service/lint-baseline.xml b/service/lint-baseline.xml
index 5149e6d..3e11d52 100644
--- a/service/lint-baseline.xml
+++ b/service/lint-baseline.xml
@@ -1,5 +1,82 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-dev" type="baseline" dependencies="true" variant="all" version="8.0.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 33 (current min is 30): `getUidRule`"
+ errorLine1=" return BpfNetMapsReader.getUidRule(sUidOwnerMap, childChain, uid);"
+ errorLine2=" ~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/BpfNetMaps.java"
+ line="643"
+ column="33"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `BpfBitmap`"
+ errorLine1=" return new BpfBitmap(BLOCKED_PORTS_MAP_PATH);"
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/connectivity/ConnectivityNativeService.java"
+ line="61"
+ column="24"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `set`"
+ errorLine1=" mBpfBlockedPortsMap.set(port);"
+ errorLine2=" ~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/connectivity/ConnectivityNativeService.java"
+ line="96"
+ column="33"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `unset`"
+ errorLine1=" mBpfBlockedPortsMap.unset(port);"
+ errorLine2=" ~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/connectivity/ConnectivityNativeService.java"
+ line="107"
+ column="33"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `clear`"
+ errorLine1=" mBpfBlockedPortsMap.clear();"
+ errorLine2=" ~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/connectivity/ConnectivityNativeService.java"
+ line="118"
+ column="33"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `get`"
+ errorLine1=" if (mBpfBlockedPortsMap.get(i)) portMap.add(i);"
+ errorLine2=" ~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/connectivity/ConnectivityNativeService.java"
+ line="131"
+ column="41"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.os.BatteryStatsManager#reportNetworkInterfaceForTransports`"
+ errorLine1=" batteryStats.reportNetworkInterfaceForTransports(iface, transportTypes);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+ line="1447"
+ column="26"/>
+ </issue>
<issue
id="NewApi"
@@ -8,23 +85,562 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~">
<location
file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
- line="1358"
+ line="1458"
column="22"/>
</issue>
<issue
id="NewApi"
+ message="Call requires API level 33 (current min is 30): `getProgramId`"
+ errorLine1=" return BpfUtils.getProgramId(attachType);"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+ line="1572"
+ column="29"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Class requires API level 31 (current min is 30): `android.net.NetworkPolicyManager`"
+ errorLine1=" mPolicyManager = mContext.getSystemService(NetworkPolicyManager.class);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+ line="1740"
+ column="52"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.net.NetworkPolicyManager#registerNetworkPolicyCallback`"
+ errorLine1=" mPolicyManager.registerNetworkPolicyCallback(null, mPolicyCallback);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+ line="1753"
+ column="24"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Cast to `UidFrozenStateChangedCallback` requires API level 34 (current min is 30)"
+ errorLine1=" new UidFrozenStateChangedCallback() {"
+ errorLine2=" ^">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+ line="1888"
+ column="21"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Class requires API level 34 (current min is 30): `android.app.ActivityManager.UidFrozenStateChangedCallback`"
+ errorLine1=" new UidFrozenStateChangedCallback() {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+ line="1888"
+ column="25"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 34 (current min is 30): `android.app.ActivityManager#registerUidFrozenStateChangedCallback`"
+ errorLine1=" activityManager.registerUidFrozenStateChangedCallback("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+ line="1907"
+ column="29"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.net.NetworkPolicyManager#isUidNetworkingBlocked`"
+ errorLine1=" return mPolicyManager.isUidNetworkingBlocked(uid, metered);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+ line="2162"
+ column="35"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.net.NetworkPolicyManager#getRestrictBackgroundStatus`"
+ errorLine1=" return mPolicyManager.getRestrictBackgroundStatus(callerUid);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+ line="2947"
+ column="35"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.net.NetworkStateSnapshot#getNetwork`"
+ errorLine1=" final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(snapshot.getNetwork());"
+ errorLine2=" ~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+ line="2963"
+ column="81"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.net.NetworkStateSnapshot#getLinkProperties`"
+ errorLine1=" snapshot.getLinkProperties(), snapshot.getNetworkCapabilities(),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+ line="2966"
+ column="34"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.net.NetworkStateSnapshot#getNetworkCapabilities`"
+ errorLine1=" snapshot.getLinkProperties(), snapshot.getNetworkCapabilities(),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+ line="2966"
+ column="64"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.net.NetworkStateSnapshot#getNetwork`"
+ errorLine1=" snapshot.getNetwork(), snapshot.getSubscriberId()));"
+ errorLine2=" ~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+ line="2967"
+ column="34"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.net.NetworkStateSnapshot#getSubscriberId`"
+ errorLine1=" snapshot.getNetwork(), snapshot.getSubscriberId()));"
+ errorLine2=" ~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+ line="2967"
+ column="57"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Class requires API level 31 (current min is 30): `android.net.NetworkPolicyManager.NetworkPolicyCallback`"
+ errorLine1=" private final NetworkPolicyCallback mPolicyCallback = new NetworkPolicyCallback() {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+ line="3210"
+ column="63"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 33 (current min is 30): `dump`"
+ errorLine1=" mBpfNetMaps.dump(pw, fd, verbose);"
+ errorLine2=" ~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+ line="4155"
+ column="25"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.os.Build#isDebuggable`"
+ errorLine1=" if (!Build.isDebuggable()) {"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+ line="5721"
+ column="20"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Class requires API level 31 (current min is 30): `android.net.NetworkPolicyManager`"
+ errorLine1=" mContext.getSystemService(NetworkPolicyManager.class);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+ line="6174"
+ column="44"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.net.NetworkPolicyManager#getMultipathPreference`"
+ errorLine1=" networkPreference = netPolicyManager.getMultipathPreference(network);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+ line="6179"
+ column="50"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `new android.net.UnderlyingNetworkInfo`"
+ errorLine1=" return new UnderlyingNetworkInfo(nai.networkCapabilities.getOwnerUid(),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+ line="6819"
+ column="16"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.net.NetworkPolicyManager#isUidRestrictedOnMeteredNetworks`"
+ errorLine1=" if (mPolicyManager.isUidRestrictedOnMeteredNetworks(uid)) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+ line="7822"
+ column="32"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.os.Build#isDebuggable`"
+ errorLine1=" if (Build.isDebuggable()) {"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+ line="9943"
+ column="23"/>
+ </issue>
+
+ <issue
+ id="NewApi"
message="Call requires API level 31 (current min is 30): `android.app.usage.NetworkStatsManager#notifyNetworkStatus`"
errorLine1=" mStatsManager.notifyNetworkStatus(getDefaultNetworks(),"
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
- line="9938"
+ line="10909"
column="27"/>
</issue>
<issue
id="NewApi"
+ message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
+ errorLine1=" IoUtils.closeQuietly(pfd);"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+ line="10962"
+ column="25"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
+ errorLine1=" IoUtils.closeQuietly(pfd);"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+ line="10979"
+ column="25"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Class requires API level 31 (current min is 30): `android.net.NetworkWatchlistManager`"
+ errorLine1=" NetworkWatchlistManager nwm = mContext.getSystemService(NetworkWatchlistManager.class);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+ line="11035"
+ column="65"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.net.NetworkWatchlistManager#getWatchlistConfigHash`"
+ errorLine1=" return nwm.getWatchlistConfigHash();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+ line="11041"
+ column="20"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 33 (current min is 30): `getProgramId`"
+ errorLine1=" final int ret = BpfUtils.getProgramId(type);"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+ line="11180"
+ column="50"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.os.BatteryStatsManager#reportMobileRadioPowerState`"
+ errorLine1=" bs.reportMobileRadioPowerState(isActive, uid);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+ line="12254"
+ column="24"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.os.BatteryStatsManager#reportWifiRadioPowerState`"
+ errorLine1=" bs.reportWifiRadioPowerState(isActive, uid);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+ line="12257"
+ column="24"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 33 (current min is 30): `addNiceApp`"
+ errorLine1=" mBpfNetMaps.addNiceApp(uid);"
+ errorLine2=" ~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+ line="13079"
+ column="29"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 33 (current min is 30): `removeNiceApp`"
+ errorLine1=" mBpfNetMaps.removeNiceApp(uid);"
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+ line="13081"
+ column="29"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 33 (current min is 30): `addNaughtyApp`"
+ errorLine1=" mBpfNetMaps.addNaughtyApp(uid);"
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+ line="13094"
+ column="29"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 33 (current min is 30): `removeNaughtyApp`"
+ errorLine1=" mBpfNetMaps.removeNaughtyApp(uid);"
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+ line="13096"
+ column="29"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.os.UserHandle#getUid`"
+ errorLine1=" final int uid = uh.getUid(appId);"
+ errorLine2=" ~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+ line="13112"
+ column="32"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 33 (current min is 30): `setUidRule`"
+ errorLine1=" mBpfNetMaps.setUidRule(chain, uid, firewallRule);"
+ errorLine2=" ~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+ line="13130"
+ column="25"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 33 (current min is 30): `setChildChain`"
+ errorLine1=" mBpfNetMaps.setChildChain(chain, enable);"
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+ line="13195"
+ column="25"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 33 (current min is 30): `isChainEnabled`"
+ errorLine1=" return mBpfNetMaps.isChainEnabled(chain);"
+ errorLine2=" ~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+ line="13213"
+ column="28"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 33 (current min is 30): `replaceUidChain`"
+ errorLine1=" mBpfNetMaps.replaceUidChain(chain, uids);"
+ errorLine2=" ~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
+ line="13220"
+ column="21"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `BpfMap`"
+ errorLine1=" mBpfDscpIpv4Policies = new BpfMap<Struct.S32, DscpPolicyValue>(IPV4_POLICY_MAP_PATH,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/connectivity/DscpPolicyTracker.java"
+ line="88"
+ column="32"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `BpfMap`"
+ errorLine1=" mBpfDscpIpv6Policies = new BpfMap<Struct.S32, DscpPolicyValue>(IPV6_POLICY_MAP_PATH,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/connectivity/DscpPolicyTracker.java"
+ line="90"
+ column="32"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `insertOrReplaceEntry`"
+ errorLine1=" mBpfDscpIpv4Policies.insertOrReplaceEntry("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/connectivity/DscpPolicyTracker.java"
+ line="183"
+ column="38"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `insertOrReplaceEntry`"
+ errorLine1=" mBpfDscpIpv6Policies.insertOrReplaceEntry("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/connectivity/DscpPolicyTracker.java"
+ line="194"
+ column="38"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `replaceEntry`"
+ errorLine1=" mBpfDscpIpv4Policies.replaceEntry(new Struct.S32(index), DscpPolicyValue.NONE);"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/connectivity/DscpPolicyTracker.java"
+ line="261"
+ column="34"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `replaceEntry`"
+ errorLine1=" mBpfDscpIpv6Policies.replaceEntry(new Struct.S32(index), DscpPolicyValue.NONE);"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/connectivity/DscpPolicyTracker.java"
+ line="262"
+ column="34"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `java.net.InetAddress#parseNumericAddress`"
+ errorLine1=' InetAddress.parseNumericAddress("::").getAddress();'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/connectivity/DscpPolicyValue.java"
+ line="99"
+ column="25"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `new android.net.NetworkStateSnapshot`"
+ errorLine1=" return new NetworkStateSnapshot(network, new NetworkCapabilities(networkCapabilities),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/connectivity/NetworkAgentInfo.java"
+ line="1353"
+ column="20"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
+ errorLine1=" IoUtils.closeQuietly(mFileDescriptor);"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/connectivity/NetworkDiagnostics.java"
+ line="570"
+ column="21"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Field requires API level 31 (current min is 30): `android.os.Build.VERSION#DEVICE_INITIAL_SDK_INT`"
+ errorLine1=" return Build.VERSION.DEVICE_INITIAL_SDK_INT;"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/connectivity/PermissionMonitor.java"
+ line="212"
+ column="20"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.os.SystemConfigManager#getSystemPermissionUids`"
+ errorLine1=" for (final int uid : mSystemConfigManager.getSystemPermissionUids(INTERNET)) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/connectivity/PermissionMonitor.java"
+ line="396"
+ column="51"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.os.SystemConfigManager#getSystemPermissionUids`"
+ errorLine1=" for (final int uid : mSystemConfigManager.getSystemPermissionUids(UPDATE_DEVICE_STATS)) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/connectivity/PermissionMonitor.java"
+ line="404"
+ column="51"/>
+ </issue>
+
+ <issue
+ id="NewApi"
message="Call requires API level 31 (current min is 30): `android.content.pm.ApplicationInfo#isOem`"
errorLine1=" return appInfo.isVendor() || appInfo.isOem() || appInfo.isProduct();"
errorLine2=" ~~~~~">
@@ -58,441 +674,34 @@
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.net.NetworkPolicyManager#getMultipathPreference`"
- errorLine1=" networkPreference = netPolicyManager.getMultipathPreference(network);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
- line="5498"
- column="50"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.net.NetworkPolicyManager#getRestrictBackgroundStatus`"
- errorLine1=" return mPolicyManager.getRestrictBackgroundStatus(callerUid);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
- line="2565"
- column="35"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.net.NetworkPolicyManager#isUidNetworkingBlocked`"
- errorLine1=" return mPolicyManager.isUidNetworkingBlocked(uid, metered);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
- line="1914"
- column="35"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.net.NetworkPolicyManager#isUidRestrictedOnMeteredNetworks`"
- errorLine1=" if (mPolicyManager.isUidRestrictedOnMeteredNetworks(uid)) {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
- line="7094"
- column="32"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.net.NetworkPolicyManager#registerNetworkPolicyCallback`"
- errorLine1=" mPolicyManager.registerNetworkPolicyCallback(null, mPolicyCallback);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
- line="1567"
- column="24"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.net.NetworkStateSnapshot#getLinkProperties`"
- errorLine1=" snapshot.getLinkProperties(), snapshot.getNetworkCapabilities(),"
- errorLine2=" ~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
- line="2584"
- column="34"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.net.NetworkStateSnapshot#getNetworkCapabilities`"
- errorLine1=" snapshot.getLinkProperties(), snapshot.getNetworkCapabilities(),"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
- line="2584"
- column="64"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.net.NetworkStateSnapshot#getNetwork`"
- errorLine1=" snapshot.getNetwork(), snapshot.getSubscriberId()));"
- errorLine2=" ~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
- line="2585"
- column="34"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.net.NetworkStateSnapshot#getNetwork`"
- errorLine1=" final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(snapshot.getNetwork());"
- errorLine2=" ~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
- line="2581"
- column="81"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.net.NetworkStateSnapshot#getSubscriberId`"
- errorLine1=" snapshot.getNetwork(), snapshot.getSubscriberId()));"
- errorLine2=" ~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
- line="2585"
- column="57"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.net.NetworkWatchlistManager#getWatchlistConfigHash`"
- errorLine1=" return nwm.getWatchlistConfigHash();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
- line="10060"
- column="20"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.net.PacProxyManager#addPacProxyInstalledListener`"
- errorLine1=" mPacProxyManager.addPacProxyInstalledListener("
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/connectivity/ProxyTracker.java"
- line="111"
- column="26"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.net.PacProxyManager#setCurrentProxyScriptUrl`"
- errorLine1=" () -> mPacProxyManager.setCurrentProxyScriptUrl(proxyProperties));"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/connectivity/ProxyTracker.java"
- line="208"
- column="48"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.net.PacProxyManager#setCurrentProxyScriptUrl`"
- errorLine1=" mPacProxyManager.setCurrentProxyScriptUrl(proxyInfo);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/connectivity/ProxyTracker.java"
- line="252"
- column="26"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.os.BatteryStatsManager#reportMobileRadioPowerState`"
- errorLine1=" bs.reportMobileRadioPowerState(isActive, NO_UID);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
- line="11006"
- column="24"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.os.BatteryStatsManager#reportNetworkInterfaceForTransports`"
- errorLine1=" batteryStats.reportNetworkInterfaceForTransports(iface, transportTypes);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
- line="1347"
- column="26"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.os.BatteryStatsManager#reportWifiRadioPowerState`"
- errorLine1=" bs.reportWifiRadioPowerState(isActive, NO_UID);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
- line="11009"
- column="24"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.os.Build#isDebuggable`"
- errorLine1=" if (Build.isDebuggable()) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
- line="9074"
- column="23"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.os.Build#isDebuggable`"
- errorLine1=" if (!Build.isDebuggable()) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
- line="5039"
- column="20"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.os.SystemConfigManager#getSystemPermissionUids`"
- errorLine1=" for (final int uid : mSystemConfigManager.getSystemPermissionUids(INTERNET)) {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/connectivity/PermissionMonitor.java"
- line="396"
- column="51"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.os.SystemConfigManager#getSystemPermissionUids`"
- errorLine1=" for (final int uid : mSystemConfigManager.getSystemPermissionUids(UPDATE_DEVICE_STATS)) {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/connectivity/PermissionMonitor.java"
- line="404"
- column="51"/>
- </issue>
-
- <issue
- id="NewApi"
message="Call requires API level 31 (current min is 30): `android.os.UserHandle#getUid`"
errorLine1=" final int uid = handle.getUid(appId);"
errorLine2=" ~~~~~~">
<location
file="packages/modules/Connectivity/service/src/com/android/server/connectivity/PermissionMonitor.java"
- line="1069"
+ line="1070"
column="44"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.system.Os#getsockoptInt`"
- errorLine1=" tcpDetails.tos = Os.getsockoptInt(fd, IPPROTO_IP, IP_TOS);"
- errorLine2=" ~~~~~~~~~~~~~">
+ message="Call requires API level 33 (current min is 30): `updateUidLockdownRule`"
+ errorLine1=" mBpfNetMaps.updateUidLockdownRule(uid, add);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
<location
- file="packages/modules/Connectivity/service/src/com/android/server/connectivity/TcpKeepaliveController.java"
- line="285"
- column="37"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.system.Os#getsockoptInt`"
- errorLine1=" tcpDetails.ttl = Os.getsockoptInt(fd, IPPROTO_IP, IP_TTL);"
- errorLine2=" ~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/connectivity/TcpKeepaliveController.java"
- line="287"
- column="37"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.system.Os#getsockoptInt`"
- errorLine1=" tcpDetails.ack = Os.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);"
- errorLine2=" ~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/connectivity/TcpKeepaliveController.java"
- line="265"
- column="33"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.system.Os#getsockoptInt`"
- errorLine1=" tcpDetails.seq = Os.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);"
- errorLine2=" ~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/connectivity/TcpKeepaliveController.java"
- line="262"
- column="33"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.system.Os#ioctlInt`"
- errorLine1=" final int result = Os.ioctlInt(fd, SIOCINQ);"
- errorLine2=" ~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/connectivity/TcpKeepaliveController.java"
- line="392"
- column="31"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.system.Os#ioctlInt`"
- errorLine1=" final int result = Os.ioctlInt(fd, SIOCOUTQ);"
- errorLine2=" ~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/connectivity/TcpKeepaliveController.java"
- line="402"
- column="31"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `java.net.InetAddress#parseNumericAddress`"
- errorLine1=' InetAddress.parseNumericAddress("::").getAddress();'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/connectivity/DscpPolicyValue.java"
- line="99"
+ file="packages/modules/Connectivity/service/src/com/android/server/connectivity/PermissionMonitor.java"
+ line="1123"
column="25"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `java.net.InetAddress#parseNumericAddress`"
- errorLine1=' private static final InetAddress GOOGLE_DNS_4 = InetAddress.parseNumericAddress("8.8.8.8");'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/connectivity/ClatCoordinator.java"
- line="89"
- column="65"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
- errorLine1=" IoUtils.closeQuietly(pfd);"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
- line="9991"
- column="25"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
- errorLine1=" IoUtils.closeQuietly(pfd);"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
- line="10008"
- column="25"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `libcore.io.IoUtils#closeQuietly`"
- errorLine1=" IoUtils.closeQuietly(mFileDescriptor);"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/connectivity/NetworkDiagnostics.java"
- line="481"
- column="21"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `new android.net.NetworkStateSnapshot`"
- errorLine1=" return new NetworkStateSnapshot(network, new NetworkCapabilities(networkCapabilities),"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/connectivity/NetworkAgentInfo.java"
- line="1269"
- column="20"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `new android.net.UnderlyingNetworkInfo`"
- errorLine1=" return new UnderlyingNetworkInfo(nai.networkCapabilities.getOwnerUid(),"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
- line="6123"
- column="16"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Class requires API level 31 (current min is 30): `android.net.NetworkPolicyManager.NetworkPolicyCallback`"
- errorLine1=" private final NetworkPolicyCallback mPolicyCallback = new NetworkPolicyCallback() {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
- line="2827"
- column="63"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Class requires API level 31 (current min is 30): `android.net.NetworkPolicyManager`"
- errorLine1=" mContext.getSystemService(NetworkPolicyManager.class);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
- line="5493"
- column="44"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Class requires API level 31 (current min is 30): `android.net.NetworkPolicyManager`"
- errorLine1=" mPolicyManager = mContext.getSystemService(NetworkPolicyManager.class);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
- line="1554"
- column="52"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Class requires API level 31 (current min is 30): `android.net.NetworkWatchlistManager`"
- errorLine1=" NetworkWatchlistManager nwm = mContext.getSystemService(NetworkWatchlistManager.class);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java"
- line="10054"
- column="65"/>
- </issue>
-
- <issue
- id="NewApi"
message="Class requires API level 31 (current min is 30): `android.net.PacProxyManager.PacProxyInstalledListener`"
errorLine1=" private class PacProxyInstalledListener implements PacProxyManager.PacProxyInstalledListener {"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/modules/Connectivity/service/src/com/android/server/connectivity/ProxyTracker.java"
- line="90"
+ line="92"
column="56"/>
</issue>
@@ -503,8 +712,107 @@
errorLine2=" ~~~~~~~~~~~~~~~">
<location
file="packages/modules/Connectivity/service/src/com/android/server/connectivity/ProxyTracker.java"
- line="108"
+ line="111"
column="53"/>
</issue>
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.net.PacProxyManager#addPacProxyInstalledListener`"
+ errorLine1=" mPacProxyManager.addPacProxyInstalledListener("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/connectivity/ProxyTracker.java"
+ line="115"
+ column="30"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.net.PacProxyManager#setCurrentProxyScriptUrl`"
+ errorLine1=" () -> mPacProxyManager.setCurrentProxyScriptUrl(proxyProperties));"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/connectivity/ProxyTracker.java"
+ line="213"
+ column="48"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.net.PacProxyManager#setCurrentProxyScriptUrl`"
+ errorLine1=" mPacProxyManager.setCurrentProxyScriptUrl(proxyInfo);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/connectivity/ProxyTracker.java"
+ line="259"
+ column="30"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.system.Os#getsockoptInt`"
+ errorLine1=" tcpDetails.seq = Os.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);"
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/connectivity/TcpKeepaliveController.java"
+ line="269"
+ column="33"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.system.Os#getsockoptInt`"
+ errorLine1=" tcpDetails.ack = Os.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);"
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/connectivity/TcpKeepaliveController.java"
+ line="272"
+ column="33"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.system.Os#getsockoptInt`"
+ errorLine1=" tcpDetails.tos = Os.getsockoptInt(fd, IPPROTO_IP, IP_TOS);"
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/connectivity/TcpKeepaliveController.java"
+ line="292"
+ column="37"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.system.Os#getsockoptInt`"
+ errorLine1=" tcpDetails.ttl = Os.getsockoptInt(fd, IPPROTO_IP, IP_TTL);"
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/connectivity/TcpKeepaliveController.java"
+ line="294"
+ column="37"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.system.Os#ioctlInt`"
+ errorLine1=" final int result = Os.ioctlInt(fd, SIOCINQ);"
+ errorLine2=" ~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/connectivity/TcpKeepaliveController.java"
+ line="401"
+ column="31"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.system.Os#ioctlInt`"
+ errorLine1=" final int result = Os.ioctlInt(fd, SIOCOUTQ);"
+ errorLine2=" ~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/service/src/com/android/server/connectivity/TcpKeepaliveController.java"
+ line="411"
+ column="31"/>
+ </issue>
+
</issues>
\ No newline at end of file
diff --git a/service/native/libs/libclat/Android.bp b/service/native/libs/libclat/Android.bp
index 5c6b123..6c1c2c4 100644
--- a/service/native/libs/libclat/Android.bp
+++ b/service/native/libs/libclat/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -38,7 +39,10 @@
cc_test {
name: "libclat_test",
defaults: ["netd_defaults"],
- test_suites: ["general-tests", "mts-tethering"],
+ test_suites: [
+ "general-tests",
+ "mts-tethering",
+ ],
test_config_template: ":net_native_test_config_template",
srcs: [
"clatutils_test.cpp",
diff --git a/service/proguard.flags b/service/proguard.flags
index cf25f05..ed9a65f 100644
--- a/service/proguard.flags
+++ b/service/proguard.flags
@@ -15,3 +15,7 @@
static final % EVENT_*;
}
+# b/313539492 Keep the onLocalNetworkInfoChanged method in classes extending Connectivity.NetworkCallback.
+-keepclassmembers class * extends **android.net.ConnectivityManager$NetworkCallback {
+ public void onLocalNetworkInfoChanged(**android.net.Network, **android.net.LocalNetworkInfo);
+}
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index aa40285..a7fddd0 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -31,7 +31,6 @@
import static android.net.BpfNetMapsConstants.UID_OWNER_MAP_PATH;
import static android.net.BpfNetMapsConstants.UID_PERMISSION_MAP_PATH;
import static android.net.BpfNetMapsConstants.UID_RULES_CONFIGURATION_KEY;
-import static android.net.BpfNetMapsUtils.PRE_T;
import static android.net.BpfNetMapsUtils.getMatchByFirewallChain;
import static android.net.BpfNetMapsUtils.isFirewallAllowList;
import static android.net.BpfNetMapsUtils.matchToString;
@@ -68,6 +67,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.utils.BackgroundThread;
+import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.BpfDump;
import com.android.net.module.util.BpfMap;
import com.android.net.module.util.IBpfMap;
@@ -95,7 +95,7 @@
*/
public class BpfNetMaps {
static {
- if (!PRE_T) {
+ if (SdkLevel.isAtLeastT()) {
System.loadLibrary("service-connectivity");
}
}
@@ -184,6 +184,7 @@
sIngressDiscardMap = ingressDiscardMap;
}
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
private static IBpfMap<S32, U32> getConfigurationMap() {
try {
return new BpfMap<>(
@@ -193,6 +194,7 @@
}
}
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
private static IBpfMap<S32, UidOwnerValue> getUidOwnerMap() {
try {
return new BpfMap<>(
@@ -202,6 +204,7 @@
}
}
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
private static IBpfMap<S32, U8> getUidPermissionMap() {
try {
return new BpfMap<>(
@@ -211,6 +214,7 @@
}
}
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
private static IBpfMap<CookieTagMapKey, CookieTagMapValue> getCookieTagMap() {
try {
return new BpfMap<>(COOKIE_TAG_MAP_PATH,
@@ -220,6 +224,7 @@
}
}
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
private static IBpfMap<S32, U8> getDataSaverEnabledMap() {
try {
return new BpfMap<>(
@@ -229,6 +234,7 @@
}
}
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
private static IBpfMap<IngressDiscardKey, IngressDiscardValue> getIngressDiscardMap() {
try {
return new BpfMap<>(INGRESS_DISCARD_MAP_PATH,
@@ -238,6 +244,7 @@
}
}
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
private static void initBpfMaps() {
if (sConfigurationMap == null) {
sConfigurationMap = getConfigurationMap();
@@ -295,6 +302,7 @@
* Initializes the class if it is not already initialized. This method will open maps but not
* cause any other effects. This method may be called multiple times on any thread.
*/
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
private static synchronized void ensureInitialized(final Context context) {
if (sInitialized) return;
initBpfMaps();
@@ -348,7 +356,7 @@
public BpfNetMaps(final Context context) {
this(context, null);
- if (PRE_T) throw new IllegalArgumentException("BpfNetMaps need to use netd before T");
+ if (!SdkLevel.isAtLeastT()) throw new IllegalArgumentException("BpfNetMaps need to use netd before T");
}
public BpfNetMaps(final Context context, final INetd netd) {
@@ -357,7 +365,7 @@
@VisibleForTesting
public BpfNetMaps(final Context context, final INetd netd, final Dependencies deps) {
- if (!PRE_T) {
+ if (SdkLevel.isAtLeastT()) {
ensureInitialized(context);
}
mNetd = netd;
@@ -371,7 +379,7 @@
}
private void throwIfPreT(final String msg) {
- if (PRE_T) {
+ if (!SdkLevel.isAtLeastT()) {
throw new UnsupportedOperationException(msg);
}
}
@@ -712,7 +720,7 @@
* cause of the failure.
*/
public void addUidInterfaceRules(final String ifName, final int[] uids) throws RemoteException {
- if (PRE_T) {
+ if (!SdkLevel.isAtLeastT()) {
mNetd.firewallAddUidInterfaceRules(ifName, uids);
return;
}
@@ -750,7 +758,7 @@
* cause of the failure.
*/
public void removeUidInterfaceRules(final int[] uids) throws RemoteException {
- if (PRE_T) {
+ if (!SdkLevel.isAtLeastT()) {
mNetd.firewallRemoveUidInterfaceRules(uids);
return;
}
@@ -829,7 +837,7 @@
* @throws RemoteException when netd has crashed.
*/
public void setNetPermForUids(final int permissions, final int[] uids) throws RemoteException {
- if (PRE_T) {
+ if (!SdkLevel.isAtLeastT()) {
mNetd.trafficSetNetPermForUids(permissions, uids);
return;
}
@@ -1019,7 +1027,7 @@
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
public void dump(final IndentingPrintWriter pw, final FileDescriptor fd, boolean verbose)
throws IOException, ServiceSpecificException {
- if (PRE_T) {
+ if (!SdkLevel.isAtLeastT()) {
throw new ServiceSpecificException(
EOPNOTSUPP, "dumpsys connectivity trafficcontroller dump not available on pre-T"
+ " devices, use dumpsys netd trafficcontroller instead.");
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index a995439..e6287bc 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -38,6 +38,7 @@
import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
import static android.net.ConnectivityManager.CALLBACK_IP_CHANGED;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
import static android.net.ConnectivityManager.FIREWALL_RULE_DEFAULT;
import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
@@ -108,15 +109,16 @@
import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET_INGRESS;
import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET_SOCK_CREATE;
import static com.android.net.module.util.NetworkMonitorUtils.isPrivateDnsValidationRequired;
-import static com.android.net.module.util.PermissionUtils.checkAnyPermissionOf;
import static com.android.net.module.util.PermissionUtils.enforceAnyPermissionOf;
import static com.android.net.module.util.PermissionUtils.enforceNetworkStackPermission;
import static com.android.net.module.util.PermissionUtils.enforceNetworkStackPermissionOr;
+import static com.android.net.module.util.PermissionUtils.hasAnyPermissionOf;
import static com.android.server.ConnectivityStatsLog.CONNECTIVITY_STATE_SAMPLE;
-
-import static java.util.Map.Entry;
+import static com.android.server.connectivity.CarrierPrivilegeAuthenticator.CarrierPrivilegesLostListener;
+import static com.android.server.connectivity.ConnectivityFlags.REQUEST_RESTRICTED_WIFI;
import android.Manifest;
+import android.annotation.CheckResult;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
@@ -337,6 +339,7 @@
import com.android.server.connectivity.ProxyTracker;
import com.android.server.connectivity.QosCallbackTracker;
import com.android.server.connectivity.RoutingCoordinatorService;
+import com.android.server.connectivity.SatelliteAccessController;
import com.android.server.connectivity.UidRangeUtils;
import com.android.server.connectivity.VpnNetworkPreferenceInfo;
import com.android.server.connectivity.wear.CompanionDeviceManagerProxyService;
@@ -351,7 +354,6 @@
import java.io.PrintWriter;
import java.io.Writer;
import java.net.Inet4Address;
-import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketException;
@@ -375,6 +377,7 @@
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
/**
* @hide
@@ -469,6 +472,8 @@
private volatile boolean mLockdownEnabled;
+ private final boolean mRequestRestrictedWifiEnabled;
+
/**
* Stale copy of uid blocked reasons provided by NPMS. As long as they are accessed only in
* internal handler thread, they don't need a lock.
@@ -564,6 +569,10 @@
// See {@link ConnectivitySettingsManager#setMobileDataPreferredUids}
@VisibleForTesting
static final int PREFERENCE_ORDER_MOBILE_DATA_PREFERERRED = 30;
+ // Order of setting satellite network preference fallback when default message application
+ // with role_sms role and android.permission.SATELLITE_COMMUNICATION permission detected
+ @VisibleForTesting
+ static final int PREFERENCE_ORDER_SATELLITE_FALLBACK = 40;
// Preference order that signifies the network shouldn't be set as a default network for
// the UIDs, only give them access to it. TODO : replace this with a boolean
// in NativeUidRangeConfig
@@ -834,6 +843,11 @@
private static final int EVENT_UID_FROZEN_STATE_CHANGED = 61;
/**
+ * Event to inform the ConnectivityService handler when a uid has lost carrier privileges.
+ */
+ private static final int EVENT_UID_CARRIER_PRIVILEGES_LOST = 62;
+
+ /**
* Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
* should be shown.
*/
@@ -922,6 +936,7 @@
private final QosCallbackTracker mQosCallbackTracker;
private final NetworkNotificationManager mNotifier;
private final LingerMonitor mLingerMonitor;
+ private final SatelliteAccessController mSatelliteAccessController;
// sequence number of NetworkRequests
private int mNextNetworkRequestId = NetworkRequest.FIRST_REQUEST_ID;
@@ -1272,6 +1287,18 @@
}
private final LegacyTypeTracker mLegacyTypeTracker = new LegacyTypeTracker(this);
+ private final CarrierPrivilegesLostListenerImpl mCarrierPrivilegesLostListenerImpl =
+ new CarrierPrivilegesLostListenerImpl();
+
+ private class CarrierPrivilegesLostListenerImpl implements CarrierPrivilegesLostListener {
+ @Override
+ public void onCarrierPrivilegesLost(int uid) {
+ if (mRequestRestrictedWifiEnabled) {
+ mHandler.sendMessage(mHandler.obtainMessage(
+ EVENT_UID_CARRIER_PRIVILEGES_LOST, uid, 0 /* arg2 */));
+ }
+ }
+ }
final LocalPriorityDump mPriorityDumper = new LocalPriorityDump();
/**
* Helper class which parses out priority arguments and dumps sections according to their
@@ -1330,6 +1357,11 @@
}
}
+ @VisibleForTesting
+ CarrierPrivilegesLostListener getCarrierPrivilegesLostListener() {
+ return mCarrierPrivilegesLostListenerImpl;
+ }
+
/**
* Dependencies of ConnectivityService, for injection in tests.
*/
@@ -1490,15 +1522,31 @@
*/
@Nullable
public CarrierPrivilegeAuthenticator makeCarrierPrivilegeAuthenticator(
- @NonNull final Context context, @NonNull final TelephonyManager tm) {
+ @NonNull final Context context,
+ @NonNull final TelephonyManager tm,
+ boolean requestRestrictedWifiEnabled,
+ @NonNull CarrierPrivilegesLostListener listener) {
if (isAtLeastT()) {
- return new CarrierPrivilegeAuthenticator(context, tm);
+ return new CarrierPrivilegeAuthenticator(
+ context, tm, requestRestrictedWifiEnabled, listener);
} else {
return null;
}
}
/**
+ * @see SatelliteAccessController
+ */
+ @Nullable
+ public SatelliteAccessController makeSatelliteAccessController(
+ @NonNull final Context context,
+ Consumer<Set<Integer>> updateSatelliteNetworkFallbackUidCallback,
+ @NonNull final Handler connectivityServiceInternalHandler) {
+ return new SatelliteAccessController(context, updateSatelliteNetworkFallbackUidCallback,
+ connectivityServiceInternalHandler);
+ }
+
+ /**
* @see DeviceConfigUtils#isTetheringFeatureEnabled
*/
public boolean isFeatureEnabled(Context context, String name) {
@@ -1761,8 +1809,20 @@
mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
mLocationPermissionChecker = mDeps.makeLocationPermissionChecker(mContext);
- mCarrierPrivilegeAuthenticator =
- mDeps.makeCarrierPrivilegeAuthenticator(mContext, mTelephonyManager);
+ mRequestRestrictedWifiEnabled = mDeps.isAtLeastU()
+ && mDeps.isFeatureEnabled(context, REQUEST_RESTRICTED_WIFI);
+ mCarrierPrivilegeAuthenticator = mDeps.makeCarrierPrivilegeAuthenticator(
+ mContext, mTelephonyManager, mRequestRestrictedWifiEnabled,
+ mCarrierPrivilegesLostListenerImpl);
+
+ if (mDeps.isAtLeastU()
+ && mDeps
+ .isFeatureNotChickenedOut(mContext, ALLOW_SATALLITE_NETWORK_FALLBACK)) {
+ mSatelliteAccessController = mDeps.makeSatelliteAccessController(
+ mContext, this::updateSatelliteNetworkPreferenceUids, mHandler);
+ } else {
+ mSatelliteAccessController = null;
+ }
// To ensure uid state is synchronized with Network Policy, register for
// NetworkPolicyManagerService events must happen prior to NetworkPolicyManagerService
@@ -1896,8 +1956,8 @@
mMulticastRoutingCoordinatorService =
mDeps.makeMulticastRoutingCoordinatorService(mHandler);
- mDestroyFrozenSockets = mDeps.isAtLeastU()
- && mDeps.isFeatureEnabled(context, KEY_DESTROY_FROZEN_SOCKETS_VERSION);
+ mDestroyFrozenSockets = mDeps.isAtLeastV() || (mDeps.isAtLeastU()
+ && mDeps.isFeatureEnabled(context, KEY_DESTROY_FROZEN_SOCKETS_VERSION));
mDelayDestroyFrozenSockets = mDeps.isAtLeastU()
&& mDeps.isFeatureEnabled(context, DELAY_DESTROY_FROZEN_SOCKETS_VERSION);
mAllowSysUiConnectivityReports = mDeps.isFeatureNotChickenedOut(
@@ -2032,6 +2092,18 @@
new Pair<>(network, proxyInfo)).sendToTarget();
}
+ /**
+ * Called when satellite network fallback uids at {@link SatelliteAccessController}
+ * cache was updated based on {@link
+ * android.app.role.OnRoleHoldersChangedListener#onRoleHoldersChanged(String, UserHandle)},
+ * to create multilayer request with preference order
+ * {@link #PREFERENCE_ORDER_SATELLITE_FALLBACK} there on.
+ *
+ */
+ private void updateSatelliteNetworkPreferenceUids(Set<Integer> satelliteNetworkFallbackUids) {
+ handleSetSatelliteNetworkPreference(satelliteNetworkFallbackUids);
+ }
+
private void handleAlwaysOnNetworkRequest(
NetworkRequest networkRequest, String settingName, boolean defaultValue) {
final boolean enable = toBool(Settings.Global.getInt(
@@ -2651,7 +2723,7 @@
Objects.requireNonNull(packageName);
Objects.requireNonNull(lp);
enforceNetworkStackOrSettingsPermission();
- if (!checkAccessPermission(-1 /* pid */, uid)) {
+ if (!hasAccessPermission(-1 /* pid */, uid)) {
return null;
}
return linkPropertiesRestrictedForCallerPermissions(lp, -1 /* callerPid */, uid);
@@ -2687,7 +2759,7 @@
Objects.requireNonNull(nc);
Objects.requireNonNull(packageName);
enforceNetworkStackOrSettingsPermission();
- if (!checkAccessPermission(-1 /* pid */, uid)) {
+ if (!hasAccessPermission(-1 /* pid */, uid)) {
return null;
}
return createWithLocationInfoSanitizedIfNecessaryWhenParceled(
@@ -2698,14 +2770,14 @@
private void redactUnderlyingNetworksForCapabilities(NetworkCapabilities nc, int pid, int uid) {
if (nc.getUnderlyingNetworks() != null
- && !checkNetworkFactoryOrSettingsPermission(pid, uid)) {
+ && !hasNetworkFactoryOrSettingsPermission(pid, uid)) {
nc.setUnderlyingNetworks(null);
}
}
private boolean canSeeAllowedUids(final int pid, final int uid, final int netOwnerUid) {
return Process.SYSTEM_UID == uid
- || checkAnyPermissionOf(mContext, pid, uid,
+ || hasAnyPermissionOf(mContext, pid, uid,
android.Manifest.permission.NETWORK_FACTORY);
}
@@ -2718,14 +2790,14 @@
// it happens for some reason (e.g. the package is uninstalled while CS is trying to
// send the callback) it would crash the system server with NPE.
final NetworkCapabilities newNc = new NetworkCapabilities(nc);
- if (!checkSettingsPermission(callerPid, callerUid)) {
+ if (!hasSettingsPermission(callerPid, callerUid)) {
newNc.setUids(null);
newNc.setSSID(null);
}
if (newNc.getNetworkSpecifier() != null) {
newNc.setNetworkSpecifier(newNc.getNetworkSpecifier().redact());
}
- if (!checkAnyPermissionOf(mContext, callerPid, callerUid,
+ if (!hasAnyPermissionOf(mContext, callerPid, callerUid,
android.Manifest.permission.NETWORK_STACK,
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK)) {
newNc.setAdministratorUids(new int[0]);
@@ -2793,11 +2865,12 @@
* Returns whether the app holds local mac address permission or not (might return cached
* result if the permission was already checked before).
*/
+ @CheckResult
public boolean hasLocalMacAddressPermission() {
if (mHasLocalMacAddressPermission == null) {
// If there is no cached result, perform the check now.
- mHasLocalMacAddressPermission =
- checkLocalMacAddressPermission(mCallingPid, mCallingUid);
+ mHasLocalMacAddressPermission = ConnectivityService.this
+ .hasLocalMacAddressPermission(mCallingPid, mCallingUid);
}
return mHasLocalMacAddressPermission;
}
@@ -2806,10 +2879,12 @@
* Returns whether the app holds settings permission or not (might return cached
* result if the permission was already checked before).
*/
+ @CheckResult
public boolean hasSettingsPermission() {
if (mHasSettingsPermission == null) {
// If there is no cached result, perform the check now.
- mHasSettingsPermission = checkSettingsPermission(mCallingPid, mCallingUid);
+ mHasSettingsPermission =
+ ConnectivityService.this.hasSettingsPermission(mCallingPid, mCallingUid);
}
return mHasSettingsPermission;
}
@@ -2913,7 +2988,7 @@
return new LinkProperties(lp);
}
- if (checkSettingsPermission(callerPid, callerUid)) {
+ if (hasSettingsPermission(callerPid, callerUid)) {
return new LinkProperties(lp, true /* parcelSensitiveFields */);
}
@@ -2929,7 +3004,7 @@
int callerUid, String callerPackageName) {
// There is no need to track the effective UID of the request here. If the caller
// lacks the settings permission, the effective UID is the same as the calling ID.
- if (!checkSettingsPermission()) {
+ if (!hasSettingsPermission()) {
// Unprivileged apps can only pass in null or their own UID.
if (nc.getUids() == null) {
// If the caller passes in null, the callback will also match networks that do not
@@ -3371,6 +3446,9 @@
public static final String LOG_BPF_RC = "log_bpf_rc_force_disable";
+ public static final String ALLOW_SATALLITE_NETWORK_FALLBACK =
+ "allow_satallite_network_fallback";
+
private void enforceInternetPermission() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.INTERNET,
@@ -3383,7 +3461,8 @@
"ConnectivityService");
}
- private boolean checkAccessPermission(int pid, int uid) {
+ @CheckResult
+ private boolean hasAccessPermission(int pid, int uid) {
return mContext.checkPermission(android.Manifest.permission.ACCESS_NETWORK_STATE, pid, uid)
== PERMISSION_GRANTED;
}
@@ -3469,7 +3548,8 @@
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
}
- private boolean checkNetworkFactoryOrSettingsPermission(int pid, int uid) {
+ @CheckResult
+ private boolean hasNetworkFactoryOrSettingsPermission(int pid, int uid) {
return PERMISSION_GRANTED == mContext.checkPermission(
android.Manifest.permission.NETWORK_FACTORY, pid, uid)
|| PERMISSION_GRANTED == mContext.checkPermission(
@@ -3479,13 +3559,14 @@
|| UserHandle.getAppId(uid) == Process.BLUETOOTH_UID;
}
- private boolean checkSettingsPermission() {
- return PermissionUtils.checkAnyPermissionOf(mContext,
- android.Manifest.permission.NETWORK_SETTINGS,
+ @CheckResult
+ private boolean hasSettingsPermission() {
+ return hasAnyPermissionOf(mContext, android.Manifest.permission.NETWORK_SETTINGS,
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
}
- private boolean checkSettingsPermission(int pid, int uid) {
+ @CheckResult
+ private boolean hasSettingsPermission(int pid, int uid) {
return PERMISSION_GRANTED == mContext.checkPermission(
android.Manifest.permission.NETWORK_SETTINGS, pid, uid)
|| PERMISSION_GRANTED == mContext.checkPermission(
@@ -3522,33 +3603,36 @@
"ConnectivityService");
}
- private boolean checkNetworkStackPermission() {
- return PermissionUtils.checkAnyPermissionOf(mContext,
- android.Manifest.permission.NETWORK_STACK,
+ @CheckResult
+ private boolean hasNetworkStackPermission() {
+ return hasAnyPermissionOf(mContext, android.Manifest.permission.NETWORK_STACK,
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
}
- private boolean checkNetworkStackPermission(int pid, int uid) {
- return checkAnyPermissionOf(mContext, pid, uid,
- android.Manifest.permission.NETWORK_STACK,
+ @CheckResult
+ private boolean hasNetworkStackPermission(int pid, int uid) {
+ return hasAnyPermissionOf(mContext, pid, uid, android.Manifest.permission.NETWORK_STACK,
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
}
- private boolean checkSystemBarServicePermission(int pid, int uid) {
- return checkAnyPermissionOf(mContext, pid, uid,
+ @CheckResult
+ private boolean hasSystemBarServicePermission(int pid, int uid) {
+ return hasAnyPermissionOf(mContext, pid, uid,
android.Manifest.permission.STATUS_BAR_SERVICE);
}
- private boolean checkNetworkSignalStrengthWakeupPermission(int pid, int uid) {
- return checkAnyPermissionOf(mContext, pid, uid,
+ @CheckResult
+ private boolean hasNetworkSignalStrengthWakeupPermission(int pid, int uid) {
+ return hasAnyPermissionOf(mContext, pid, uid,
android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP,
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
android.Manifest.permission.NETWORK_SETTINGS);
}
- private boolean checkConnectivityRestrictedNetworksPermission(int callingUid,
+ @CheckResult
+ private boolean hasConnectivityRestrictedNetworksPermission(int callingUid,
boolean checkUidsAllowedList) {
- if (PermissionUtils.checkAnyPermissionOf(mContext,
+ if (hasAnyPermissionOf(mContext,
android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS)) {
return true;
}
@@ -3556,8 +3640,7 @@
// 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)) {
+ if (hasAnyPermissionOf(mContext, android.Manifest.permission.CONNECTIVITY_INTERNAL)) {
return true;
}
@@ -3571,7 +3654,7 @@
private void enforceConnectivityRestrictedNetworksPermission(boolean checkUidsAllowedList) {
final int callingUid = mDeps.getCallingUid();
- if (!checkConnectivityRestrictedNetworksPermission(callingUid, checkUidsAllowedList)) {
+ if (!hasConnectivityRestrictedNetworksPermission(callingUid, checkUidsAllowedList)) {
throw new SecurityException("ConnectivityService: user " + callingUid
+ " has no permission to access restricted network.");
}
@@ -3581,7 +3664,8 @@
mContext.enforceCallingOrSelfPermission(KeepaliveTracker.PERMISSION, "ConnectivityService");
}
- private boolean checkLocalMacAddressPermission(int pid, int uid) {
+ @CheckResult
+ private boolean hasLocalMacAddressPermission(int pid, int uid) {
return PERMISSION_GRANTED == mContext.checkPermission(
Manifest.permission.LOCAL_MAC_ADDRESS, pid, uid);
}
@@ -3731,6 +3815,10 @@
updateMobileDataPreferredUids();
}
+ if (mSatelliteAccessController != null) {
+ mSatelliteAccessController.start();
+ }
+
// On T+ devices, register callback for statsd to pull NETWORK_BPF_MAP_INFO atom
if (mDeps.isAtLeastT()) {
mBpfNetMaps.setPullAtomCallback(mContext);
@@ -3875,12 +3963,13 @@
@Override
protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer,
@Nullable String[] args) {
- if (!checkDumpPermission(mContext, TAG, writer)) return;
+ if (!hasDumpPermission(mContext, TAG, writer)) return;
mPriorityDumper.dump(fd, writer, args);
}
- private boolean checkDumpPermission(Context context, String tag, PrintWriter pw) {
+ @CheckResult
+ private boolean hasDumpPermission(Context context, String tag, PrintWriter pw) {
if (context.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
pw.println("Permission Denial: can't dump " + tag + " from from pid="
@@ -5697,7 +5786,7 @@
}
private RequestInfoPerUidCounter getRequestCounter(NetworkRequestInfo nri) {
- return checkAnyPermissionOf(mContext,
+ return hasAnyPermissionOf(mContext,
nri.mPid, nri.mUid, NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK)
? mSystemNetworkRequestCounter : mNetworkRequestCounter;
}
@@ -5921,7 +6010,7 @@
if (nm == null) return;
if (request == CaptivePortal.APP_REQUEST_REEVALUATION_REQUIRED) {
- checkNetworkStackPermission();
+ hasNetworkStackPermission();
nm.forceReevaluation(mDeps.getCallingUid());
}
}
@@ -5951,7 +6040,7 @@
* @see MultinetworkPolicyTracker#getAvoidBadWifi()
*/
public boolean shouldAvoidBadWifi() {
- if (!checkNetworkStackPermission()) {
+ if (!hasNetworkStackPermission()) {
throw new SecurityException("avoidBadWifi requires NETWORK_STACK permission");
}
return avoidBadWifi();
@@ -6402,6 +6491,9 @@
UidFrozenStateChangedArgs args = (UidFrozenStateChangedArgs) msg.obj;
handleFrozenUids(args.mUids, args.mFrozenStates);
break;
+ case EVENT_UID_CARRIER_PRIVILEGES_LOST:
+ handleUidCarrierPrivilegesLost(msg.arg1);
+ break;
}
}
}
@@ -7471,20 +7563,25 @@
// specific SSID/SignalStrength, or the calling app has permission to do so.
private void ensureSufficientPermissionsForRequest(NetworkCapabilities nc,
int callerPid, int callerUid, String callerPackageName) {
- if (null != nc.getSsid() && !checkSettingsPermission(callerPid, callerUid)) {
+ if (null != nc.getSsid() && !hasSettingsPermission(callerPid, callerUid)) {
throw new SecurityException("Insufficient permissions to request a specific SSID");
}
if (nc.hasSignalStrength()
- && !checkNetworkSignalStrengthWakeupPermission(callerPid, callerUid)) {
+ && !hasNetworkSignalStrengthWakeupPermission(callerPid, callerUid)) {
throw new SecurityException(
"Insufficient permissions to request a specific signal strength");
}
mAppOpsManager.checkPackage(callerUid, callerPackageName);
- if (!nc.getSubscriptionIds().isEmpty()) {
- enforceNetworkFactoryPermission();
+ if (nc.getSubscriptionIds().isEmpty()) {
+ return;
}
+ if (mRequestRestrictedWifiEnabled
+ && canRequestRestrictedNetworkDueToCarrierPrivileges(nc, callerUid)) {
+ return;
+ }
+ enforceNetworkFactoryPermission();
}
private int[] getSignalStrengthThresholds(@NonNull final NetworkAgentInfo nai) {
@@ -7574,7 +7671,7 @@
int reqTypeInt, Messenger messenger, int timeoutMs, final IBinder binder,
int legacyType, int callbackFlags, @NonNull String callingPackageName,
@Nullable String callingAttributionTag) {
- if (legacyType != TYPE_NONE && !checkNetworkStackPermission()) {
+ if (legacyType != TYPE_NONE && !hasNetworkStackPermission()) {
if (isTargetSdkAtleast(Build.VERSION_CODES.M, mDeps.getCallingUid(),
callingPackageName)) {
throw new SecurityException("Insufficient permissions to specify legacy type");
@@ -7764,6 +7861,22 @@
applicationNetworkCapabilities.enforceSelfCertifiedNetworkCapabilitiesDeclared(
networkCapabilities);
}
+
+ private boolean canRequestRestrictedNetworkDueToCarrierPrivileges(
+ NetworkCapabilities networkCapabilities, int callingUid) {
+ if (mRequestRestrictedWifiEnabled) {
+ // For U+ devices, callers with carrier privilege could request restricted networks
+ // with CBS capabilities, or any restricted WiFi networks.
+ return ((networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)
+ || networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))
+ && hasCarrierPrivilegeForNetworkCaps(callingUid, networkCapabilities));
+ } else {
+ // For T+ devices, callers with carrier privilege could request with CBS
+ // capabilities.
+ return (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)
+ && hasCarrierPrivilegeForNetworkCaps(callingUid, networkCapabilities));
+ }
+ }
private void enforceNetworkRequestPermissions(NetworkCapabilities networkCapabilities,
String callingPackageName, String callingAttributionTag, final int callingUid) {
if (shouldCheckCapabilitiesDeclaration(networkCapabilities, callingUid,
@@ -7771,13 +7884,11 @@
enforceRequestCapabilitiesDeclaration(callingPackageName, networkCapabilities,
callingUid);
}
- if (networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED) == false) {
- // For T+ devices, callers with carrier privilege could request with CBS capabilities.
- if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)
- && hasCarrierPrivilegeForNetworkCaps(callingUid, networkCapabilities)) {
- return;
+ if (!networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)) {
+ if (!canRequestRestrictedNetworkDueToCarrierPrivileges(
+ networkCapabilities, callingUid)) {
+ enforceConnectivityRestrictedNetworksPermission(true /* checkUidsAllowedList */);
}
- enforceConnectivityRestrictedNetworksPermission(true /* checkUidsAllowedList */);
} else {
enforceChangePermission(callingPackageName, callingAttributionTag);
}
@@ -9044,6 +9155,38 @@
}
}
+ private void handleUidCarrierPrivilegesLost(int uid) {
+ ensureRunningOnConnectivityServiceThread();
+ // A NetworkRequest needs to be revoked when all the conditions are met
+ // 1. It requests restricted network
+ // 2. The requestor uid matches the uid with the callback
+ // 3. The app doesn't have Carrier Privileges
+ // 4. The app doesn't have permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS
+ for (final NetworkRequest nr : mNetworkRequests.keySet()) {
+ if ((nr.isRequest() || nr.isListen())
+ && !nr.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ && nr.getRequestorUid() == uid
+ && !hasConnectivityRestrictedNetworksPermission(uid, true)) {
+ declareNetworkRequestUnfulfillable(nr);
+ }
+ }
+
+ // A NetworkAgent's allowedUids may need to be updated if the app has lost
+ // carrier config
+ for (final NetworkAgentInfo nai : mNetworkAgentInfos) {
+ if (nai.networkCapabilities.getAllowedUidsNoCopy().contains(uid)) {
+ final NetworkCapabilities nc = new NetworkCapabilities(nai.networkCapabilities);
+ NetworkAgentInfo.restrictCapabilitiesFromNetworkAgent(
+ nc,
+ uid,
+ false /* hasAutomotiveFeature (irrelevant) */,
+ mDeps,
+ mCarrierPrivilegeAuthenticator);
+ updateCapabilities(nai.getScore(), nai, nc);
+ }
+ }
+ }
+
/**
* Update the NetworkCapabilities for {@code nai} to {@code nc}. Specifically:
*
@@ -9491,7 +9634,6 @@
final ArraySet<Integer> toAdd = new ArraySet<>(newUids);
toRemove.removeAll(newUids);
toAdd.removeAll(prevUids);
-
try {
if (!toAdd.isEmpty()) {
mNetd.networkAddUidRangesParcel(new NativeUidRangeConfig(
@@ -11137,17 +11279,28 @@
err.getFileDescriptor(), args);
}
- private Boolean parseBooleanArgument(final String arg) {
- if ("true".equals(arg)) {
- return true;
- } else if ("false".equals(arg)) {
- return false;
- } else {
- return null;
- }
- }
-
private class ShellCmd extends BasicShellCommandHandler {
+
+ private Boolean parseBooleanArgument(final String arg) {
+ if ("true".equals(arg)) {
+ return true;
+ } else if ("false".equals(arg)) {
+ return false;
+ } else {
+ getOutPrintWriter().println("Invalid boolean argument: " + arg);
+ return null;
+ }
+ }
+
+ private Integer parseIntegerArgument(final String arg) {
+ try {
+ return Integer.valueOf(arg);
+ } catch (NumberFormatException ne) {
+ getOutPrintWriter().println("Invalid integer argument: " + arg);
+ return null;
+ }
+ }
+
@Override
public int onCommand(String cmd) {
if (cmd == null) {
@@ -11224,6 +11377,38 @@
}
return 0;
}
+ case "set-background-networking-enabled-for-uid": {
+ final Integer uid = parseIntegerArgument(getNextArg());
+ final Boolean enabled = parseBooleanArgument(getNextArg());
+ if (null == enabled || null == uid) {
+ onHelp();
+ return -1;
+ }
+ final int rule = enabled ? FIREWALL_RULE_ALLOW : FIREWALL_RULE_DEFAULT;
+ setUidFirewallRule(FIREWALL_CHAIN_BACKGROUND, uid, rule);
+ final String msg = (enabled ? "Enabled" : "Disabled")
+ + " background networking for uid " + uid;
+ Log.i(TAG, msg);
+ pw.println(msg);
+ return 0;
+ }
+ case "get-background-networking-enabled-for-uid": {
+ final Integer uid = parseIntegerArgument(getNextArg());
+ if (null == uid) {
+ onHelp();
+ return -1;
+ }
+ final int rule = getUidFirewallRule(FIREWALL_CHAIN_BACKGROUND, uid);
+ if (FIREWALL_RULE_ALLOW == rule) {
+ pw.println(uid + ": allow");
+ } else if (FIREWALL_RULE_DENY == rule || FIREWALL_RULE_DEFAULT == rule) {
+ pw.println(uid + ": deny");
+ } else {
+ throw new IllegalStateException(
+ "Unknown rule " + rule + " for uid " + uid);
+ }
+ return 0;
+ }
case "reevaluate":
// Usage : adb shell cmd connectivity reevaluate <netId>
// If netId is omitted, then reevaluate the default network
@@ -11284,6 +11469,10 @@
+ " no effect if the chain is disabled.");
pw.println(" get-package-networking-enabled [package name]");
pw.println(" Get the deny bit in FIREWALL_CHAIN_OEM_DENY_3 for package.");
+ pw.println(" set-background-networking-enabled-for-uid [uid] [true|false]");
+ pw.println(" Set the allow bit in FIREWALL_CHAIN_BACKGROUND for the given uid.");
+ pw.println(" get-background-networking-enabled-for-uid [uid]");
+ pw.println(" Get the allow bit in FIREWALL_CHAIN_BACKGROUND for the given uid.");
}
}
@@ -11324,7 +11513,7 @@
// Connection owner UIDs are visible only to the network stack and to the VpnService-based
// VPN, if any, that applies to the UID that owns the connection.
- if (checkNetworkStackPermission()) return uid;
+ if (hasNetworkStackPermission()) return uid;
final NetworkAgentInfo vpn = getVpnForUid(uid);
if (vpn == null || getVpnType(vpn) != VpnManager.TYPE_VPN_SERVICE
@@ -11584,7 +11773,7 @@
if (report == null) {
continue;
}
- if (!checkConnectivityDiagnosticsPermissions(
+ if (!hasConnectivityDiagnosticsPermissions(
nri.mPid, nri.mUid, nai, cbInfo.mCallingPackageName)) {
continue;
}
@@ -11747,7 +11936,7 @@
continue;
}
- if (!checkConnectivityDiagnosticsPermissions(
+ if (!hasConnectivityDiagnosticsPermissions(
nri.mPid, nri.mUid, nai, cbInfo.mCallingPackageName)) {
continue;
}
@@ -11791,14 +11980,15 @@
return false;
}
+ @CheckResult
@VisibleForTesting
- boolean checkConnectivityDiagnosticsPermissions(
+ boolean hasConnectivityDiagnosticsPermissions(
int callbackPid, int callbackUid, NetworkAgentInfo nai, String callbackPackageName) {
- if (checkNetworkStackPermission(callbackPid, callbackUid)) {
+ if (hasNetworkStackPermission(callbackPid, callbackUid)) {
return true;
}
if (mAllowSysUiConnectivityReports
- && checkSystemBarServicePermission(callbackPid, callbackUid)) {
+ && hasSystemBarServicePermission(callbackPid, callbackUid)) {
return true;
}
@@ -12664,16 +12854,27 @@
@VisibleForTesting
@NonNull
- ArraySet<NetworkRequestInfo> createNrisFromMobileDataPreferredUids(
- @NonNull final Set<Integer> uids) {
+ ArraySet<NetworkRequestInfo> createNrisForPreferenceOrder(@NonNull final Set<Integer> uids,
+ @NonNull final List<NetworkRequest> requests,
+ final int preferenceOrder) {
final ArraySet<NetworkRequestInfo> nris = new ArraySet<>();
if (uids.size() == 0) {
// Should not create NetworkRequestInfo if no preferences. Without uid range in
// NetworkRequestInfo, makeDefaultForApps() would treat it as a illegal NRI.
- if (DBG) log("Don't create NetworkRequestInfo because no preferences");
return nris;
}
+ final Set<UidRange> ranges = new ArraySet<>();
+ for (final int uid : uids) {
+ ranges.add(new UidRange(uid, uid));
+ }
+ setNetworkRequestUids(requests, ranges);
+ nris.add(new NetworkRequestInfo(Process.myUid(), requests, preferenceOrder));
+ return nris;
+ }
+
+ ArraySet<NetworkRequestInfo> createNrisFromMobileDataPreferredUids(
+ @NonNull final Set<Integer> uids) {
final List<NetworkRequest> requests = new ArrayList<>();
// The NRI should be comprised of two layers:
// - The request for the mobile network preferred.
@@ -12682,14 +12883,28 @@
TRANSPORT_CELLULAR, NetworkRequest.Type.REQUEST));
requests.add(createDefaultInternetRequestForTransport(
TYPE_NONE, NetworkRequest.Type.TRACK_DEFAULT));
- final Set<UidRange> ranges = new ArraySet<>();
- for (final int uid : uids) {
- ranges.add(new UidRange(uid, uid));
- }
- setNetworkRequestUids(requests, ranges);
- nris.add(new NetworkRequestInfo(Process.myUid(), requests,
- PREFERENCE_ORDER_MOBILE_DATA_PREFERERRED));
- return nris;
+ return createNrisForPreferenceOrder(uids, requests, PREFERENCE_ORDER_MOBILE_DATA_PREFERERRED
+ );
+ }
+
+ ArraySet<NetworkRequestInfo> createMultiLayerNrisFromSatelliteNetworkFallbackUids(
+ @NonNull final Set<Integer> uids) {
+ final List<NetworkRequest> requests = new ArrayList<>();
+
+ // request: track default(unrestricted internet network)
+ requests.add(createDefaultInternetRequestForTransport(
+ TYPE_NONE, NetworkRequest.Type.TRACK_DEFAULT));
+
+ // request: restricted Satellite internet
+ final NetworkCapabilities cap = new NetworkCapabilities.Builder()
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ .addTransportType(NetworkCapabilities.TRANSPORT_SATELLITE)
+ .build();
+ requests.add(createNetworkRequest(NetworkRequest.Type.REQUEST, cap));
+
+ return createNrisForPreferenceOrder(uids, requests, PREFERENCE_ORDER_SATELLITE_FALLBACK);
}
private void handleMobileDataPreferredUidsChanged() {
@@ -12701,6 +12916,16 @@
rematchAllNetworksAndRequests();
}
+ private void handleSetSatelliteNetworkPreference(
+ @NonNull final Set<Integer> satelliteNetworkPreferredUids) {
+ removeDefaultNetworkRequestsForPreference(PREFERENCE_ORDER_SATELLITE_FALLBACK);
+ addPerAppDefaultNetworkRequests(
+ createMultiLayerNrisFromSatelliteNetworkFallbackUids(satelliteNetworkPreferredUids)
+ );
+ // Finally, rematch.
+ rematchAllNetworksAndRequests();
+ }
+
private void handleIngressRateLimitChanged() {
final long oldIngressRateLimit = mIngressRateLimit;
mIngressRateLimit = ConnectivitySettingsManager.getIngressRateLimitInBytesPerSecond(
diff --git a/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java b/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
index 5705ebe..533278e 100644
--- a/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
+++ b/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
@@ -79,11 +79,16 @@
@NonNull
private final List<PrivilegeListener> mCarrierPrivilegesChangedListeners = new ArrayList<>();
private final boolean mUseCallbacksForServiceChanged;
+ private final boolean mRequestRestrictedWifiEnabled;
+ @NonNull
+ private final CarrierPrivilegesLostListener mListener;
public CarrierPrivilegeAuthenticator(@NonNull final Context c,
@NonNull final Dependencies deps,
@NonNull final TelephonyManager t,
- @NonNull final TelephonyManagerShim telephonyManagerShim) {
+ @NonNull final TelephonyManagerShim telephonyManagerShim,
+ final boolean requestRestrictedWifiEnabled,
+ @NonNull CarrierPrivilegesLostListener listener) {
mContext = c;
mTelephonyManager = t;
mTelephonyManagerShim = telephonyManagerShim;
@@ -92,6 +97,8 @@
mHandler = new Handler(thread.getLooper());
mUseCallbacksForServiceChanged = deps.isFeatureEnabled(
c, CARRIER_SERVICE_CHANGED_USE_CALLBACK);
+ mRequestRestrictedWifiEnabled = requestRestrictedWifiEnabled;
+ mListener = listener;
final IntentFilter filter = new IntentFilter();
filter.addAction(TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED);
synchronized (mLock) {
@@ -113,8 +120,10 @@
}
public CarrierPrivilegeAuthenticator(@NonNull final Context c,
- @NonNull final TelephonyManager t) {
- this(c, new Dependencies(), t, TelephonyManagerShimImpl.newInstance(t));
+ @NonNull final TelephonyManager t, final boolean requestRestrictedWifiEnabled,
+ @NonNull CarrierPrivilegesLostListener listener) {
+ this(c, new Dependencies(), t, TelephonyManagerShimImpl.newInstance(t),
+ requestRestrictedWifiEnabled, listener);
}
public static class Dependencies {
@@ -133,6 +142,18 @@
}
}
+ /**
+ * Listener interface to get a notification when the carrier App lost its privileges.
+ */
+ public interface CarrierPrivilegesLostListener {
+ /**
+ * Called when the carrier App lost its privileges.
+ *
+ * @param uid The uid of the carrier app which has lost its privileges.
+ */
+ void onCarrierPrivilegesLost(int uid);
+ }
+
private void simConfigChanged() {
synchronized (mLock) {
unregisterCarrierPrivilegesListeners();
@@ -171,7 +192,11 @@
return;
}
synchronized (mLock) {
+ int oldUid = mCarrierServiceUid.get(mLogicalSlot);
mCarrierServiceUid.put(mLogicalSlot, carrierServiceUid);
+ if (oldUid != 0 && oldUid != carrierServiceUid) {
+ mListener.onCarrierPrivilegesLost(oldUid);
+ }
}
}
}
@@ -193,7 +218,11 @@
private void unregisterCarrierPrivilegesListeners() {
for (PrivilegeListener carrierPrivilegesListener : mCarrierPrivilegesChangedListeners) {
removeCarrierPrivilegesListener(carrierPrivilegesListener);
+ int oldUid = mCarrierServiceUid.get(carrierPrivilegesListener.mLogicalSlot);
mCarrierServiceUid.delete(carrierPrivilegesListener.mLogicalSlot);
+ if (oldUid != 0) {
+ mListener.onCarrierPrivilegesLost(oldUid);
+ }
}
mCarrierPrivilegesChangedListeners.clear();
}
@@ -231,7 +260,7 @@
public boolean isCarrierServiceUidForNetworkCapabilities(int callingUid,
@NonNull NetworkCapabilities networkCapabilities) {
if (callingUid == Process.INVALID_UID) return false;
- final int subId;
+ int subId;
if (networkCapabilities.hasSingleTransportBesidesTest(TRANSPORT_CELLULAR)) {
subId = getSubIdFromTelephonySpecifier(networkCapabilities.getNetworkSpecifier());
} else if (networkCapabilities.hasSingleTransportBesidesTest(TRANSPORT_WIFI)) {
@@ -239,6 +268,12 @@
} else {
subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
}
+ if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID
+ && mRequestRestrictedWifiEnabled
+ && networkCapabilities.getSubscriptionIds().size() == 1) {
+ subId = networkCapabilities.getSubscriptionIds().toArray(new Integer[0])[0];
+ }
+
if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID
&& !networkCapabilities.getSubscriptionIds().contains(subId)) {
// Ideally, the code above should just use networkCapabilities.getSubscriptionIds()
@@ -257,10 +292,16 @@
@VisibleForTesting
void updateCarrierServiceUid() {
synchronized (mLock) {
+ SparseIntArray oldCarrierServiceUid = mCarrierServiceUid.clone();
mCarrierServiceUid.clear();
for (int i = 0; i < mModemCount; i++) {
mCarrierServiceUid.put(i, getCarrierServicePackageUidForSlot(i));
}
+ for (int i = 0; i < oldCarrierServiceUid.size(); i++) {
+ if (mCarrierServiceUid.indexOfValue(oldCarrierServiceUid.valueAt(i)) < 0) {
+ mListener.onCarrierPrivilegesLost(oldCarrierServiceUid.valueAt(i));
+ }
+ }
}
}
@@ -340,6 +381,7 @@
public void dump(IndentingPrintWriter pw) {
pw.println("CarrierPrivilegeAuthenticator:");
+ pw.println("mRequestRestrictedWifiEnabled = " + mRequestRestrictedWifiEnabled);
synchronized (mLock) {
final int size = mCarrierServiceUid.size();
for (int i = 0; i < size; ++i) {
diff --git a/service/src/com/android/server/connectivity/ConnectivityFlags.java b/service/src/com/android/server/connectivity/ConnectivityFlags.java
index f8f76ef..bf09160 100644
--- a/service/src/com/android/server/connectivity/ConnectivityFlags.java
+++ b/service/src/com/android/server/connectivity/ConnectivityFlags.java
@@ -36,6 +36,8 @@
public static final String CARRIER_SERVICE_CHANGED_USE_CALLBACK =
"carrier_service_changed_use_callback_version";
+ public static final String REQUEST_RESTRICTED_WIFI =
+ "request_restricted_wifi";
private boolean mNoRematchAllRequestsOnRegister;
/**
diff --git a/service/src/com/android/server/connectivity/KeepaliveStatsTracker.java b/service/src/com/android/server/connectivity/KeepaliveStatsTracker.java
index 7a8b41b..48af9fa 100644
--- a/service/src/com/android/server/connectivity/KeepaliveStatsTracker.java
+++ b/service/src/com/android/server/connectivity/KeepaliveStatsTracker.java
@@ -74,9 +74,10 @@
public class KeepaliveStatsTracker {
private static final String TAG = KeepaliveStatsTracker.class.getSimpleName();
private static final int INVALID_KEEPALIVE_ID = -1;
- // 1 hour acceptable deviation in metrics collection duration time.
+ // 2 hour acceptable deviation in metrics collection duration time to account for the 1 hour
+ // window of AlarmManager.
private static final long MAX_EXPECTED_DURATION_MS =
- AutomaticOnOffKeepaliveTracker.METRICS_COLLECTION_DURATION_MS + 1 * 60 * 60 * 1_000L;
+ AutomaticOnOffKeepaliveTracker.METRICS_COLLECTION_DURATION_MS + 2 * 60 * 60 * 1_000L;
@NonNull private final Handler mConnectivityServiceHandler;
@NonNull private final Dependencies mDependencies;
diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
index 50cad45..76993a6 100644
--- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
@@ -1551,7 +1551,7 @@
* @param hasAutomotiveFeature true if this device has the automotive feature, false otherwise
* @param authenticator the carrier privilege authenticator to check for telephony constraints
*/
- public void restrictCapabilitiesFromNetworkAgent(@NonNull final NetworkCapabilities nc,
+ public static void restrictCapabilitiesFromNetworkAgent(@NonNull final NetworkCapabilities nc,
final int creatorUid, final boolean hasAutomotiveFeature,
@NonNull final ConnectivityService.Dependencies deps,
@Nullable final CarrierPrivilegeAuthenticator authenticator) {
@@ -1564,7 +1564,7 @@
}
}
- private boolean areAllowedUidsAcceptableFromNetworkAgent(
+ private static boolean areAllowedUidsAcceptableFromNetworkAgent(
@NonNull final NetworkCapabilities nc, final boolean hasAutomotiveFeature,
@NonNull final ConnectivityService.Dependencies deps,
@Nullable final CarrierPrivilegeAuthenticator carrierPrivilegeAuthenticator) {
diff --git a/service/src/com/android/server/connectivity/SatelliteAccessController.java b/service/src/com/android/server/connectivity/SatelliteAccessController.java
new file mode 100644
index 0000000..0968aff
--- /dev/null
+++ b/service/src/com/android/server/connectivity/SatelliteAccessController.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.app.role.OnRoleHoldersChangedListener;
+import android.app.role.RoleManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.Process;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Tracks the uid of all the default messaging application which are role_sms role and
+ * satellite_communication permission complaint and requests ConnectivityService to create multi
+ * layer request with satellite internet access support for the default message application.
+ * @hide
+ */
+public class SatelliteAccessController {
+ private static final String TAG = SatelliteAccessController.class.getSimpleName();
+ private final PackageManager mPackageManager;
+ private final Dependencies mDeps;
+ private final DefaultMessageRoleListener mDefaultMessageRoleListener;
+ private final Consumer<Set<Integer>> mCallback;
+ private final Set<Integer> mSatelliteNetworkPreferredUidCache = new ArraySet<>();
+ private final Handler mConnectivityServiceHandler;
+
+ /**
+ * Monitor {@link android.app.role.OnRoleHoldersChangedListener#onRoleHoldersChanged(String,
+ * UserHandle)},
+ *
+ */
+ private final class DefaultMessageRoleListener
+ implements OnRoleHoldersChangedListener {
+ @Override
+ public void onRoleHoldersChanged(String role, UserHandle user) {
+ if (RoleManager.ROLE_SMS.equals(role)) {
+ Log.i(TAG, "ROLE_SMS Change detected ");
+ onRoleSmsChanged();
+ }
+ }
+
+ public void register() {
+ try {
+ mDeps.addOnRoleHoldersChangedListenerAsUser(
+ mConnectivityServiceHandler::post, this, UserHandle.ALL);
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Could not register satellite controller listener due to " + e);
+ }
+ }
+ }
+
+ public SatelliteAccessController(@NonNull final Context c,
+ Consumer<Set<Integer>> callback,
+ @NonNull final Handler connectivityServiceInternalHandler) {
+ this(c, new Dependencies(c), callback, connectivityServiceInternalHandler);
+ }
+
+ public static class Dependencies {
+ private final RoleManager mRoleManager;
+
+ private Dependencies(Context context) {
+ mRoleManager = context.getSystemService(RoleManager.class);
+ }
+
+ /** See {@link RoleManager#getRoleHolders(String)} */
+ public List<String> getRoleHolders(String roleName) {
+ return mRoleManager.getRoleHolders(roleName);
+ }
+
+ /** See {@link RoleManager#addOnRoleHoldersChangedListenerAsUser} */
+ public void addOnRoleHoldersChangedListenerAsUser(@NonNull Executor executor,
+ @NonNull OnRoleHoldersChangedListener listener, UserHandle user) {
+ mRoleManager.addOnRoleHoldersChangedListenerAsUser(executor, listener, user);
+ }
+ }
+
+ @VisibleForTesting
+ SatelliteAccessController(@NonNull final Context c, @NonNull final Dependencies deps,
+ Consumer<Set<Integer>> callback,
+ @NonNull final Handler connectivityServiceInternalHandler) {
+ mDeps = deps;
+ mPackageManager = c.getPackageManager();
+ mDefaultMessageRoleListener = new DefaultMessageRoleListener();
+ mCallback = callback;
+ mConnectivityServiceHandler = connectivityServiceInternalHandler;
+ }
+
+ private void updateSatelliteNetworkPreferredUidListCache(List<String> packageNames) {
+ for (String packageName : packageNames) {
+ // Check if SATELLITE_COMMUNICATION permission is enabled for default sms application
+ // package before adding it part of satellite network preferred uid cache list.
+ if (isSatellitePermissionEnabled(packageName)) {
+ mSatelliteNetworkPreferredUidCache.add(getUidForPackage(packageName));
+ }
+ }
+ }
+
+ //Check if satellite communication is enabled for the package
+ private boolean isSatellitePermissionEnabled(String packageName) {
+ if (mPackageManager != null) {
+ return mPackageManager.checkPermission(
+ Manifest.permission.SATELLITE_COMMUNICATION, packageName)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+ return false;
+ }
+
+ private int getUidForPackage(String pkgName) {
+ if (pkgName == null) {
+ return Process.INVALID_UID;
+ }
+ try {
+ if (mPackageManager != null) {
+ ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo(pkgName, 0);
+ if (applicationInfo != null) {
+ return applicationInfo.uid;
+ }
+ }
+ } catch (PackageManager.NameNotFoundException exception) {
+ Log.e(TAG, "Unable to find uid for package: " + pkgName);
+ }
+ return Process.INVALID_UID;
+ }
+
+ //on Role sms change triggered by OnRoleHoldersChangedListener()
+ private void onRoleSmsChanged() {
+ final List<String> packageNames = getRoleSmsChangedPackageName();
+
+ // Create a new Set
+ Set<Integer> previousSatellitePreferredUid = new ArraySet<>(
+ mSatelliteNetworkPreferredUidCache);
+
+ mSatelliteNetworkPreferredUidCache.clear();
+
+ if (packageNames != null) {
+ Log.i(TAG, "role_sms_packages: " + packageNames);
+ // On Role change listener, update the satellite network preferred uid cache list
+ updateSatelliteNetworkPreferredUidListCache(packageNames);
+ Log.i(TAG, "satellite_preferred_uid: " + mSatelliteNetworkPreferredUidCache);
+ } else {
+ Log.wtf(TAG, "package name was found null");
+ }
+
+ // on Role change, update the multilayer request at ConnectivityService with updated
+ // satellite network preferred uid cache list if changed or to revoke for previous default
+ // sms app
+ if (!mSatelliteNetworkPreferredUidCache.equals(previousSatellitePreferredUid)) {
+ Log.i(TAG, "update multi layer request");
+ mCallback.accept(mSatelliteNetworkPreferredUidCache);
+ }
+ }
+
+ private List<String> getRoleSmsChangedPackageName() {
+ try {
+ return mDeps.getRoleHolders(RoleManager.ROLE_SMS);
+ } catch (RuntimeException e) {
+ Log.wtf(TAG, "Could not get package name at role sms change update due to: " + e);
+ return null;
+ }
+ }
+
+ /** Register OnRoleHoldersChangedListener */
+ public void start() {
+ mConnectivityServiceHandler.post(this::onRoleSmsChanged);
+ mDefaultMessageRoleListener.register();
+ }
+}
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index 8f018c0..47e897d 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -28,34 +28,35 @@
// though they are not in the current.txt files.
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
java_library {
- name: "net-utils-device-common",
- srcs: [
- "device/com/android/net/module/util/arp/ArpPacket.java",
- "device/com/android/net/module/util/DeviceConfigUtils.java",
- "device/com/android/net/module/util/DomainUtils.java",
- "device/com/android/net/module/util/FdEventsReader.java",
- "device/com/android/net/module/util/NetworkMonitorUtils.java",
- "device/com/android/net/module/util/PacketReader.java",
- "device/com/android/net/module/util/SharedLog.java",
- "device/com/android/net/module/util/SocketUtils.java",
- "device/com/android/net/module/util/FeatureVersions.java",
- "device/com/android/net/module/util/HandlerUtils.java",
- // This library is used by system modules, for which the system health impact of Kotlin
- // has not yet been evaluated. Annotations may need jarjar'ing.
- // "src_devicecommon/**/*.kt",
- ],
- sdk_version: "module_current",
- min_sdk_version: "30",
- target_sdk_version: "30",
- apex_available: [
- "//apex_available:anyapex",
- "//apex_available:platform",
- ],
- visibility: [
+ name: "net-utils-device-common",
+ srcs: [
+ "device/com/android/net/module/util/arp/ArpPacket.java",
+ "device/com/android/net/module/util/DeviceConfigUtils.java",
+ "device/com/android/net/module/util/DomainUtils.java",
+ "device/com/android/net/module/util/FdEventsReader.java",
+ "device/com/android/net/module/util/NetworkMonitorUtils.java",
+ "device/com/android/net/module/util/PacketReader.java",
+ "device/com/android/net/module/util/SharedLog.java",
+ "device/com/android/net/module/util/SocketUtils.java",
+ "device/com/android/net/module/util/FeatureVersions.java",
+ "device/com/android/net/module/util/HandlerUtils.java",
+ // This library is used by system modules, for which the system health impact of Kotlin
+ // has not yet been evaluated. Annotations may need jarjar'ing.
+ // "src_devicecommon/**/*.kt",
+ ],
+ sdk_version: "module_current",
+ min_sdk_version: "30",
+ target_sdk_version: "30",
+ apex_available: [
+ "//apex_available:anyapex",
+ "//apex_available:platform",
+ ],
+ visibility: [
"//frameworks/base/packages/Tethering",
"//packages/modules/Connectivity:__subpackages__",
"//packages/modules/Connectivity/framework:__subpackages__",
@@ -65,26 +66,26 @@
"//frameworks/opt/net/telephony",
"//packages/modules/NetworkStack:__subpackages__",
"//packages/modules/CaptivePortalLogin",
- ],
- static_libs: [
- "net-utils-framework-common",
- ],
- libs: [
- "androidx.annotation_annotation",
- "framework-annotations-lib",
- "framework-configinfrastructure",
- "framework-connectivity.stubs.module_lib",
- ],
- lint: {
- strict_updatability_linting: true,
- error_checks: ["NewApi"],
- },
+ ],
+ static_libs: [
+ "net-utils-framework-common",
+ ],
+ libs: [
+ "androidx.annotation_annotation",
+ "framework-annotations-lib",
+ "framework-configinfrastructure",
+ "framework-connectivity.stubs.module_lib",
+ ],
+ lint: {
+ strict_updatability_linting: true,
+ error_checks: ["NewApi"],
+ },
}
java_defaults {
name: "lib_mockito_extended",
static_libs: [
- "mockito-target-extended-minus-junit4"
+ "mockito-target-extended-minus-junit4",
],
jni_libs: [
"libdexmakerjvmtiagent",
@@ -95,12 +96,12 @@
java_library {
name: "net-utils-dnspacket-common",
srcs: [
- "framework/**/DnsPacket.java",
- "framework/**/DnsPacketUtils.java",
- "framework/**/DnsSvcbPacket.java",
- "framework/**/DnsSvcbRecord.java",
- "framework/**/HexDump.java",
- "framework/**/NetworkStackConstants.java",
+ "framework/**/DnsPacket.java",
+ "framework/**/DnsPacketUtils.java",
+ "framework/**/DnsSvcbPacket.java",
+ "framework/**/DnsSvcbRecord.java",
+ "framework/**/HexDump.java",
+ "framework/**/NetworkStackConstants.java",
],
sdk_version: "module_current",
visibility: [
@@ -247,7 +248,7 @@
"//apex_available:platform",
],
lint: {
- strict_updatability_linting: true,
+ baseline_filename: "lint-baseline.xml",
error_checks: ["NewApi"],
},
}
@@ -464,10 +465,10 @@
filegroup {
name: "net-utils-wifi-service-common-srcs",
srcs: [
- "device/android/net/NetworkFactory.java",
- "device/android/net/NetworkFactoryImpl.java",
- "device/android/net/NetworkFactoryLegacyImpl.java",
- "device/android/net/NetworkFactoryShim.java",
+ "device/android/net/NetworkFactory.java",
+ "device/android/net/NetworkFactoryImpl.java",
+ "device/android/net/NetworkFactoryLegacyImpl.java",
+ "device/android/net/NetworkFactoryShim.java",
],
visibility: [
"//frameworks/opt/net/wifi/service",
diff --git a/staticlibs/client-libs/Android.bp b/staticlibs/client-libs/Android.bp
index c938dd6..f665584 100644
--- a/staticlibs/client-libs/Android.bp
+++ b/staticlibs/client-libs/Android.bp
@@ -1,4 +1,5 @@
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -10,17 +11,17 @@
apex_available: [
"//apex_available:platform",
"com.android.tethering",
- "com.android.wifi"
+ "com.android.wifi",
],
visibility: [
"//packages/modules/Connectivity:__subpackages__",
"//frameworks/base/services:__subpackages__",
"//frameworks/base/packages:__subpackages__",
- "//packages/modules/Wifi/service:__subpackages__"
+ "//packages/modules/Wifi/service:__subpackages__",
],
libs: ["androidx.annotation_annotation"],
static_libs: [
"netd_aidl_interface-lateststable-java",
- "netd_event_listener_interface-lateststable-java"
- ]
+ "netd_event_listener_interface-lateststable-java",
+ ],
}
diff --git a/staticlibs/client-libs/tests/unit/Android.bp b/staticlibs/client-libs/tests/unit/Android.bp
index 03e3e70..7aafd69 100644
--- a/staticlibs/client-libs/tests/unit/Android.bp
+++ b/staticlibs/client-libs/tests/unit/Android.bp
@@ -1,4 +1,5 @@
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -26,7 +27,7 @@
"//packages/modules/Connectivity/tests:__subpackages__",
"//packages/modules/Connectivity/Tethering/tests:__subpackages__",
"//packages/modules/NetworkStack/tests/integration",
- ]
+ ],
}
android_test {
diff --git a/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java b/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java
index 42f26f4..5b7cbb8 100644
--- a/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java
+++ b/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java
@@ -64,9 +64,6 @@
@VisibleForTesting
public static final long DEFAULT_PACKAGE_VERSION = 1000;
- private static final String CORE_NETWORKING_TRUNK_STABLE_NAMESPACE = "android_core_networking";
- private static final String CORE_NETWORKING_TRUNK_STABLE_FLAG_PACKAGE = "com.android.net.flags";
-
@VisibleForTesting
public static void resetPackageVersionCacheForTest() {
sPackageVersion = -1;
@@ -409,31 +406,4 @@
return pkgs.get(0).activityInfo.applicationInfo.packageName;
}
-
- /**
- * Check whether one specific trunk stable flag in android_core_networking namespace is enabled.
- * This method reads trunk stable feature flag value from DeviceConfig directly since
- * java_aconfig_library soong module is not available in the mainline branch.
- * After the mainline branch support the aconfig soong module, this function must be removed and
- * java_aconfig_library must be used instead to check if the feature is enabled.
- *
- * @param flagName The name of the trunk stable flag
- * @return true if this feature is enabled, or false if disabled.
- */
- public static boolean isTrunkStableFeatureEnabled(final String flagName) {
- return isTrunkStableFeatureEnabled(
- CORE_NETWORKING_TRUNK_STABLE_NAMESPACE,
- CORE_NETWORKING_TRUNK_STABLE_FLAG_PACKAGE,
- flagName
- );
- }
-
- private static boolean isTrunkStableFeatureEnabled(final String namespace,
- final String packageName, final String flagName) {
- return DeviceConfig.getBoolean(
- namespace,
- packageName + "." + flagName,
- false /* defaultValue */
- );
- }
}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/InetDiagMessage.java b/staticlibs/device/com/android/net/module/util/netlink/InetDiagMessage.java
index b980c7d..fecaa09 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/InetDiagMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/InetDiagMessage.java
@@ -31,6 +31,7 @@
import static com.android.net.module.util.netlink.NetlinkConstants.stringForProtocol;
import static com.android.net.module.util.netlink.NetlinkUtils.DEFAULT_RECV_BUFSIZE;
import static com.android.net.module.util.netlink.NetlinkUtils.IO_TIMEOUT_MS;
+import static com.android.net.module.util.netlink.NetlinkUtils.SOCKET_RECV_BUFSIZE;
import static com.android.net.module.util.netlink.NetlinkUtils.TCP_ALIVE_STATE_FILTER;
import static com.android.net.module.util.netlink.NetlinkUtils.connectToKernel;
import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_DUMP;
@@ -281,7 +282,7 @@
int uid = INVALID_UID;
FileDescriptor fd = null;
try {
- fd = NetlinkUtils.netlinkSocketForProto(NETLINK_INET_DIAG);
+ fd = NetlinkUtils.netlinkSocketForProto(NETLINK_INET_DIAG, SOCKET_RECV_BUFSIZE);
connectToKernel(fd);
uid = lookupUid(protocol, local, remote, fd);
} catch (ErrnoException | SocketException | IllegalArgumentException
diff --git a/staticlibs/device/com/android/net/module/util/netlink/NduseroptMessage.java b/staticlibs/device/com/android/net/module/util/netlink/NduseroptMessage.java
index bdf574d..2e9a99b 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/NduseroptMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/NduseroptMessage.java
@@ -20,6 +20,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import java.net.Inet6Address;
import java.net.InetAddress;
@@ -63,6 +64,20 @@
/** The IP address that sent the packet containing the option. */
public final InetAddress srcaddr;
+ @VisibleForTesting
+ public NduseroptMessage(@NonNull final StructNlMsgHdr header, byte family, int optslen,
+ int ifindex, byte icmptype, byte icmpcode, @NonNull final NdOption option,
+ final InetAddress srcaddr) {
+ super(header);
+ this.family = family;
+ this.opts_len = optslen;
+ this.ifindex = ifindex;
+ this.icmp_type = icmptype;
+ this.icmp_code = icmpcode;
+ this.option = option;
+ this.srcaddr = srcaddr;
+ }
+
NduseroptMessage(@NonNull StructNlMsgHdr header, @NonNull ByteBuffer buf)
throws UnknownHostException {
super(header);
diff --git a/staticlibs/device/com/android/net/module/util/netlink/NetlinkUtils.java b/staticlibs/device/com/android/net/module/util/netlink/NetlinkUtils.java
index 7c2be2c..541a375 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/NetlinkUtils.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/NetlinkUtils.java
@@ -29,6 +29,7 @@
import static android.system.OsConstants.SO_RCVBUF;
import static android.system.OsConstants.SO_RCVTIMEO;
import static android.system.OsConstants.SO_SNDTIMEO;
+
import static com.android.net.module.util.netlink.NetlinkConstants.hexify;
import static com.android.net.module.util.netlink.NetlinkConstants.NLMSG_DONE;
import static com.android.net.module.util.netlink.NetlinkConstants.RTNL_FAMILY_IP6MR;
@@ -49,6 +50,7 @@
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.Inet6Address;
+import java.net.InetAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -56,7 +58,6 @@
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
-import java.util.stream.Collectors;
/**
* Utilities for netlink related class that may not be able to fit into a specific class.
@@ -85,6 +86,7 @@
public static final int DEFAULT_RECV_BUFSIZE = 8 * 1024;
public static final int SOCKET_RECV_BUFSIZE = 64 * 1024;
+ public static final int SOCKET_DUMP_RECV_BUFSIZE = 128 * 1024;
/**
* Return whether the input ByteBuffer contains enough remaining bytes for
@@ -159,7 +161,7 @@
*/
public static void sendOneShotKernelMessage(int nlProto, byte[] msg) throws ErrnoException {
final String errPrefix = "Error in NetlinkSocket.sendOneShotKernelMessage";
- final FileDescriptor fd = netlinkSocketForProto(nlProto);
+ final FileDescriptor fd = netlinkSocketForProto(nlProto, SOCKET_RECV_BUFSIZE);
try {
connectToKernel(fd);
@@ -177,19 +179,19 @@
}
/**
- * Send an RTM_NEWADDR message to kernel to add or update an IPv6 address.
+ * Send an RTM_NEWADDR message to kernel to add or update an IP address.
*
* @param ifIndex interface index.
- * @param ip IPv6 address to be added.
- * @param prefixlen IPv6 address prefix length.
- * @param flags IPv6 address flags.
- * @param scope IPv6 address scope.
- * @param preferred The preferred lifetime of IPv6 address.
- * @param valid The valid lifetime of IPv6 address.
+ * @param ip IP address to be added.
+ * @param prefixlen IP address prefix length.
+ * @param flags IP address flags.
+ * @param scope IP address scope.
+ * @param preferred The preferred lifetime of IP address.
+ * @param valid The valid lifetime of IP address.
*/
- public static boolean sendRtmNewAddressRequest(int ifIndex, @NonNull final Inet6Address ip,
+ public static boolean sendRtmNewAddressRequest(int ifIndex, @NonNull final InetAddress ip,
short prefixlen, int flags, byte scope, long preferred, long valid) {
- Objects.requireNonNull(ip, "IPv6 address to be added should not be null.");
+ Objects.requireNonNull(ip, "IP address to be added should not be null.");
final byte[] msg = RtNetlinkAddressMessage.newRtmNewAddressMessage(1 /* seqNo*/, ip,
prefixlen, flags, scope, ifIndex, preferred, valid);
try {
@@ -223,22 +225,41 @@
}
/**
- * Create netlink socket with the given netlink protocol type.
+ * Create netlink socket with the given netlink protocol type and buffersize.
+ *
+ * @param nlProto the netlink protocol
+ * @param bufferSize the receive buffer size to set when the value is not 0
*
* @return fd the fileDescriptor of the socket.
* @throws ErrnoException if the FileDescriptor not connect to be created successfully
*/
- public static FileDescriptor netlinkSocketForProto(int nlProto) throws ErrnoException {
- final FileDescriptor fd = Os.socket(AF_NETLINK, SOCK_DGRAM, nlProto);
- Os.setsockoptInt(fd, SOL_SOCKET, SO_RCVBUF, SOCKET_RECV_BUFSIZE);
+ public static FileDescriptor netlinkSocketForProto(int nlProto, int bufferSize)
+ throws ErrnoException {
+ final FileDescriptor fd = Os.socket(AF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, nlProto);
+ if (bufferSize > 0) {
+ Os.setsockoptInt(fd, SOL_SOCKET, SO_RCVBUF, bufferSize);
+ }
return fd;
}
/**
+ * Create netlink socket with the given netlink protocol type. Receive buffer size is not set.
+ *
+ * @param nlProto the netlink protocol
+ *
+ * @return fd the fileDescriptor of the socket.
+ * @throws ErrnoException if the FileDescriptor not connect to be created successfully
+ */
+ public static FileDescriptor netlinkSocketForProto(int nlProto)
+ throws ErrnoException {
+ return netlinkSocketForProto(nlProto, 0);
+ }
+
+ /**
* Construct a netlink inet_diag socket.
*/
public static FileDescriptor createNetLinkInetDiagSocket() throws ErrnoException {
- return Os.socket(AF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_INET_DIAG);
+ return netlinkSocketForProto(NETLINK_INET_DIAG);
}
/**
@@ -373,7 +394,7 @@
Consumer<T> func)
throws SocketException, InterruptedIOException, ErrnoException {
// Create socket
- final FileDescriptor fd = netlinkSocketForProto(nlFamily);
+ final FileDescriptor fd = netlinkSocketForProto(nlFamily, SOCKET_DUMP_RECV_BUFSIZE);
try {
getAndProcessNetlinkDumpMessagesWithFd(fd, dumpRequestMessage, nlFamily,
msgClass, func);
diff --git a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkAddressMessage.java b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkAddressMessage.java
index cbe0ab0..4846df7 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkAddressMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkAddressMessage.java
@@ -16,6 +16,7 @@
package com.android.net.module.util.netlink;
+import static com.android.net.module.util.Inet4AddressUtils.getBroadcastAddress;
import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_ACK;
import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REPLACE;
import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST;
@@ -28,6 +29,7 @@
import com.android.net.module.util.HexDump;
+import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.nio.ByteBuffer;
@@ -48,6 +50,8 @@
*/
public class RtNetlinkAddressMessage extends NetlinkMessage {
public static final short IFA_ADDRESS = 1;
+ public static final short IFA_LOCAL = 2;
+ public static final short IFA_BROADCAST = 4;
public static final short IFA_CACHEINFO = 6;
public static final short IFA_FLAGS = 8;
@@ -71,6 +75,7 @@
mIfacacheInfo = structIfacacheInfo;
mFlags = flags;
}
+
private RtNetlinkAddressMessage(@NonNull StructNlMsgHdr header) {
this(header, null, null, null, 0);
}
@@ -158,6 +163,24 @@
// still be packed to ByteBuffer even if the flag is 0.
final StructNlAttr flags = new StructNlAttr(IFA_FLAGS, mFlags);
flags.pack(byteBuffer);
+
+ // Add the required IFA_LOCAL and IFA_BROADCAST attributes for IPv4 addresses. The IFA_LOCAL
+ // attribute represents the local address, which is equivalent to IFA_ADDRESS on a normally
+ // configured broadcast interface, however, for PPP interfaces, IFA_ADDRESS indicates the
+ // destination address and the local address is provided in the IFA_LOCAL attribute. If the
+ // IFA_LOCAL attribute is not present in the RTM_NEWADDR message, the kernel replies with an
+ // error netlink message with invalid parameters. IFA_BROADCAST is also required, otherwise
+ // the broadcast on the interface is 0.0.0.0. See include/uapi/linux/if_addr.h for details.
+ // For IPv6 addresses, the IFA_ADDRESS attribute applies and introduces no ambiguity.
+ if (mIpAddress instanceof Inet4Address) {
+ final StructNlAttr localAddress = new StructNlAttr(IFA_LOCAL, mIpAddress);
+ localAddress.pack(byteBuffer);
+
+ final Inet4Address broadcast =
+ getBroadcastAddress((Inet4Address) mIpAddress, mIfaddrmsg.prefixLen);
+ final StructNlAttr broadcastAddress = new StructNlAttr(IFA_BROADCAST, broadcast);
+ broadcastAddress.pack(byteBuffer);
+ }
}
/**
@@ -184,7 +207,7 @@
0 /* tstamp */);
msg.mFlags = flags;
- final byte[] bytes = new byte[msg.getRequiredSpace()];
+ final byte[] bytes = new byte[msg.getRequiredSpace(family)];
nlmsghdr.nlmsg_len = bytes.length;
final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
byteBuffer.order(ByteOrder.nativeOrder());
@@ -237,7 +260,7 @@
// RtNetlinkAddressMessage, e.g. RTM_DELADDR sent from user space to kernel to delete an
// IP address only requires IFA_ADDRESS attribute. The caller should check if these attributes
// are necessary to carry when constructing a RtNetlinkAddressMessage.
- private int getRequiredSpace() {
+ private int getRequiredSpace(int family) {
int spaceRequired = StructNlMsgHdr.STRUCT_SIZE + StructIfaddrMsg.STRUCT_SIZE;
// IFA_ADDRESS attr
spaceRequired += NetlinkConstants.alignedLengthOf(
@@ -247,6 +270,14 @@
StructNlAttr.NLA_HEADERLEN + StructIfacacheInfo.STRUCT_SIZE);
// IFA_FLAGS "u32" attr
spaceRequired += StructNlAttr.NLA_HEADERLEN + 4;
+ if (family == OsConstants.AF_INET) {
+ // IFA_LOCAL attr
+ spaceRequired += NetlinkConstants.alignedLengthOf(
+ StructNlAttr.NLA_HEADERLEN + mIpAddress.getAddress().length);
+ // IFA_BROADCAST attr
+ spaceRequired += NetlinkConstants.alignedLengthOf(
+ StructNlAttr.NLA_HEADERLEN + mIpAddress.getAddress().length);
+ }
return spaceRequired;
}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkRouteMessage.java b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkRouteMessage.java
index b2b1e93..545afea 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkRouteMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkRouteMessage.java
@@ -19,10 +19,8 @@
import static android.system.OsConstants.AF_INET;
import static android.system.OsConstants.AF_INET6;
-import static android.system.OsConstants.NETLINK_ROUTE;
import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_ANY;
import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ANY;
-import static com.android.net.module.util.netlink.NetlinkConstants.hexify;
import static com.android.net.module.util.netlink.NetlinkConstants.RTNL_FAMILY_IP6MR;
import android.annotation.SuppressLint;
@@ -38,9 +36,6 @@
import java.net.Inet6Address;
import java.net.InetAddress;
import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.IntBuffer;
-import java.util.Arrays;
/**
* A NetlinkMessage subclass for rtnetlink route messages.
@@ -86,18 +81,27 @@
private long mSinceLastUseMillis; // Milliseconds since the route was used,
// for resolved multicast routes
- public RtNetlinkRouteMessage(StructNlMsgHdr header, StructRtMsg rtMsg) {
+
+ @VisibleForTesting
+ public RtNetlinkRouteMessage(final StructNlMsgHdr header, final StructRtMsg rtMsg,
+ final IpPrefix source, final IpPrefix destination, final InetAddress gateway,
+ int iif, int oif, final StructRtaCacheInfo cacheInfo) {
super(header);
mRtmsg = rtMsg;
- mSource = null;
- mDestination = null;
- mGateway = null;
- mIifIndex = 0;
- mOifIndex = 0;
- mRtaCacheInfo = null;
+ mSource = source;
+ mDestination = destination;
+ mGateway = gateway;
+ mIifIndex = iif;
+ mOifIndex = oif;
+ mRtaCacheInfo = cacheInfo;
mSinceLastUseMillis = -1;
}
+ public RtNetlinkRouteMessage(StructNlMsgHdr header, StructRtMsg rtMsg) {
+ this(header, rtMsg, null /* source */, null /* destination */, null /* gateway */,
+ 0 /* iif */, 0 /* oif */, null /* cacheInfo */);
+ }
+
/**
* Returns the rtnetlink family.
*/
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructRtMsg.java b/staticlibs/device/com/android/net/module/util/netlink/StructRtMsg.java
index 3cd7292..6d9318c 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/StructRtMsg.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructRtMsg.java
@@ -18,6 +18,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import com.android.net.module.util.Struct;
import com.android.net.module.util.Struct.Field;
@@ -57,8 +58,9 @@
@Field(order = 8, type = Type.U32)
public final long flags;
- StructRtMsg(short family, short dstLen, short srcLen, short tos, short table, short protocol,
- short scope, short type, long flags) {
+ @VisibleForTesting
+ public StructRtMsg(short family, short dstLen, short srcLen, short tos, short table,
+ short protocol, short scope, short type, long flags) {
this.family = family;
this.dstLen = dstLen;
this.srcLen = srcLen;
diff --git a/staticlibs/framework/com/android/net/module/util/PermissionUtils.java b/staticlibs/framework/com/android/net/module/util/PermissionUtils.java
index f167d3d..0d7d96f 100644
--- a/staticlibs/framework/com/android/net/module/util/PermissionUtils.java
+++ b/staticlibs/framework/com/android/net/module/util/PermissionUtils.java
@@ -23,6 +23,7 @@
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
+import android.annotation.CheckResult;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -45,8 +46,9 @@
/**
* Return true if the context has one of given permission.
*/
- public static boolean checkAnyPermissionOf(@NonNull Context context,
- @NonNull String... permissions) {
+ @CheckResult
+ public static boolean hasAnyPermissionOf(@NonNull Context context,
+ @NonNull String... permissions) {
for (String permission : permissions) {
if (context.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) {
return true;
@@ -56,11 +58,12 @@
}
/**
- * Return true if the context has one of give permission that is allowed
+ * Return true if the context has one of given permission that is allowed
* for a particular process and user ID running in the system.
*/
- public static boolean checkAnyPermissionOf(@NonNull Context context,
- int pid, int uid, @NonNull String... permissions) {
+ @CheckResult
+ public static boolean hasAnyPermissionOf(@NonNull Context context,
+ int pid, int uid, @NonNull String... permissions) {
for (String permission : permissions) {
if (context.checkPermission(permission, pid, uid) == PERMISSION_GRANTED) {
return true;
@@ -74,7 +77,7 @@
*/
public static void enforceAnyPermissionOf(@NonNull Context context,
@NonNull String... permissions) {
- if (!checkAnyPermissionOf(context, permissions)) {
+ if (!hasAnyPermissionOf(context, permissions)) {
throw new SecurityException("Requires one of the following permissions: "
+ String.join(", ", permissions) + ".");
}
@@ -133,7 +136,8 @@
/**
* Return true if the context has DUMP permission.
*/
- public static boolean checkDumpPermission(Context context, String tag, PrintWriter pw) {
+ @CheckResult
+ public static boolean hasDumpPermission(Context context, String tag, PrintWriter pw) {
if (context.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
!= PERMISSION_GRANTED) {
pw.println("Permission Denial: can't dump " + tag + " from from pid="
diff --git a/staticlibs/lint-baseline.xml b/staticlibs/lint-baseline.xml
new file mode 100644
index 0000000..2ee3a43
--- /dev/null
+++ b/staticlibs/lint-baseline.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.4.0-alpha04" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha04">
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `makeNetlinkSocketAddress`"
+ errorLine1=" Os.bind(fd, makeNetlinkSocketAddress(0, mBindGroups));"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/staticlibs/device/com/android/net/module/util/ip/NetlinkMonitor.java"
+ line="111"
+ column="25"/>
+ </issue>
+
+</issues>
diff --git a/staticlibs/native/bpf_headers/Android.bp b/staticlibs/native/bpf_headers/Android.bp
index 41184ea..d55584a 100644
--- a/staticlibs/native/bpf_headers/Android.bp
+++ b/staticlibs/native/bpf_headers/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/staticlibs/native/bpf_syscall_wrappers/Android.bp b/staticlibs/native/bpf_syscall_wrappers/Android.bp
index b3efc21..1e0cb22 100644
--- a/staticlibs/native/bpf_syscall_wrappers/Android.bp
+++ b/staticlibs/native/bpf_syscall_wrappers/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/staticlibs/native/bpfmapjni/Android.bp b/staticlibs/native/bpfmapjni/Android.bp
index 8babcce..7e6b4ec 100644
--- a/staticlibs/native/bpfmapjni/Android.bp
+++ b/staticlibs/native/bpfmapjni/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/staticlibs/native/bpfutiljni/Android.bp b/staticlibs/native/bpfutiljni/Android.bp
index 39a2795..1ef01a6 100644
--- a/staticlibs/native/bpfutiljni/Android.bp
+++ b/staticlibs/native/bpfutiljni/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/staticlibs/native/ip_checksum/Android.bp b/staticlibs/native/ip_checksum/Android.bp
index 9878d73..e2e118e 100644
--- a/staticlibs/native/ip_checksum/Android.bp
+++ b/staticlibs/native/ip_checksum/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/staticlibs/native/netjniutils/Android.bp b/staticlibs/native/netjniutils/Android.bp
index ca3bbbc..4cab459 100644
--- a/staticlibs/native/netjniutils/Android.bp
+++ b/staticlibs/native/netjniutils/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/staticlibs/native/nettestutils/Android.bp b/staticlibs/native/nettestutils/Android.bp
index df3bb42..ef87f04 100644
--- a/staticlibs/native/nettestutils/Android.bp
+++ b/staticlibs/native/nettestutils/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/staticlibs/native/tcutils/Android.bp b/staticlibs/native/tcutils/Android.bp
index 9a38745..926590d 100644
--- a/staticlibs/native/tcutils/Android.bp
+++ b/staticlibs/native/tcutils/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/staticlibs/netd/Android.bp b/staticlibs/netd/Android.bp
index 2b7e620..59ef20d 100644
--- a/staticlibs/netd/Android.bp
+++ b/staticlibs/netd/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/staticlibs/netd/libnetdutils/Android.bp b/staticlibs/netd/libnetdutils/Android.bp
index fdb9380..2ae5911 100644
--- a/staticlibs/netd/libnetdutils/Android.bp
+++ b/staticlibs/netd/libnetdutils/Android.bp
@@ -1,4 +1,5 @@
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -22,7 +23,10 @@
"Utils.cpp",
],
defaults: ["netd_defaults"],
- cflags: ["-Wall", "-Werror"],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
shared_libs: [
"libbase",
"liblog",
diff --git a/staticlibs/tests/unit/Android.bp b/staticlibs/tests/unit/Android.bp
index 0dfca57..4c226cc 100644
--- a/staticlibs/tests/unit/Android.bp
+++ b/staticlibs/tests/unit/Android.bp
@@ -3,12 +3,16 @@
//########################################################################
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
android_library {
name: "NetworkStaticLibTestsLib",
- srcs: ["src/**/*.java","src/**/*.kt"],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
min_sdk_version: "30",
defaults: ["framework-connectivity-test-defaults"],
static_libs: [
@@ -34,8 +38,7 @@
"//packages/modules/NetworkStack/tests/integration",
],
lint: {
- strict_updatability_linting: true,
- test: true
+ test: true,
},
}
@@ -52,5 +55,4 @@
],
jarjar_rules: "jarjar-rules.txt",
test_suites: ["device-tests"],
- lint: { strict_updatability_linting: true },
}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java
index 06b3e2f..f32337d 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java
@@ -71,10 +71,6 @@
public class DeviceConfigUtilsTest {
private static final String TEST_NAME_SPACE = "connectivity";
private static final String TEST_EXPERIMENT_FLAG = "experiment_flag";
- private static final String CORE_NETWORKING_TRUNK_STABLE_NAMESPACE = "android_core_networking";
- private static final String TEST_TRUNK_STABLE_FLAG = "trunk_stable_feature";
- private static final String TEST_CORE_NETWORKING_TRUNK_STABLE_FLAG_PROPERTY =
- "com.android.net.flags.trunk_stable_feature";
private static final int TEST_FLAG_VALUE = 28;
private static final String TEST_FLAG_VALUE_STRING = "28";
private static final int TEST_DEFAULT_FLAG_VALUE = 0;
@@ -507,25 +503,4 @@
verify(mContext, never()).getPackageName();
verify(mPm, never()).getPackageInfo(anyString(), anyInt());
}
-
- @Test
- public void testIsCoreNetworkingTrunkStableFeatureEnabled() {
- doReturn(null).when(() -> DeviceConfig.getProperty(
- CORE_NETWORKING_TRUNK_STABLE_NAMESPACE,
- TEST_CORE_NETWORKING_TRUNK_STABLE_FLAG_PROPERTY));
- assertFalse(DeviceConfigUtils.isTrunkStableFeatureEnabled(
- TEST_TRUNK_STABLE_FLAG));
-
- doReturn("false").when(() -> DeviceConfig.getProperty(
- CORE_NETWORKING_TRUNK_STABLE_NAMESPACE,
- TEST_CORE_NETWORKING_TRUNK_STABLE_FLAG_PROPERTY));
- assertFalse(DeviceConfigUtils.isTrunkStableFeatureEnabled(
- TEST_TRUNK_STABLE_FLAG));
-
- doReturn("true").when(() -> DeviceConfig.getProperty(
- CORE_NETWORKING_TRUNK_STABLE_NAMESPACE,
- TEST_CORE_NETWORKING_TRUNK_STABLE_FLAG_PROPERTY));
- assertTrue(DeviceConfigUtils.isTrunkStableFeatureEnabled(
- TEST_TRUNK_STABLE_FLAG));
- }
}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/PermissionUtilsTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/PermissionUtilsTest.kt
index d5b43fb..8586e82 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/PermissionUtilsTest.kt
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/PermissionUtilsTest.kt
@@ -25,12 +25,12 @@
import android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
-import com.android.net.module.util.PermissionUtils.checkAnyPermissionOf
import com.android.net.module.util.PermissionUtils.enforceAnyPermissionOf
import com.android.net.module.util.PermissionUtils.enforceNetworkStackPermission
import com.android.net.module.util.PermissionUtils.enforceNetworkStackPermissionOr
import com.android.net.module.util.PermissionUtils.enforcePackageNameMatchesUid
import com.android.net.module.util.PermissionUtils.enforceSystemFeature
+import com.android.net.module.util.PermissionUtils.hasAnyPermissionOf
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRunner
import kotlin.test.assertEquals
@@ -78,18 +78,18 @@
.checkCallingOrSelfPermission(TEST_PERMISSION1)
doReturn(PERMISSION_DENIED).`when`(mockContext)
.checkCallingOrSelfPermission(TEST_PERMISSION2)
- assertTrue(checkAnyPermissionOf(mockContext, TEST_PERMISSION1, TEST_PERMISSION2))
+ assertTrue(hasAnyPermissionOf(mockContext, TEST_PERMISSION1, TEST_PERMISSION2))
enforceAnyPermissionOf(mockContext, TEST_PERMISSION1, TEST_PERMISSION2)
doReturn(PERMISSION_DENIED).`when`(mockContext)
.checkCallingOrSelfPermission(TEST_PERMISSION1)
doReturn(PERMISSION_GRANTED).`when`(mockContext)
.checkCallingOrSelfPermission(TEST_PERMISSION2)
- assertTrue(checkAnyPermissionOf(mockContext, TEST_PERMISSION1, TEST_PERMISSION2))
+ assertTrue(hasAnyPermissionOf(mockContext, TEST_PERMISSION1, TEST_PERMISSION2))
enforceAnyPermissionOf(mockContext, TEST_PERMISSION1, TEST_PERMISSION2)
doReturn(PERMISSION_DENIED).`when`(mockContext).checkCallingOrSelfPermission(any())
- assertFalse(checkAnyPermissionOf(mockContext, TEST_PERMISSION1, TEST_PERMISSION2))
+ assertFalse(hasAnyPermissionOf(mockContext, TEST_PERMISSION1, TEST_PERMISSION2))
assertFailsWith<SecurityException>("Expect fail but permission granted.") {
enforceAnyPermissionOf(mockContext, TEST_PERMISSION1, TEST_PERMISSION2)
}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java
index 0958f11..f64adb8 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java
@@ -21,6 +21,8 @@
import static android.system.OsConstants.AF_UNSPEC;
import static android.system.OsConstants.EACCES;
import static android.system.OsConstants.NETLINK_ROUTE;
+import static android.system.OsConstants.SOL_SOCKET;
+import static android.system.OsConstants.SO_RCVBUF;
import static com.android.net.module.util.netlink.NetlinkConstants.RTNL_FAMILY_IP6MR;
import static com.android.net.module.util.netlink.NetlinkUtils.DEFAULT_RECV_BUFSIZE;
import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_DUMP;
@@ -33,6 +35,8 @@
import static org.junit.Assume.assumeFalse;
import android.content.Context;
+import android.net.util.SocketUtils;
+import android.os.Build;
import android.system.ErrnoException;
import android.system.NetlinkSocketAddress;
import android.system.Os;
@@ -43,6 +47,7 @@
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.Struct;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import libcore.io.IoUtils;
@@ -204,4 +209,23 @@
assertNotNull("Route doesn't contain destination: " + route, route.getDestination());
}
}
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.R) // getsockoptInt requires > R
+ public void testNetlinkSocketForProto_defaultBufferSize() throws Exception {
+ final FileDescriptor fd = NetlinkUtils.netlinkSocketForProto(NETLINK_ROUTE);
+ final int bufferSize = Os.getsockoptInt(fd, SOL_SOCKET, SO_RCVBUF) / 2;
+
+ assertTrue("bufferSize: " + bufferSize, bufferSize > 0); // whatever the default value is
+ SocketUtils.closeSocket(fd);
+ }
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.R) // getsockoptInt requires > R
+ public void testNetlinkSocketForProto_setBufferSize() throws Exception {
+ final FileDescriptor fd = NetlinkUtils.netlinkSocketForProto(NETLINK_ROUTE,
+ 8000);
+ final int bufferSize = Os.getsockoptInt(fd, SOL_SOCKET, SO_RCVBUF) / 2;
+
+ assertEquals(8000, bufferSize);
+ SocketUtils.closeSocket(fd);
+ }
}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkAddressMessageTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkAddressMessageTest.java
index 01126d2..1d08525 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkAddressMessageTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkAddressMessageTest.java
@@ -42,6 +42,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.net.Inet4Address;
import java.net.Inet6Address;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -179,6 +180,57 @@
}
@Test
+ public void testCreateRtmNewAddressMessage_IPv4Address() {
+ // Hexadecimal representation of our created packet.
+ final String expectedNewAddressHex =
+ // struct nlmsghdr
+ "4c000000" // length = 76
+ + "1400" // type = 20 (RTM_NEWADDR)
+ + "0501" // flags = NLM_F_ACK | NLM_F_REQUEST | NLM_F_REPLACE
+ + "01000000" // seqno = 1
+ + "00000000" // pid = 0 (send to kernel)
+ // struct IfaddrMsg
+ + "02" // family = inet
+ + "18" // prefix len = 24
+ + "00" // flags = 0
+ + "00" // scope = RT_SCOPE_UNIVERSE
+ + "14000000" // ifindex = 20
+ // struct nlattr: IFA_ADDRESS
+ + "0800" // len
+ + "0100" // type
+ + "C0A80491" // IPv4 address = 192.168.4.145
+ // struct nlattr: IFA_CACHEINFO
+ + "1400" // len
+ + "0600" // type
+ + "C0A80000" // preferred = 43200s
+ + "C0A80000" // valid = 43200s
+ + "00000000" // cstamp
+ + "00000000" // tstamp
+ // struct nlattr: IFA_FLAGS
+ + "0800" // len
+ + "0800" // type
+ + "00000000" // flags = 0
+ // struct nlattr: IFA_LOCAL
+ + "0800" // len
+ + "0200" // type
+ + "C0A80491" // local address = 192.168.4.145
+ // struct nlattr: IFA_BROADCAST
+ + "0800" // len
+ + "0400" // type
+ + "C0A804FF"; // broadcast address = 192.168.4.255
+ final byte[] expectedNewAddress =
+ HexEncoding.decode(expectedNewAddressHex.toCharArray(), false);
+
+ final Inet4Address ipAddress =
+ (Inet4Address) InetAddresses.parseNumericAddress("192.168.4.145");
+ final byte[] bytes = RtNetlinkAddressMessage.newRtmNewAddressMessage(1 /* seqno */,
+ ipAddress, (short) 24 /* prefix len */, 0 /* flags */,
+ (byte) RT_SCOPE_UNIVERSE /* scope */, 20 /* ifindex */,
+ (long) 0xA8C0 /* preferred */, (long) 0xA8C0 /* valid */);
+ assertArrayEquals(expectedNewAddress, bytes);
+ }
+
+ @Test
public void testCreateRtmDelAddressMessage() {
// Hexadecimal representation of our created packet.
final String expectedDelAddressHex =
diff --git a/staticlibs/testutils/Android.bp b/staticlibs/testutils/Android.bp
index 43853ee..a8e5a69 100644
--- a/staticlibs/testutils/Android.bp
+++ b/staticlibs/testutils/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -24,11 +25,11 @@
],
defaults: [
"framework-connectivity-test-defaults",
- "lib_mockito_extended"
+ "lib_mockito_extended",
],
libs: [
"androidx.annotation_annotation",
- "net-utils-device-common-bpf", // TestBpfMap extends IBpfMap.
+ "net-utils-device-common-bpf", // TestBpfMap extends IBpfMap.
],
static_libs: [
"androidx.test.ext.junit",
@@ -42,7 +43,9 @@
"net-utils-device-common-wear",
"modules-utils-build_system",
],
- lint: { strict_updatability_linting: true },
+ lint: {
+ strict_updatability_linting: true,
+ },
}
java_library {
@@ -72,9 +75,11 @@
"jsr305",
],
static_libs: [
- "kotlin-test"
+ "kotlin-test",
],
- lint: { strict_updatability_linting: true },
+ lint: {
+ strict_updatability_linting: true,
+ },
}
java_test_host {
diff --git a/staticlibs/testutils/app/connectivitychecker/Android.bp b/staticlibs/testutils/app/connectivitychecker/Android.bp
index 049ec9e..5af8c14 100644
--- a/staticlibs/testutils/app/connectivitychecker/Android.bp
+++ b/staticlibs/testutils/app/connectivitychecker/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -30,5 +31,7 @@
"net-tests-utils",
],
host_required: ["net-tests-utils-host-common"],
- lint: { strict_updatability_linting: true },
+ lint: {
+ strict_updatability_linting: true,
+ },
}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/ConnectUtil.kt b/staticlibs/testutils/devicetests/com/android/testutils/ConnectUtil.kt
index b1d64f8..8090d5b 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/ConnectUtil.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/ConnectUtil.kt
@@ -116,7 +116,10 @@
}
}
- private fun connectToWifiConfig(config: WifiConfiguration) {
+ // Suppress warning because WifiManager methods to connect to a config are
+ // documented not to be deprecated for privileged users.
+ @Suppress("DEPRECATION")
+ fun connectToWifiConfig(config: WifiConfiguration) {
repeat(MAX_WIFI_CONNECT_RETRIES) {
val error = runAsShell(permission.NETWORK_SETTINGS) {
val listener = ConnectWifiListener()
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt b/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt
index 10accd4..69fdbf8 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt
@@ -31,6 +31,7 @@
import org.junit.runner.notification.Failure
import org.junit.runner.notification.RunNotifier
import org.junit.runners.Parameterized
+import org.mockito.Mockito
/**
* A runner that can skip tests based on the development SDK as defined in [DevSdkIgnoreRule].
@@ -124,6 +125,9 @@
notifier.fireTestFailure(Failure(leakMonitorDesc,
IllegalStateException("Unexpected thread changes: $threadsDiff")))
}
+ // Clears up internal state of all inline mocks.
+ // TODO: Call clearInlineMocks() at the end of each test.
+ Mockito.framework().clearInlineMocks()
notifier.fireTestFinished(leakMonitorDesc)
}
diff --git a/tests/benchmark/Android.bp b/tests/benchmark/Android.bp
index 6ea5347..7854bb5 100644
--- a/tests/benchmark/Android.bp
+++ b/tests/benchmark/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_fwk_core_networking",
// See: http://go/android-license-faq
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -40,4 +41,3 @@
test_suites: ["device-tests"],
jarjar_rules: ":connectivity-jarjar-rules",
}
-
diff --git a/tests/common/Android.bp b/tests/common/Android.bp
index 7b5c298..6e9d614 100644
--- a/tests/common/Android.bp
+++ b/tests/common/Android.bp
@@ -17,6 +17,7 @@
// Tests in this folder are included both in unit tests and CTS.
// They must be fast and stable, and exercise public or test APIs.
package {
+ default_team: "trendy_team_fwk_core_networking",
// See: http://go/android-license-faq
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -93,7 +94,10 @@
name: "ConnectivityCoverageTests",
// Tethering started on SDK 30
min_sdk_version: "30",
- test_suites: ["general-tests", "mts-tethering"],
+ test_suites: [
+ "general-tests",
+ "mts-tethering",
+ ],
defaults: [
"ConnectivityTestsLatestSdkDefaults",
"framework-connectivity-internal-test-defaults",
@@ -185,7 +189,7 @@
// See SuiteModuleLoader.java.
// TODO: why are the modules separated by + instead of being separate entries in the array?
mainline_presubmit_modules = [
- "CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex",
+ "CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex",
]
cc_defaults {
diff --git a/tests/common/java/android/net/nsd/NsdServiceInfoTest.java b/tests/common/java/android/net/nsd/NsdServiceInfoTest.java
index 79c4980..8e89037 100644
--- a/tests/common/java/android/net/nsd/NsdServiceInfoTest.java
+++ b/tests/common/java/android/net/nsd/NsdServiceInfoTest.java
@@ -119,6 +119,7 @@
fullInfo.setSubtypes(Set.of("_thread", "_matter"));
fullInfo.setPort(4242);
fullInfo.setHostAddresses(List.of(IPV4_ADDRESS));
+ fullInfo.setHostname("home");
fullInfo.setNetwork(new Network(123));
fullInfo.setInterfaceIndex(456);
checkParcelable(fullInfo);
@@ -134,6 +135,7 @@
attributedInfo.setServiceType("_kitten._tcp");
attributedInfo.setPort(4242);
attributedInfo.setHostAddresses(List.of(IPV6_ADDRESS, IPV4_ADDRESS));
+ attributedInfo.setHostname("home");
attributedInfo.setAttribute("color", "pink");
attributedInfo.setAttribute("sound", (new String("にゃあ")).getBytes("UTF-8"));
attributedInfo.setAttribute("adorable", (String) null);
@@ -169,6 +171,7 @@
assertEquals(original.getServiceName(), result.getServiceName());
assertEquals(original.getServiceType(), result.getServiceType());
assertEquals(original.getHost(), result.getHost());
+ assertEquals(original.getHostname(), result.getHostname());
assertTrue(original.getPort() == result.getPort());
assertEquals(original.getNetwork(), result.getNetwork());
assertEquals(original.getInterfaceIndex(), result.getInterfaceIndex());
diff --git a/tests/cts/hostside/Android.bp b/tests/cts/hostside/Android.bp
index a0aafc6..2688fb8 100644
--- a/tests/cts/hostside/Android.bp
+++ b/tests/cts/hostside/Android.bp
@@ -12,17 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-next_app_data = [ ":CtsHostsideNetworkTestsAppNext" ]
+next_app_data = [":CtsHostsideNetworkTestsAppNext"]
// The above line is put in place to prevent any future automerger merge conflict between aosp,
// downstream branches. The CtsHostsideNetworkTestsAppNext target will not exist in
// some downstream branches, but it should exist in aosp and some downstream branches.
-
-
-
-
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -45,7 +42,7 @@
"general-tests",
"mcts-tethering",
"mts-tethering",
- "sts"
+ "sts",
],
data: [
":CtsHostsideNetworkTestsApp",
diff --git a/tests/cts/hostside/aidl/Android.bp b/tests/cts/hostside/aidl/Android.bp
index 2751f6f..18a5897 100644
--- a/tests/cts/hostside/aidl/Android.bp
+++ b/tests/cts/hostside/aidl/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/tests/cts/hostside/app/Android.bp b/tests/cts/hostside/app/Android.bp
index 470bb17..d555491 100644
--- a/tests/cts/hostside/app/Android.bp
+++ b/tests/cts/hostside/app/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/tests/cts/hostside/app2/Android.bp b/tests/cts/hostside/app2/Android.bp
index db92f5c..c526172 100644
--- a/tests/cts/hostside/app2/Android.bp
+++ b/tests/cts/hostside/app2/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/tests/cts/hostside/certs/Android.bp b/tests/cts/hostside/certs/Android.bp
index 60b5476..301973e 100644
--- a/tests/cts/hostside/certs/Android.bp
+++ b/tests/cts/hostside/certs/Android.bp
@@ -1,4 +1,5 @@
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/tests/cts/hostside/networkslicingtestapp/Android.bp b/tests/cts/hostside/networkslicingtestapp/Android.bp
index 2aa3f69..100b6e4 100644
--- a/tests/cts/hostside/networkslicingtestapp/Android.bp
+++ b/tests/cts/hostside/networkslicingtestapp/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -39,8 +40,8 @@
android_test_helper_app {
name: "CtsHostsideNetworkCapTestsAppWithoutProperty",
defaults: [
- "cts_support_defaults",
- "CtsHostsideNetworkCapTestsAppDefaults"
+ "cts_support_defaults",
+ "CtsHostsideNetworkCapTestsAppDefaults",
],
manifest: "AndroidManifestWithoutProperty.xml",
}
@@ -48,8 +49,8 @@
android_test_helper_app {
name: "CtsHostsideNetworkCapTestsAppWithProperty",
defaults: [
- "cts_support_defaults",
- "CtsHostsideNetworkCapTestsAppDefaults"
+ "cts_support_defaults",
+ "CtsHostsideNetworkCapTestsAppDefaults",
],
manifest: "AndroidManifestWithProperty.xml",
}
@@ -57,8 +58,8 @@
android_test_helper_app {
name: "CtsHostsideNetworkCapTestsAppSdk33",
defaults: [
- "cts_support_defaults",
- "CtsHostsideNetworkCapTestsAppDefaults"
+ "cts_support_defaults",
+ "CtsHostsideNetworkCapTestsAppDefaults",
],
target_sdk_version: "33",
manifest: "AndroidManifestWithoutProperty.xml",
diff --git a/tests/cts/multidevices/Android.bp b/tests/cts/multidevices/Android.bp
new file mode 100644
index 0000000..5ac4229
--- /dev/null
+++ b/tests/cts/multidevices/Android.bp
@@ -0,0 +1,42 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+python_test_host {
+ name: "CtsConnectivityMultiDevicesTestCases",
+ main: "connectivity_multi_devices_test.py",
+ srcs: ["connectivity_multi_devices_test.py"],
+ libs: [
+ "mobly",
+ ],
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ test_options: {
+ unit_test: false,
+ },
+ data: [
+ // Package the snippet with the mobly test
+ ":connectivity_multi_devices_snippet",
+ ],
+ version: {
+ py3: {
+ embedded_launcher: true,
+ },
+ },
+}
diff --git a/tests/cts/multidevices/AndroidTest.xml b/tests/cts/multidevices/AndroidTest.xml
new file mode 100644
index 0000000..5312b4d
--- /dev/null
+++ b/tests/cts/multidevices/AndroidTest.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Config for CTS Connectivity multi devices test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="networking" />
+ <option name="config-descriptor:metadata" key="token" value="SIM_CARD" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+ <device name="device1">
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="test-file-name" value="connectivity_multi_devices_snippet.apk" />
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ </target_preparer>
+ </device>
+ <device name="device2">
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="test-file-name" value="connectivity_multi_devices_snippet.apk" />
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ </target_preparer>
+ </device>
+
+ <test class="com.android.tradefed.testtype.mobly.MoblyBinaryHostTest">
+ <!-- The mobly-par-file-name should match the module name -->
+ <option name="mobly-par-file-name" value="CtsConnectivityMultiDevicesTestCases" />
+ <!-- Timeout limit in milliseconds for all test cases of the python binary -->
+ <option name="mobly-test-timeout" value="180000" />
+ </test>
+</configuration>
+
diff --git a/tests/cts/multidevices/connectivity_multi_devices_test.py b/tests/cts/multidevices/connectivity_multi_devices_test.py
new file mode 100644
index 0000000..ab88504
--- /dev/null
+++ b/tests/cts/multidevices/connectivity_multi_devices_test.py
@@ -0,0 +1,110 @@
+# Lint as: python3
+"""Connectivity multi devices tests."""
+import base64
+import sys
+import uuid
+
+from mobly import asserts
+from mobly import base_test
+from mobly import test_runner
+from mobly import utils
+from mobly.controllers import android_device
+
+CONNECTIVITY_MULTI_DEVICES_SNIPPET_PACKAGE = "com.google.snippet.connectivity"
+
+
+class UpstreamType:
+ CELLULAR = 1
+ WIFI = 2
+
+
+class ConnectivityMultiDevicesTest(base_test.BaseTestClass):
+
+ def setup_class(self):
+ # Declare that two Android devices are needed.
+ self.clientDevice, self.serverDevice = self.register_controller(
+ android_device, min_number=2
+ )
+
+ def setup_device(device):
+ device.load_snippet(
+ "connectivity_multi_devices_snippet",
+ CONNECTIVITY_MULTI_DEVICES_SNIPPET_PACKAGE,
+ )
+
+ # Set up devices in parallel to save time.
+ utils.concurrent_exec(
+ setup_device,
+ ((self.clientDevice,), (self.serverDevice,)),
+ max_workers=2,
+ raise_on_exception=True,
+ )
+
+ @staticmethod
+ def generate_uuid32_base64():
+ """Generates a UUID32 and encodes it in Base64.
+
+ Returns:
+ str: The Base64-encoded UUID32 string. Which is 22 characters.
+ """
+ return base64.b64encode(uuid.uuid1().bytes).decode("utf-8").strip("=")
+
+ def _do_test_hotspot_for_upstream_type(self, upstream_type):
+ """Test hotspot with the specified upstream type.
+
+ This test create a hotspot, make the client connect
+ to it, and verify the packet is forwarded by the hotspot.
+ """
+ server = self.serverDevice.connectivity_multi_devices_snippet
+ client = self.clientDevice.connectivity_multi_devices_snippet
+
+ # Assert pre-conditions specific to each upstream type.
+ asserts.skip_if(not client.hasWifiFeature(), "Client requires Wifi feature")
+ asserts.skip_if(
+ not server.hasHotspotFeature(), "Server requires hotspot feature"
+ )
+ if upstream_type == UpstreamType.CELLULAR:
+ asserts.skip_if(
+ not server.hasTelephonyFeature(), "Server requires Telephony feature"
+ )
+ server.requestCellularAndEnsureDefault()
+ elif upstream_type == UpstreamType.WIFI:
+ asserts.skip_if(
+ not server.isStaApConcurrencySupported(),
+ "Server requires Wifi AP + STA concurrency",
+ )
+ server.ensureWifiIsDefault()
+ else:
+ raise ValueError(f"Invalid upstream type: {upstream_type}")
+
+ # Generate ssid/passphrase with random characters to make sure nearby devices won't
+ # connect unexpectedly. Note that total length of ssid cannot go over 32.
+ testSsid = "HOTSPOT-" + self.generate_uuid32_base64()
+ testPassphrase = self.generate_uuid32_base64()
+
+ try:
+ # Create a hotspot with fixed SSID and password.
+ server.startHotspot(testSsid, testPassphrase)
+
+ # Make the client connects to the hotspot.
+ client.connectToWifi(testSsid, testPassphrase, True)
+
+ finally:
+ if upstream_type == UpstreamType.CELLULAR:
+ server.unrequestCellular()
+ # Teardown the hotspot.
+ server.stopAllTethering()
+
+ def test_hotspot_upstream_wifi(self):
+ self._do_test_hotspot_for_upstream_type(UpstreamType.WIFI)
+
+ def test_hotspot_upstream_cellular(self):
+ self._do_test_hotspot_for_upstream_type(UpstreamType.CELLULAR)
+
+
+if __name__ == "__main__":
+ # Take test args
+ if "--" in sys.argv:
+ index = sys.argv.index("--")
+ sys.argv = sys.argv[:1] + sys.argv[index + 1 :]
+ test_runner.main()
diff --git a/tests/cts/multidevices/snippet/Android.bp b/tests/cts/multidevices/snippet/Android.bp
new file mode 100644
index 0000000..5940cbb
--- /dev/null
+++ b/tests/cts/multidevices/snippet/Android.bp
@@ -0,0 +1,37 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "connectivity_multi_devices_snippet",
+ defaults: [
+ "ConnectivityTestsLatestSdkDefaults",
+ "cts_defaults",
+ "framework-connectivity-test-defaults",
+ ],
+ srcs: [
+ "ConnectivityMultiDevicesSnippet.kt",
+ ],
+ manifest: "AndroidManifest.xml",
+ static_libs: [
+ "androidx.test.runner",
+ "mobly-snippet-lib",
+ "cts-net-utils",
+ ],
+ platform_apis: true,
+ min_sdk_version: "30", // R
+}
diff --git a/tests/cts/multidevices/snippet/AndroidManifest.xml b/tests/cts/multidevices/snippet/AndroidManifest.xml
new file mode 100644
index 0000000..9ed8146
--- /dev/null
+++ b/tests/cts/multidevices/snippet/AndroidManifest.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.google.snippet.connectivity">
+ <!-- Declare the minimum Android SDK version and internet permission,
+ which are required by Mobly Snippet Lib since it uses network socket. -->
+ <uses-sdk android:minSdkVersion="30" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+ <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+ <uses-permission android:name="android.permission.INTERNET" />
+ <application>
+ <!-- Add any classes that implement the Snippet interface as meta-data, whose
+ value is a comma-separated string, each section being the package path
+ of a snippet class -->
+ <meta-data
+ android:name="mobly-snippets"
+ android:value="com.google.snippet.connectivity.ConnectivityMultiDevicesSnippet" />
+ </application>
+ <!-- Add an instrumentation tag so that the app can be launched through an
+ instrument command. The runner `com.google.android.mobly.snippet.SnippetRunner`
+ is derived from `AndroidJUnitRunner`, and is required to use the
+ Mobly Snippet Lib. -->
+ <instrumentation
+ android:name="com.google.android.mobly.snippet.SnippetRunner"
+ android:targetPackage="com.google.snippet.connectivity" />
+</manifest>
diff --git a/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt b/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt
new file mode 100644
index 0000000..115210b
--- /dev/null
+++ b/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.snippet.connectivity
+
+import android.Manifest.permission.OVERRIDE_WIFI_CONFIG
+import android.content.pm.PackageManager.FEATURE_TELEPHONY
+import android.content.pm.PackageManager.FEATURE_WIFI
+import android.net.ConnectivityManager
+import android.net.Network
+import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.NetworkRequest
+import android.net.cts.util.CtsNetUtils
+import android.net.cts.util.CtsTetheringUtils
+import android.net.wifi.ScanResult
+import android.net.wifi.SoftApConfiguration
+import android.net.wifi.SoftApConfiguration.SECURITY_TYPE_WPA2_PSK
+import android.net.wifi.WifiConfiguration
+import android.net.wifi.WifiManager
+import android.net.wifi.WifiNetworkSpecifier
+import android.net.wifi.WifiSsid
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.testutils.ConnectUtil
+import com.android.testutils.RecorderCallback.CallbackEntry.Available
+import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
+import com.android.testutils.TestableNetworkCallback
+import com.android.testutils.runAsShell
+import com.google.android.mobly.snippet.Snippet
+import com.google.android.mobly.snippet.rpc.Rpc
+
+class ConnectivityMultiDevicesSnippet : Snippet {
+ private val context = InstrumentationRegistry.getInstrumentation().getTargetContext()
+ private val wifiManager = context.getSystemService(WifiManager::class.java)!!
+ private val cm = context.getSystemService(ConnectivityManager::class.java)!!
+ private val pm = context.packageManager
+ private val ctsNetUtils = CtsNetUtils(context)
+ private val ctsTetheringUtils = CtsTetheringUtils(context)
+ private var oldSoftApConfig: SoftApConfiguration? = null
+
+ @Rpc(description = "Check whether the device has wifi feature.")
+ fun hasWifiFeature() = pm.hasSystemFeature(FEATURE_WIFI)
+
+ @Rpc(description = "Check whether the device has telephony feature.")
+ fun hasTelephonyFeature() = pm.hasSystemFeature(FEATURE_TELEPHONY)
+
+ @Rpc(description = "Check whether the device supporters AP + STA concurrency.")
+ fun isStaApConcurrencySupported() {
+ wifiManager.isStaApConcurrencySupported()
+ }
+
+ @Rpc(description = "Request cellular connection and ensure it is the default network.")
+ fun requestCellularAndEnsureDefault() {
+ ctsNetUtils.disableWifi()
+ val network = ctsNetUtils.connectToCell()
+ ctsNetUtils.expectNetworkIsSystemDefault(network)
+ }
+
+ @Rpc(description = "Unrequest cellular connection.")
+ fun unrequestCellular() {
+ ctsNetUtils.disconnectFromCell()
+ }
+
+ @Rpc(description = "Ensure any wifi is connected and is the default network.")
+ fun ensureWifiIsDefault() {
+ val network = ctsNetUtils.ensureWifiConnected()
+ ctsNetUtils.expectNetworkIsSystemDefault(network)
+ }
+
+ @Rpc(description = "Connect to specified wifi network.")
+ // Suppress warning because WifiManager methods to connect to a config are
+ // documented not to be deprecated for privileged users.
+ @Suppress("DEPRECATION")
+ fun connectToWifi(ssid: String, passphrase: String, requireValidation: Boolean): Network {
+ val specifier = WifiNetworkSpecifier.Builder()
+ .setSsid(ssid)
+ .setWpa2Passphrase(passphrase)
+ .setBand(ScanResult.WIFI_BAND_24_GHZ)
+ .build()
+ val wifiConfig = WifiConfiguration()
+ wifiConfig.SSID = "\"" + ssid + "\""
+ wifiConfig.preSharedKey = "\"" + passphrase + "\""
+ wifiConfig.hiddenSSID = true
+ wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA2_PSK)
+ wifiConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP)
+ wifiConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP)
+
+ // Register network callback for the specific wifi.
+ val networkCallback = TestableNetworkCallback()
+ val wifiRequest = NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI)
+ .setNetworkSpecifier(specifier)
+ .build()
+ cm.registerNetworkCallback(wifiRequest, networkCallback)
+
+ try {
+ // Add the test configuration and connect to it.
+ val connectUtil = ConnectUtil(context)
+ connectUtil.connectToWifiConfig(wifiConfig)
+
+ val event = networkCallback.expect<Available>()
+ if (requireValidation) {
+ networkCallback.eventuallyExpect<CapabilitiesChanged> {
+ it.caps.hasCapability(NET_CAPABILITY_VALIDATED)
+ }
+ }
+ return event.network
+ } finally {
+ cm.unregisterNetworkCallback(networkCallback)
+ }
+ }
+
+ @Rpc(description = "Check whether the device supports hotspot feature.")
+ fun hasHotspotFeature(): Boolean {
+ val tetheringCallback = ctsTetheringUtils.registerTetheringEventCallback()
+ try {
+ return tetheringCallback.isWifiTetheringSupported(context)
+ } finally {
+ ctsTetheringUtils.unregisterTetheringEventCallback(tetheringCallback)
+ }
+ }
+
+ @Rpc(description = "Start a hotspot with given SSID and passphrase.")
+ fun startHotspot(ssid: String, passphrase: String) {
+ // Store old config.
+ runAsShell(OVERRIDE_WIFI_CONFIG) {
+ oldSoftApConfig = wifiManager.getSoftApConfiguration()
+ }
+
+ val softApConfig = SoftApConfiguration.Builder()
+ .setWifiSsid(WifiSsid.fromBytes(ssid.toByteArray()))
+ .setPassphrase(passphrase, SECURITY_TYPE_WPA2_PSK)
+ .setBand(SoftApConfiguration.BAND_2GHZ)
+ .build()
+ runAsShell(OVERRIDE_WIFI_CONFIG) {
+ wifiManager.setSoftApConfiguration(softApConfig)
+ }
+ val tetheringCallback = ctsTetheringUtils.registerTetheringEventCallback()
+ try {
+ tetheringCallback.expectNoTetheringActive()
+ ctsTetheringUtils.startWifiTethering(tetheringCallback)
+ } finally {
+ ctsTetheringUtils.unregisterTetheringEventCallback(tetheringCallback)
+ }
+ }
+
+ @Rpc(description = "Stop all tethering.")
+ fun stopAllTethering() {
+ ctsTetheringUtils.stopAllTethering()
+
+ // Restore old config.
+ oldSoftApConfig?.let {
+ runAsShell(OVERRIDE_WIFI_CONFIG) {
+ wifiManager.setSoftApConfiguration(it)
+ }
+ }
+ }
+}
diff --git a/tests/cts/net/Android.bp b/tests/cts/net/Android.bp
index 3d53d6c..98d5630 100644
--- a/tests/cts/net/Android.bp
+++ b/tests/cts/net/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -54,6 +55,7 @@
"junit",
"junit-params",
"modules-utils-build",
+ "net-tests-utils",
"net-utils-framework-common",
"truth",
"TetheringIntegrationTestsBaseLib",
@@ -68,7 +70,7 @@
data: [
":ConnectivityTestPreparer",
":CtsCarrierServicePackage",
- ]
+ ],
}
// Networking CTS tests for development and release. These tests always target the platform SDK
diff --git a/tests/cts/net/api23Test/Android.bp b/tests/cts/net/api23Test/Android.bp
index 1f1dd5d..2ec3a70 100644
--- a/tests/cts/net/api23Test/Android.bp
+++ b/tests/cts/net/api23Test/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/tests/cts/net/api23Test/src/android/net/cts/api23test/ConnectivityManagerApi23Test.java b/tests/cts/net/api23Test/src/android/net/cts/api23test/ConnectivityManagerApi23Test.java
index 8d68c5f..af1af43 100644
--- a/tests/cts/net/api23Test/src/android/net/cts/api23test/ConnectivityManagerApi23Test.java
+++ b/tests/cts/net/api23Test/src/android/net/cts/api23test/ConnectivityManagerApi23Test.java
@@ -65,7 +65,7 @@
}
ConnectivityReceiver.prepare();
- mCtsNetUtils.toggleWifi();
+ mCtsNetUtils.reconnectWifiAndWaitForConnectivityAction();
// The connectivity broadcast has been sent; push through a terminal broadcast
// to wait for in the receive to confirm it didn't see the connectivity change.
@@ -88,7 +88,7 @@
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
Thread.sleep(200);
- mCtsNetUtils.toggleWifi();
+ mCtsNetUtils.reconnectWifiAndWaitForConnectivityAction();
Intent getConnectivityCount = new Intent(GET_WIFI_CONNECTIVITY_ACTION_COUNT);
assertEquals(2, sendOrderedBroadcastAndReturnResultCode(
@@ -106,7 +106,7 @@
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
mContext.registerReceiver(receiver, filter);
- mCtsNetUtils.toggleWifi();
+ mCtsNetUtils.reconnectWifiAndWaitForConnectivityAction();
Intent finalIntent = new Intent(ConnectivityReceiver.FINAL_ACTION);
finalIntent.setClass(mContext, ConnectivityReceiver.class);
mContext.sendBroadcast(finalIntent);
diff --git a/tests/cts/net/appForApi23/Android.bp b/tests/cts/net/appForApi23/Android.bp
index b39690f..d300743 100644
--- a/tests/cts/net/appForApi23/Android.bp
+++ b/tests/cts/net/appForApi23/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/tests/cts/net/jni/Android.bp b/tests/cts/net/jni/Android.bp
index a421349..fbf4f29 100644
--- a/tests/cts/net/jni/Android.bp
+++ b/tests/cts/net/jni/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/tests/cts/net/native/Android.bp b/tests/cts/net/native/Android.bp
index 153ff51..3f24592 100644
--- a/tests/cts/net/native/Android.bp
+++ b/tests/cts/net/native/Android.bp
@@ -15,6 +15,7 @@
// Build the unit tests.
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/tests/cts/net/native/dns/Android.bp b/tests/cts/net/native/dns/Android.bp
index a9e3715..8e24fba 100644
--- a/tests/cts/net/native/dns/Android.bp
+++ b/tests/cts/net/native/dns/Android.bp
@@ -1,4 +1,5 @@
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/tests/cts/net/src/android/net/cts/BatteryStatsManagerTest.java b/tests/cts/net/src/android/net/cts/BatteryStatsManagerTest.java
index 466514c..3e5d0ba 100644
--- a/tests/cts/net/src/android/net/cts/BatteryStatsManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/BatteryStatsManagerTest.java
@@ -199,7 +199,8 @@
Log.d(TAG, "Generate traffic on wifi network.");
generateNetworkTraffic(wifiNetwork, url);
// Wifi battery stats are updated when wifi on.
- mCtsNetUtils.toggleWifi();
+ mCtsNetUtils.disableWifi();
+ mCtsNetUtils.ensureWifiConnected();
// Check wifi battery stats are updated.
runAsShell(UPDATE_DEVICE_STATS,
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 544f300..f0edee2 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -1353,9 +1353,7 @@
public void testToggleWifiConnectivityAction() throws Exception {
assumeTrue(mPackageManager.hasSystemFeature(FEATURE_WIFI));
- // toggleWifi calls connectToWifi and disconnectFromWifi, which both wait for
- // CONNECTIVITY_ACTION broadcasts.
- mCtsNetUtils.toggleWifi();
+ mCtsNetUtils.reconnectWifiAndWaitForConnectivityAction();
}
/** Verify restricted networks cannot be requested. */
@@ -1554,6 +1552,40 @@
}
}
+ @Test @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) @ConnectivityModuleTest
+ public void testSetBackgroundNetworkingShellCommand() {
+ final int testUid = 54352;
+ runShellCommand("cmd connectivity set-background-networking-enabled-for-uid " + testUid
+ + " true");
+ int rule = runAsShell(NETWORK_SETTINGS,
+ () -> mCm.getUidFirewallRule(FIREWALL_CHAIN_BACKGROUND, testUid));
+ assertEquals(rule, FIREWALL_RULE_ALLOW);
+
+ runShellCommand("cmd connectivity set-background-networking-enabled-for-uid " + testUid
+ + " false");
+ rule = runAsShell(NETWORK_SETTINGS,
+ () -> mCm.getUidFirewallRule(FIREWALL_CHAIN_BACKGROUND, testUid));
+ assertEquals(rule, FIREWALL_RULE_DENY);
+ }
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) @ConnectivityModuleTest
+ public void testGetBackgroundNetworkingShellCommand() {
+ final int testUid = 54312;
+ runAsShell(NETWORK_SETTINGS,
+ () -> mCm.setUidFirewallRule(FIREWALL_CHAIN_BACKGROUND, testUid,
+ FIREWALL_RULE_ALLOW));
+ String output = runShellCommand(
+ "cmd connectivity get-background-networking-enabled-for-uid " + testUid);
+ assertTrue(output.contains("allow"));
+
+ runAsShell(NETWORK_SETTINGS,
+ () -> mCm.setUidFirewallRule(FIREWALL_CHAIN_BACKGROUND, testUid,
+ FIREWALL_RULE_DEFAULT));
+ output = runShellCommand(
+ "cmd connectivity get-background-networking-enabled-for-uid " + testUid);
+ assertTrue(output.contains("deny"));
+ }
+
// TODO: move the following socket keep alive test to dedicated test class.
/**
* Callback used in tcp keepalive offload that allows caller to wait callback fires.
diff --git a/tests/cts/net/src/android/net/cts/DiscoveryRequestTest.kt b/tests/cts/net/src/android/net/cts/DiscoveryRequestTest.kt
new file mode 100644
index 0000000..909a5bc
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/DiscoveryRequestTest.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.cts
+
+import android.net.Network
+import android.net.nsd.DiscoveryRequest
+import android.os.Build
+import androidx.test.filters.SmallTest
+import com.android.testutils.ConnectivityModuleTest
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.assertParcelingIsLossless
+import com.android.testutils.assertThrows
+import kotlin.test.assertEquals
+import kotlin.test.assertNotEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** CTS tests for {@link DiscoveryRequest}. */
+@IgnoreUpTo(Build.VERSION_CODES.S_V2)
+@RunWith(DevSdkIgnoreRunner::class)
+@SmallTest
+@ConnectivityModuleTest
+class DiscoveryRequestTest {
+ @Test
+ fun testParcelingIsLossLess() {
+ val requestWithNullFields =
+ DiscoveryRequest.Builder("_ipps._tcp").build()
+ val requestWithAllFields =
+ DiscoveryRequest.Builder("_ipps._tcp")
+ .setSubtype("_xyz")
+ .setNetwork(Network(1))
+ .build()
+
+ assertParcelingIsLossless(requestWithNullFields)
+ assertParcelingIsLossless(requestWithAllFields)
+ }
+
+ @Test
+ fun testBuilder_success() {
+ val request = DiscoveryRequest.Builder("_ipps._tcp")
+ .setSubtype("_xyz")
+ .setNetwork(Network(1))
+ .build()
+
+ assertEquals("_ipps._tcp", request.serviceType)
+ assertEquals("_xyz", request.subtype)
+ assertEquals(Network(1), request.network)
+ }
+
+ @Test
+ fun testBuilderConstructor_emptyServiceType_throwsIllegalArgument() {
+ assertThrows(IllegalArgumentException::class.java) {
+ DiscoveryRequest.Builder("")
+ }
+ }
+
+ @Test
+ fun testEquality() {
+ val request1 = DiscoveryRequest.Builder("_ipps._tcp").build()
+ val request2 = DiscoveryRequest.Builder("_ipps._tcp").build()
+ val request3 = DiscoveryRequest.Builder("_ipps._tcp")
+ .setSubtype("_xyz")
+ .setNetwork(Network(1))
+ .build()
+ val request4 = DiscoveryRequest.Builder("_ipps._tcp")
+ .setSubtype("_xyz")
+ .setNetwork(Network(1))
+ .build()
+
+ assertEquals(request1, request2)
+ assertEquals(request3, request4)
+ assertNotEquals(request1, request3)
+ assertNotEquals(request2, request4)
+ }
+}
diff --git a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
index 594f3fb..5a4587c 100644
--- a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
+++ b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
@@ -32,6 +32,8 @@
import static com.android.testutils.DevSdkIgnoreRuleKt.VANILLA_ICE_CREAM;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.fail;
import static org.junit.Assert.assertArrayEquals;
@@ -173,6 +175,20 @@
}
@Test
+ @IgnoreUpTo(Build.VERSION_CODES.S)
+ public void testSubscriptionIds() {
+ int[] subIds = {1, 2};
+ assertTrue(
+ new NetworkRequest.Builder().build()
+ .getSubscriptionIds().isEmpty());
+ assertThat(new NetworkRequest.Builder()
+ .setSubscriptionIds(Set.of(subIds[0], subIds[1]))
+ .build()
+ .getSubscriptionIds())
+ .containsExactly(subIds[0], subIds[1]);
+ }
+
+ @Test
@IgnoreUpTo(Build.VERSION_CODES.Q)
public void testRequestorPackageName() {
assertNull(new NetworkRequest.Builder().build().getRequestorPackageName());
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index 1309e79..43aa8a6 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -53,6 +53,7 @@
import android.net.cts.NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent.ServiceUpdatedLost
import android.net.cts.NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent.UnregisterCallbackSucceeded
import android.net.cts.util.CtsNetUtils
+import android.net.nsd.DiscoveryRequest
import android.net.nsd.NsdManager
import android.net.nsd.NsdServiceInfo
import android.net.nsd.OffloadEngine
@@ -160,7 +161,11 @@
private val cm by lazy { context.getSystemService(ConnectivityManager::class.java)!! }
private val serviceName = "NsdTest%09d".format(Random().nextInt(1_000_000_000))
private val serviceName2 = "NsdTest%09d".format(Random().nextInt(1_000_000_000))
+ private val serviceName3 = "NsdTest%09d".format(Random().nextInt(1_000_000_000))
private val serviceType = "_nmt%09d._tcp".format(Random().nextInt(1_000_000_000))
+ private val serviceType2 = "_nmt%09d._tcp".format(Random().nextInt(1_000_000_000))
+ private val customHostname = "NsdTestHost%09d".format(Random().nextInt(1_000_000_000))
+ private val customHostname2 = "NsdTestHost%09d".format(Random().nextInt(1_000_000_000))
private val handlerThread = HandlerThread(NsdManagerTest::class.java.simpleName)
private val ctsNetUtils by lazy{ CtsNetUtils(context) }
@@ -686,7 +691,8 @@
assertEquals(listOf("_subtype"), serviceInfo.subtypes)
assertTrue(serviceInfo.hostname.startsWith("Android_"))
assertTrue(serviceInfo.hostname.endsWith("local"))
- assertEquals(0, serviceInfo.priority)
+ // Test service types should not be in the priority list
+ assertEquals(Integer.MAX_VALUE, serviceInfo.priority)
assertEquals(OffloadEngine.OFFLOAD_TYPE_REPLY.toLong(), serviceInfo.offloadType)
val offloadPayload = serviceInfo.offloadPayload
assertNotNull(offloadPayload)
@@ -1041,9 +1047,11 @@
nsdManager.discoverServices("_subtype1.$serviceType",
NsdManager.PROTOCOL_DNS_SD,
testNetwork1.network, Executor { it.run() }, subtype1DiscoveryRecord)
- nsdManager.discoverServices("_subtype2.$serviceType",
- NsdManager.PROTOCOL_DNS_SD,
- testNetwork1.network, Executor { it.run() }, subtype2DiscoveryRecord)
+
+ nsdManager.discoverServices(
+ DiscoveryRequest.Builder(serviceType).setSubtype("_subtype2")
+ .setNetwork(testNetwork1.network).build(),
+ Executor { it.run() }, subtype2DiscoveryRecord)
val info1 = subtype1DiscoveryRecord.waitForServiceDiscovered(
serviceName, serviceType, testNetwork1.network)
@@ -1098,6 +1106,73 @@
}
@Test
+ fun testSubtypeAdvertisingAndDiscovery_nonAlphanumericalSubtypes() {
+ // All non-alphanumerical characters between 0x20 and 0x7e, with a leading underscore
+ val nonAlphanumSubtype = "_ !\"#\$%&'()*+-/:;<=>?@[\\]^_`{|}"
+ // Test both legacy syntax and the subtypes setter, on different networks
+ val si1 = makeTestServiceInfo(network = testNetwork1.network).apply {
+ serviceType = "$serviceType,_test1,$nonAlphanumSubtype"
+ }
+ val si2 = makeTestServiceInfo(network = testNetwork2.network).apply {
+ subtypes = setOf("_test2", nonAlphanumSubtype)
+ }
+
+ val registrationRecord1 = NsdRegistrationRecord()
+ val registrationRecord2 = NsdRegistrationRecord()
+ val subtypeDiscoveryRecord1 = NsdDiscoveryRecord()
+ val subtypeDiscoveryRecord2 = NsdDiscoveryRecord()
+ tryTest {
+ registerService(registrationRecord1, si1)
+ registerService(registrationRecord2, si2)
+ nsdManager.discoverServices(DiscoveryRequest.Builder(serviceType)
+ .setSubtype(nonAlphanumSubtype)
+ .setNetwork(testNetwork1.network)
+ .build(), { it.run() }, subtypeDiscoveryRecord1)
+ nsdManager.discoverServices("$nonAlphanumSubtype.$serviceType",
+ NsdManager.PROTOCOL_DNS_SD, testNetwork2.network, { it.run() },
+ subtypeDiscoveryRecord2)
+
+ val discoveredInfo1 = subtypeDiscoveryRecord1.waitForServiceDiscovered(serviceName,
+ serviceType, testNetwork1.network)
+ val discoveredInfo2 = subtypeDiscoveryRecord2.waitForServiceDiscovered(serviceName,
+ serviceType, testNetwork2.network)
+ assertTrue(discoveredInfo1.subtypes.contains(nonAlphanumSubtype))
+ assertTrue(discoveredInfo2.subtypes.contains(nonAlphanumSubtype))
+ } cleanupStep {
+ nsdManager.stopServiceDiscovery(subtypeDiscoveryRecord1)
+ subtypeDiscoveryRecord1.expectCallback<DiscoveryStopped>()
+ } cleanupStep {
+ nsdManager.stopServiceDiscovery(subtypeDiscoveryRecord2)
+ subtypeDiscoveryRecord2.expectCallback<DiscoveryStopped>()
+ } cleanup {
+ nsdManager.unregisterService(registrationRecord1)
+ nsdManager.unregisterService(registrationRecord2)
+ }
+ }
+
+ @Test
+ fun testSubtypeDiscovery_typeMatchButSubtypeNotMatch_notDiscovered() {
+ val si1 = makeTestServiceInfo(network = testNetwork1.network).apply {
+ serviceType += ",_subtype1"
+ }
+ val registrationRecord = NsdRegistrationRecord()
+ val subtype2DiscoveryRecord = NsdDiscoveryRecord()
+ tryTest {
+ registerService(registrationRecord, si1)
+ val request = DiscoveryRequest.Builder(serviceType)
+ .setSubtype("_subtype2").setNetwork(testNetwork1.network).build()
+ nsdManager.discoverServices(request, { it.run() }, subtype2DiscoveryRecord)
+ subtype2DiscoveryRecord.expectCallback<DiscoveryStarted>()
+ subtype2DiscoveryRecord.assertNoCallback(timeoutMs = 2000)
+ } cleanupStep {
+ nsdManager.stopServiceDiscovery(subtype2DiscoveryRecord)
+ subtype2DiscoveryRecord.expectCallback<DiscoveryStopped>()
+ } cleanup {
+ nsdManager.unregisterService(registrationRecord)
+ }
+ }
+
+ @Test
fun testSubtypeAdvertising_tooManySubtypes_returnsFailureBadParameters() {
val si = makeTestServiceInfo(network = testNetwork1.network)
// Sets 101 subtypes in total
@@ -1162,6 +1237,83 @@
}
@Test
+ fun testRegisterServiceWithCustomHostAndAddresses_conflictDuringProbing_hostRenamed() {
+ val si = makeTestServiceInfo(testNetwork1.network).apply {
+ hostname = customHostname
+ hostAddresses = listOf(
+ parseNumericAddress("192.0.2.24"),
+ parseNumericAddress("2001:db8::3"))
+ }
+
+ val packetReader = TapPacketReader(Handler(handlerThread.looper),
+ testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
+ packetReader.startAsyncForTest()
+ handlerThread.waitForIdle(TIMEOUT_MS)
+
+ // Register service on testNetwork1
+ val registrationRecord = NsdRegistrationRecord()
+ nsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, { it.run() },
+ registrationRecord)
+
+ tryTest {
+ assertNotNull(packetReader.pollForProbe(serviceName, serviceType),
+ "Did not find a probe for the service")
+ packetReader.sendResponse(buildConflictingAnnouncementForCustomHost())
+
+ // Registration must use an updated hostname to avoid the conflict
+ val cb = registrationRecord.expectCallback<ServiceRegistered>(REGISTRATION_TIMEOUT_MS)
+ // Service name is not renamed because there's no conflict on the service name.
+ assertEquals(serviceName, cb.serviceInfo.serviceName)
+ val hostname = cb.serviceInfo.hostname ?: fail("Missing hostname")
+ hostname.let {
+ assertTrue("Unexpected registered hostname: $it",
+ it.startsWith(customHostname) && it != customHostname)
+ }
+ } cleanupStep {
+ nsdManager.unregisterService(registrationRecord)
+ registrationRecord.expectCallback<ServiceUnregistered>()
+ } cleanup {
+ packetReader.handler.post { packetReader.stop() }
+ handlerThread.waitForIdle(TIMEOUT_MS)
+ }
+ }
+
+ @Test
+ fun testRegisterServiceWithCustomHostNoAddresses_noConflictDuringProbing_notRenamed() {
+ val si = makeTestServiceInfo(testNetwork1.network).apply {
+ hostname = customHostname
+ }
+
+ val packetReader = TapPacketReader(Handler(handlerThread.looper),
+ testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
+ packetReader.startAsyncForTest()
+ handlerThread.waitForIdle(TIMEOUT_MS)
+
+ // Register service on testNetwork1
+ val registrationRecord = NsdRegistrationRecord()
+ nsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, { it.run() },
+ registrationRecord)
+
+ tryTest {
+ assertNotNull(packetReader.pollForProbe(serviceName, serviceType),
+ "Did not find a probe for the service")
+ // Not a conflict because no record is registered for the hostname
+ packetReader.sendResponse(buildConflictingAnnouncementForCustomHost())
+
+ // Registration is not renamed because there's no conflict
+ val cb = registrationRecord.expectCallback<ServiceRegistered>(REGISTRATION_TIMEOUT_MS)
+ assertEquals(serviceName, cb.serviceInfo.serviceName)
+ assertEquals(customHostname, cb.serviceInfo.hostname)
+ } cleanupStep {
+ nsdManager.unregisterService(registrationRecord)
+ registrationRecord.expectCallback<ServiceUnregistered>()
+ } cleanup {
+ packetReader.handler.post { packetReader.stop() }
+ handlerThread.waitForIdle(TIMEOUT_MS)
+ }
+ }
+
+ @Test
fun testRegisterWithConflictAfterProbing() {
// This test requires shims supporting T+ APIs (NsdServiceInfo.network)
assumeTrue(TestUtils.shouldTestTApis())
@@ -1236,6 +1388,121 @@
}
}
+ @Test
+ fun testRegisterServiceWithCustomHostAndAddresses_conflictAfterProbing_hostRenamed() {
+ val si = makeTestServiceInfo(testNetwork1.network).apply {
+ hostname = customHostname
+ hostAddresses = listOf(
+ parseNumericAddress("192.0.2.24"),
+ parseNumericAddress("2001:db8::3"))
+ }
+
+ // Register service on testNetwork1
+ val registrationRecord = NsdRegistrationRecord()
+ val discoveryRecord = NsdDiscoveryRecord()
+ val registeredService = registerService(registrationRecord, si)
+ val packetReader = TapPacketReader(
+ Handler(handlerThread.looper),
+ testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
+ packetReader.startAsyncForTest()
+ handlerThread.waitForIdle(TIMEOUT_MS)
+
+ tryTest {
+ repeat(3) {
+ assertNotNull(packetReader.pollForAdvertisement(serviceName, serviceType),
+ "Expect 3 announcements sent after initial probing")
+ }
+
+ assertEquals(si.serviceName, registeredService.serviceName)
+ assertEquals(si.hostname, registeredService.hostname)
+
+ nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD,
+ testNetwork1.network, { it.run() }, discoveryRecord)
+ val discoveredInfo = discoveryRecord.waitForServiceDiscovered(
+ si.serviceName, serviceType)
+
+ // Send a conflicting announcement
+ val conflictingAnnouncement = buildConflictingAnnouncementForCustomHost()
+ packetReader.sendResponse(conflictingAnnouncement)
+
+ // Expect to see probes (RFC6762 9., service is reset to probing state)
+ assertNotNull(packetReader.pollForProbe(serviceName, serviceType),
+ "Probe not received within timeout after conflict")
+
+ // Send the conflicting packet again to reply to the probe
+ packetReader.sendResponse(conflictingAnnouncement)
+
+ val newRegistration =
+ registrationRecord
+ .expectCallbackEventually<ServiceRegistered>(REGISTRATION_TIMEOUT_MS) {
+ it.serviceInfo.serviceName == serviceName
+ && it.serviceInfo.hostname.let { hostname ->
+ hostname != null
+ && hostname.startsWith(customHostname)
+ && hostname != customHostname
+ }
+ }
+
+ val resolvedInfo = resolveService(discoveredInfo)
+ assertEquals(newRegistration.serviceInfo.serviceName, resolvedInfo.serviceName)
+ assertEquals(newRegistration.serviceInfo.hostname, resolvedInfo.hostname)
+
+ discoveryRecord.assertNoCallback()
+ } cleanupStep {
+ nsdManager.stopServiceDiscovery(discoveryRecord)
+ discoveryRecord.expectCallback<DiscoveryStopped>()
+ } cleanupStep {
+ nsdManager.unregisterService(registrationRecord)
+ registrationRecord.expectCallback<ServiceUnregistered>()
+ } cleanup {
+ packetReader.handler.post { packetReader.stop() }
+ handlerThread.waitForIdle(TIMEOUT_MS)
+ }
+ }
+
+ @Test
+ fun testRegisterServiceWithCustomHostNoAddresses_noConflictAfterProbing_notRenamed() {
+ val si = makeTestServiceInfo(testNetwork1.network).apply {
+ hostname = customHostname
+ }
+
+ // Register service on testNetwork1
+ val registrationRecord = NsdRegistrationRecord()
+ val discoveryRecord = NsdDiscoveryRecord()
+ val registeredService = registerService(registrationRecord, si)
+ val packetReader = TapPacketReader(Handler(handlerThread.looper),
+ testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
+ packetReader.startAsyncForTest()
+ handlerThread.waitForIdle(TIMEOUT_MS)
+
+ tryTest {
+ assertNotNull(packetReader.pollForAdvertisement(serviceName, serviceType),
+ "No announcements sent after initial probing")
+
+ assertEquals(si.serviceName, registeredService.serviceName)
+ assertEquals(si.hostname, registeredService.hostname)
+
+ // Send a conflicting announcement
+ val conflictingAnnouncement = buildConflictingAnnouncementForCustomHost()
+ packetReader.sendResponse(conflictingAnnouncement)
+
+ nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD,
+ testNetwork1.network, { it.run() }, discoveryRecord)
+
+ // The service is not renamed
+ discoveryRecord.waitForServiceDiscovered(si.serviceName, serviceType)
+ } cleanupStep {
+ nsdManager.stopServiceDiscovery(discoveryRecord)
+ discoveryRecord.expectCallback<DiscoveryStopped>()
+ } cleanupStep {
+ nsdManager.unregisterService(registrationRecord)
+ registrationRecord.expectCallback<ServiceUnregistered>()
+ } cleanup {
+ packetReader.handler.post { packetReader.stop() }
+ handlerThread.waitForIdle(TIMEOUT_MS)
+ }
+ }
+
// Test that even if only a PTR record is received as a reply when discovering, without the
// SRV, TXT, address records as recommended (but not mandated) by RFC 6763 12, the service can
// still be discovered.
@@ -1420,6 +1687,212 @@
return Inet6Address.getByAddress(addrBytes) as Inet6Address
}
+ @Test
+ fun testAdvertisingAndDiscovery_servicesWithCustomHost_customHostAddressesFound() {
+ val hostAddresses1 = listOf(
+ parseNumericAddress("192.0.2.23"),
+ parseNumericAddress("2001:db8::1"),
+ parseNumericAddress("2001:db8::2"))
+ val hostAddresses2 = listOf(
+ parseNumericAddress("192.0.2.24"),
+ parseNumericAddress("2001:db8::3"))
+ val si1 = NsdServiceInfo().also {
+ it.network = testNetwork1.network
+ it.serviceName = serviceName
+ it.serviceType = serviceType
+ it.port = TEST_PORT
+ it.hostname = customHostname
+ it.hostAddresses = hostAddresses1
+ }
+ val si2 = NsdServiceInfo().also {
+ it.network = testNetwork1.network
+ it.serviceName = serviceName2
+ it.serviceType = serviceType
+ it.port = TEST_PORT + 1
+ it.hostname = customHostname2
+ it.hostAddresses = hostAddresses2
+ }
+ val registrationRecord1 = NsdRegistrationRecord()
+ val registrationRecord2 = NsdRegistrationRecord()
+
+ val discoveryRecord1 = NsdDiscoveryRecord()
+ val discoveryRecord2 = NsdDiscoveryRecord()
+ tryTest {
+ registerService(registrationRecord1, si1)
+
+ nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD,
+ testNetwork1.network, Executor { it.run() }, discoveryRecord1)
+
+ val discoveredInfo = discoveryRecord1.waitForServiceDiscovered(
+ serviceName, serviceType, testNetwork1.network)
+ val resolvedInfo = resolveService(discoveredInfo)
+
+ assertEquals(TEST_PORT, resolvedInfo.port)
+ assertEquals(si1.hostname, resolvedInfo.hostname)
+ assertAddressEquals(hostAddresses1, resolvedInfo.hostAddresses)
+
+ registerService(registrationRecord2, si2)
+ nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD,
+ testNetwork1.network, Executor { it.run() }, discoveryRecord2)
+
+ val discoveredInfo2 = discoveryRecord2.waitForServiceDiscovered(
+ serviceName2, serviceType, testNetwork1.network)
+ val resolvedInfo2 = resolveService(discoveredInfo2)
+
+ assertEquals(TEST_PORT + 1, resolvedInfo2.port)
+ assertEquals(si2.hostname, resolvedInfo2.hostname)
+ assertAddressEquals(hostAddresses2, resolvedInfo2.hostAddresses)
+ } cleanupStep {
+ nsdManager.stopServiceDiscovery(discoveryRecord1)
+ nsdManager.stopServiceDiscovery(discoveryRecord2)
+
+ discoveryRecord1.expectCallbackEventually<DiscoveryStopped>()
+ discoveryRecord2.expectCallbackEventually<DiscoveryStopped>()
+ } cleanup {
+ nsdManager.unregisterService(registrationRecord1)
+ nsdManager.unregisterService(registrationRecord2)
+ }
+ }
+
+ @Test
+ fun testAdvertisingAndDiscovery_multipleRegistrationsForSameCustomHost_unionOfAddressesFound() {
+ val hostAddresses1 = listOf(
+ parseNumericAddress("192.0.2.23"),
+ parseNumericAddress("2001:db8::1"),
+ parseNumericAddress("2001:db8::2"))
+ val hostAddresses2 = listOf(
+ parseNumericAddress("192.0.2.24"),
+ parseNumericAddress("2001:db8::3"))
+ val hostAddresses3 = listOf(
+ parseNumericAddress("2001:db8::3"),
+ parseNumericAddress("2001:db8::5"))
+ val si1 = NsdServiceInfo().also {
+ it.network = testNetwork1.network
+ it.hostname = customHostname
+ it.hostAddresses = hostAddresses1
+ }
+ val si2 = NsdServiceInfo().also {
+ it.network = testNetwork1.network
+ it.serviceName = serviceName
+ it.serviceType = serviceType
+ it.port = TEST_PORT
+ it.hostname = customHostname
+ it.hostAddresses = hostAddresses2
+ }
+ val si3 = NsdServiceInfo().also {
+ it.network = testNetwork1.network
+ it.serviceName = serviceName3
+ it.serviceType = serviceType
+ it.port = TEST_PORT + 1
+ it.hostname = customHostname
+ it.hostAddresses = hostAddresses3
+ }
+
+ val registrationRecord1 = NsdRegistrationRecord()
+ val registrationRecord2 = NsdRegistrationRecord()
+ val registrationRecord3 = NsdRegistrationRecord()
+
+ val discoveryRecord = NsdDiscoveryRecord()
+ tryTest {
+ registerService(registrationRecord1, si1)
+ registerService(registrationRecord2, si2)
+
+ nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD,
+ testNetwork1.network, Executor { it.run() }, discoveryRecord)
+
+ val discoveredInfo1 = discoveryRecord.waitForServiceDiscovered(
+ serviceName, serviceType, testNetwork1.network)
+ val resolvedInfo1 = resolveService(discoveredInfo1)
+
+ assertEquals(TEST_PORT, resolvedInfo1.port)
+ assertEquals(si1.hostname, resolvedInfo1.hostname)
+ assertAddressEquals(
+ hostAddresses1 + hostAddresses2,
+ resolvedInfo1.hostAddresses)
+
+ registerService(registrationRecord3, si3)
+
+ val discoveredInfo2 = discoveryRecord.waitForServiceDiscovered(
+ serviceName3, serviceType, testNetwork1.network)
+ val resolvedInfo2 = resolveService(discoveredInfo2)
+
+ assertEquals(TEST_PORT + 1, resolvedInfo2.port)
+ assertEquals(si2.hostname, resolvedInfo2.hostname)
+ assertAddressEquals(
+ hostAddresses1 + hostAddresses2 + hostAddresses3,
+ resolvedInfo2.hostAddresses)
+ } cleanupStep {
+ nsdManager.stopServiceDiscovery(discoveryRecord)
+
+ discoveryRecord.expectCallbackEventually<DiscoveryStopped>()
+ } cleanup {
+ nsdManager.unregisterService(registrationRecord1)
+ nsdManager.unregisterService(registrationRecord2)
+ nsdManager.unregisterService(registrationRecord3)
+ }
+ }
+
+ @Test
+ fun testAdvertisingAndDiscovery_servicesWithTheSameCustomHostAddressOmitted_addressesFound() {
+ val hostAddresses = listOf(
+ parseNumericAddress("192.0.2.23"),
+ parseNumericAddress("2001:db8::1"),
+ parseNumericAddress("2001:db8::2"))
+ val si1 = NsdServiceInfo().also {
+ it.network = testNetwork1.network
+ it.serviceType = serviceType
+ it.serviceName = serviceName
+ it.port = TEST_PORT
+ it.hostname = customHostname
+ it.hostAddresses = hostAddresses
+ }
+ val si2 = NsdServiceInfo().also {
+ it.network = testNetwork1.network
+ it.serviceType = serviceType
+ it.serviceName = serviceName2
+ it.port = TEST_PORT + 1
+ it.hostname = customHostname
+ }
+
+ val registrationRecord1 = NsdRegistrationRecord()
+ val registrationRecord2 = NsdRegistrationRecord()
+
+ val discoveryRecord = NsdDiscoveryRecord()
+ tryTest {
+ registerService(registrationRecord1, si1)
+
+ nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD,
+ testNetwork1.network, Executor { it.run() }, discoveryRecord)
+
+ val discoveredInfo1 = discoveryRecord.waitForServiceDiscovered(
+ serviceName, serviceType, testNetwork1.network)
+ val resolvedInfo1 = resolveService(discoveredInfo1)
+
+ assertEquals(serviceName, discoveredInfo1.serviceName)
+ assertEquals(TEST_PORT, resolvedInfo1.port)
+ assertEquals(si1.hostname, resolvedInfo1.hostname)
+ assertAddressEquals(hostAddresses, resolvedInfo1.hostAddresses)
+
+ registerService(registrationRecord2, si2)
+
+ val discoveredInfo2 = discoveryRecord.waitForServiceDiscovered(
+ serviceName2, serviceType, testNetwork1.network)
+ val resolvedInfo2 = resolveService(discoveredInfo2)
+
+ assertEquals(serviceName2, discoveredInfo2.serviceName)
+ assertEquals(TEST_PORT + 1, resolvedInfo2.port)
+ assertEquals(si2.hostname, resolvedInfo2.hostname)
+ assertAddressEquals(hostAddresses, resolvedInfo2.hostAddresses)
+ } cleanupStep {
+ nsdManager.stopServiceDiscovery(discoveryRecord)
+
+ discoveryRecord.expectCallback<DiscoveryStopped>()
+ } cleanup {
+ nsdManager.unregisterService(registrationRecord1)
+ nsdManager.unregisterService(registrationRecord2)
+ }
+ }
+
private fun buildConflictingAnnouncement(): ByteBuffer {
/*
Generated with:
@@ -1436,6 +1909,22 @@
return buildMdnsPacket(mdnsPayload)
}
+ private fun buildConflictingAnnouncementForCustomHost(): ByteBuffer {
+ /*
+ Generated with scapy:
+ raw(DNS(rd=0, qr=1, aa=1, qd = None, an =
+ DNSRR(rrname='NsdTestHost123456789.local', type=28, rclass=1, ttl=120,
+ rdata='2001:db8::321')
+ )).hex()
+ */
+ val mdnsPayload = HexDump.hexStringToByteArray("000084000000000100000000144e7364" +
+ "54657374486f7374313233343536373839056c6f63616c00001c000100000078001020010db80000" +
+ "00000000000000000321")
+ replaceCustomHostnameWithTestSuffix(mdnsPayload)
+
+ return buildMdnsPacket(mdnsPayload)
+ }
+
/**
* Replaces occurrences of "NsdTest123456789" and "_nmt123456789" in mDNS payload with the
* actual random name and type that are used by the test.
@@ -1452,6 +1941,19 @@
replaceAll(packetBuffer, testPacketTypePrefix, encodedTypePrefix)
}
+ /**
+ * Replaces occurrences of "NsdTestHost123456789" in mDNS payload with the
+ * actual random host name that are used by the test.
+ */
+ private fun replaceCustomHostnameWithTestSuffix(mdnsPayload: ByteArray) {
+ // Test custom hostnames have consistent length and are always ASCII
+ val testPacketName = "NsdTestHost123456789".encodeToByteArray()
+ val encodedHostname = customHostname.encodeToByteArray()
+
+ val packetBuffer = ByteBuffer.wrap(mdnsPayload)
+ replaceAll(packetBuffer, testPacketName, encodedHostname)
+ }
+
private tailrec fun replaceAll(buffer: ByteBuffer, source: ByteArray, replacement: ByteArray) {
assertEquals(source.size, replacement.size)
val index = buffer.array().indexOf(source)
@@ -1550,3 +2052,9 @@
if (this == null) return ""
return String(this, StandardCharsets.UTF_8)
}
+
+private fun assertAddressEquals(expected: List<InetAddress>, actual: List<InetAddress>) {
+ // No duplicate addresses in the actual address list
+ assertEquals(actual.toSet().size, actual.size)
+ assertEquals(expected.toSet(), actual.toSet())
+}
\ No newline at end of file
diff --git a/tests/cts/net/src/android/net/cts/OffloadServiceInfoTest.kt b/tests/cts/net/src/android/net/cts/OffloadServiceInfoTest.kt
new file mode 100644
index 0000000..36de4f2
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/OffloadServiceInfoTest.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.cts
+
+import android.net.nsd.OffloadEngine.OFFLOAD_TYPE_FILTER_QUERIES
+import android.net.nsd.OffloadServiceInfo
+import android.os.Build
+import androidx.test.filters.SmallTest
+import com.android.testutils.ConnectivityModuleTest
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import kotlin.test.assertContentEquals
+import kotlin.test.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** CTS tests for {@link OffloadServiceInfo}. */
+@IgnoreUpTo(Build.VERSION_CODES.S_V2)
+@RunWith(DevSdkIgnoreRunner::class)
+@SmallTest
+@ConnectivityModuleTest
+class OffloadServiceInfoTest {
+ @Test
+ fun testCreateOffloadServiceInfo() {
+ val offloadServiceInfo = OffloadServiceInfo(
+ OffloadServiceInfo.Key("_testService", "_testType"),
+ listOf("_sub1", "_sub2"),
+ "Android.local",
+ byteArrayOf(0x1, 0x2, 0x3),
+ 1 /* priority */,
+ OFFLOAD_TYPE_FILTER_QUERIES.toLong()
+ )
+
+ assertEquals(OffloadServiceInfo.Key("_testService", "_testType"), offloadServiceInfo.key)
+ assertEquals(listOf("_sub1", "_sub2"), offloadServiceInfo.subtypes)
+ assertEquals("Android.local", offloadServiceInfo.hostname)
+ assertContentEquals(byteArrayOf(0x1, 0x2, 0x3), offloadServiceInfo.offloadPayload)
+ assertEquals(1, offloadServiceInfo.priority)
+ assertEquals(OFFLOAD_TYPE_FILTER_QUERIES.toLong(), offloadServiceInfo.offloadType)
+ }
+}
diff --git a/tests/cts/net/util/Android.bp b/tests/cts/net/util/Android.bp
index fffd30f..644634b 100644
--- a/tests/cts/net/util/Android.bp
+++ b/tests/cts/net/util/Android.bp
@@ -16,12 +16,16 @@
// Common utilities for cts net tests.
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
java_library {
name: "cts-net-utils",
- srcs: ["java/**/*.java", "java/**/*.kt"],
+ srcs: [
+ "java/**/*.java",
+ "java/**/*.kt",
+ ],
static_libs: [
"compatibility-device-util-axt",
"junit",
diff --git a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
index 96330e2..3d828a4 100644
--- a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
+++ b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
@@ -173,21 +173,39 @@
return cb;
}
- // Toggle WiFi twice, leaving it in the state it started in
- public void toggleWifi() throws Exception {
- if (mWifiManager.isWifiEnabled()) {
- Network wifiNetwork = getWifiNetwork();
- // Ensure system default network is WIFI because it's expected in disconnectFromWifi()
- expectNetworkIsSystemDefault(wifiNetwork);
- disconnectFromWifi(wifiNetwork);
- connectToWifi();
- } else {
- connectToWifi();
- Network wifiNetwork = getWifiNetwork();
- // Ensure system default network is WIFI because it's expected in disconnectFromWifi()
- expectNetworkIsSystemDefault(wifiNetwork);
- disconnectFromWifi(wifiNetwork);
+ /**
+ * Toggle Wi-Fi off and on, waiting for the {@link ConnectivityManager#CONNECTIVITY_ACTION}
+ * broadcast in both cases.
+ */
+ public void reconnectWifiAndWaitForConnectivityAction() throws Exception {
+ assertTrue(mWifiManager.isWifiEnabled());
+ Network wifiNetwork = getWifiNetwork();
+ // Ensure system default network is WIFI because it's expected in disconnectFromWifi()
+ expectNetworkIsSystemDefault(wifiNetwork);
+ disconnectFromWifi(wifiNetwork, true /* expectLegacyBroadcast */);
+ connectToWifi(true /* expectLegacyBroadcast */);
+ }
+
+ /**
+ * Turn Wi-Fi off, then back on and make sure it connects, if it is supported.
+ */
+ public void reconnectWifiIfSupported() throws Exception {
+ if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) {
+ return;
}
+ disableWifi();
+ ensureWifiConnected();
+ }
+
+ /**
+ * Turn cell data off, then back on and make sure it connects, if it is supported.
+ */
+ public void reconnectCellIfSupported() throws Exception {
+ if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+ return;
+ }
+ setMobileDataEnabled(false);
+ setMobileDataEnabled(true);
}
public Network expectNetworkIsSystemDefault(Network network)
diff --git a/tests/cts/netpermission/internetpermission/Android.bp b/tests/cts/netpermission/internetpermission/Android.bp
index 5314396..7d5ca2f 100644
--- a/tests/cts/netpermission/internetpermission/Android.bp
+++ b/tests/cts/netpermission/internetpermission/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/tests/cts/netpermission/updatestatspermission/Android.bp b/tests/cts/netpermission/updatestatspermission/Android.bp
index 40474db..2fde1ce 100644
--- a/tests/cts/netpermission/updatestatspermission/Android.bp
+++ b/tests/cts/netpermission/updatestatspermission/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/tests/cts/tethering/Android.bp b/tests/cts/tethering/Android.bp
index 4284f56..3928961 100644
--- a/tests/cts/tethering/Android.bp
+++ b/tests/cts/tethering/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/tests/deflake/Android.bp b/tests/deflake/Android.bp
index 8205f1c..726e504 100644
--- a/tests/deflake/Android.bp
+++ b/tests/deflake/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_fwk_core_networking",
// See: http://go/android-license-faq
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/tests/integration/Android.bp b/tests/integration/Android.bp
index f705e34..349529dd 100644
--- a/tests/integration/Android.bp
+++ b/tests/integration/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_fwk_core_networking",
// See: http://go/android-license-faq
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -74,7 +75,10 @@
java_library {
name: "frameworks-net-integration-testutils",
defaults: ["framework-connectivity-test-defaults"],
- srcs: ["util/**/*.java", "util/**/*.kt"],
+ srcs: [
+ "util/**/*.java",
+ "util/**/*.kt",
+ ],
static_libs: [
"androidx.annotation_annotation",
"androidx.test.rules",
diff --git a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
index 9b1bf6e..9148770 100644
--- a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
+++ b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
@@ -56,10 +56,12 @@
import com.android.server.NetworkAgentWrapper
import com.android.server.TestNetIdManager
import com.android.server.connectivity.CarrierPrivilegeAuthenticator
+import com.android.server.connectivity.CarrierPrivilegeAuthenticator.CarrierPrivilegesLostListener
import com.android.server.connectivity.ConnectivityResources
import com.android.server.connectivity.MockableSystemProperties
import com.android.server.connectivity.MultinetworkPolicyTracker
import com.android.server.connectivity.ProxyTracker
+import com.android.server.connectivity.SatelliteAccessController
import com.android.testutils.DevSdkIgnoreRunner
import com.android.testutils.DeviceInfoUtils
import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
@@ -86,6 +88,7 @@
import org.mockito.Mockito.mock
import org.mockito.MockitoAnnotations
import org.mockito.Spy
+import java.util.function.Consumer
const val SERVICE_BIND_TIMEOUT_MS = 5_000L
const val TEST_TIMEOUT_MS = 10_000L
@@ -240,15 +243,25 @@
override fun makeCarrierPrivilegeAuthenticator(
context: Context,
- tm: TelephonyManager
+ tm: TelephonyManager,
+ requestRestrictedWifiEnabled: Boolean,
+ listener: CarrierPrivilegesLostListener
): CarrierPrivilegeAuthenticator {
return CarrierPrivilegeAuthenticator(context,
object : CarrierPrivilegeAuthenticator.Dependencies() {
override fun makeHandlerThread(): HandlerThread =
super.makeHandlerThread().also { handlerThreads.add(it) }
},
- tm, TelephonyManagerShimImpl.newInstance(tm))
+ tm, TelephonyManagerShimImpl.newInstance(tm),
+ requestRestrictedWifiEnabled, listener)
}
+
+ override fun makeSatelliteAccessController(
+ context: Context,
+ updateSatellitePreferredUid: Consumer<MutableSet<Int>>?,
+ connectivityServiceInternalHandler: Handler
+ ): SatelliteAccessController? = mock(
+ SatelliteAccessController::class.java)
}
@After
diff --git a/tests/mts/Android.bp b/tests/mts/Android.bp
index 6425223..336be2e 100644
--- a/tests/mts/Android.bp
+++ b/tests/mts/Android.bp
@@ -14,6 +14,7 @@
//
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -38,5 +39,5 @@
"bpf_existence_test.cpp",
],
compile_multilib: "first",
- min_sdk_version: "30", // Ensure test runs on R and above.
+ min_sdk_version: "30", // Ensure test runs on R and above.
}
diff --git a/tests/native/connectivity_native_test/Android.bp b/tests/native/connectivity_native_test/Android.bp
index 8825aa4..2f66d17 100644
--- a/tests/native/connectivity_native_test/Android.bp
+++ b/tests/native/connectivity_native_test/Android.bp
@@ -1,4 +1,5 @@
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/tests/native/utilities/Android.bp b/tests/native/utilities/Android.bp
index 4706b3d..48a5414 100644
--- a/tests/native/utilities/Android.bp
+++ b/tests/native/utilities/Android.bp
@@ -14,14 +14,17 @@
// limitations under the License.
package {
+ default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
+// TODO: delete this as it is a cross-module api boundary violation
cc_test_library {
name: "libconnectivity_native_test_utils",
+ visibility: ["//packages/modules/DnsResolver/tests:__subpackages__"],
defaults: [
"netd_defaults",
- "resolv_test_defaults"
+ "resolv_test_defaults",
],
srcs: [
"firewall.cpp",
diff --git a/tests/smoketest/Android.bp b/tests/smoketest/Android.bp
index 4ab24fc..121efa1 100644
--- a/tests/smoketest/Android.bp
+++ b/tests/smoketest/Android.bp
@@ -10,6 +10,7 @@
// TODO: remove this hack when there is a better solution for jni_libs that includes
// dependent libraries.
package {
+ default_team: "trendy_team_fwk_core_networking",
// See: http://go/android-license-faq
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index 8b286a0..4a1298f 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -2,6 +2,7 @@
// Build FrameworksNetTests package
//########################################################################
package {
+ default_team: "trendy_team_fwk_core_networking",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "Android-Apache-2.0"
@@ -73,7 +74,7 @@
"java/com/android/server/connectivity/NetdEventListenerServiceTest.java",
"java/com/android/server/connectivity/VpnTest.java",
"java/com/android/server/net/ipmemorystore/*.java",
- ]
+ ],
}
// Subset of services-core used to by ConnectivityService tests to test VPN realistically.
@@ -115,7 +116,7 @@
"service-connectivity-tiramisu-pre-jarjar",
"services.core-vpn",
"testables",
- "cts-net-utils"
+ "cts-net-utils",
],
libs: [
"android.net.ipsec.ike.stubs.module_lib",
diff --git a/tests/unit/java/android/net/NetworkUtilsTest.java b/tests/unit/java/android/net/NetworkUtilsTest.java
index 2bf2211..e453c02 100644
--- a/tests/unit/java/android/net/NetworkUtilsTest.java
+++ b/tests/unit/java/android/net/NetworkUtilsTest.java
@@ -159,10 +159,9 @@
return timeval;
}
- @Test
- public void testSetSockOptBytes() throws ErrnoException {
- final FileDescriptor sock = Os.socket(AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6);
- final StructTimeval writeTimeval = StructTimeval.fromMillis(1200);
+ private void testSetSockOptBytes(FileDescriptor sock, long timeValMillis)
+ throws ErrnoException {
+ final StructTimeval writeTimeval = StructTimeval.fromMillis(timeValMillis);
byte[] timeval = getTimevalBytes(writeTimeval);
final StructTimeval readTimeval;
@@ -170,6 +169,16 @@
readTimeval = Os.getsockoptTimeval(sock, SOL_SOCKET, SO_RCVTIMEO);
assertEquals(writeTimeval, readTimeval);
+ }
+
+ @Test
+ public void testSetSockOptBytes() throws ErrnoException {
+ final FileDescriptor sock = Os.socket(AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6);
+
+ testSetSockOptBytes(sock, 3000);
+
+ testSetSockOptBytes(sock, 5000);
+
SocketUtils.closeSocketQuietly(sock);
}
diff --git a/tests/unit/java/android/net/nsd/NsdManagerTest.java b/tests/unit/java/android/net/nsd/NsdManagerTest.java
index 461ead8..951675c 100644
--- a/tests/unit/java/android/net/nsd/NsdManagerTest.java
+++ b/tests/unit/java/android/net/nsd/NsdManagerTest.java
@@ -52,6 +52,9 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.net.InetAddress;
+import java.util.List;
+
@DevSdkIgnoreRunner.MonitorThreadLeak
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
@@ -285,6 +288,7 @@
private void doTestDiscoverService() throws Exception {
NsdManager manager = mManager;
+ DiscoveryRequest request1 = new DiscoveryRequest.Builder("a_type").build();
NsdServiceInfo reply1 = new NsdServiceInfo("a_name", "a_type");
NsdServiceInfo reply2 = new NsdServiceInfo("another_name", "a_type");
NsdServiceInfo reply3 = new NsdServiceInfo("a_third_name", "a_type");
@@ -305,7 +309,7 @@
int key2 = getRequestKey(req ->
verify(mServiceConn, times(2)).discoverServices(req.capture(), any()));
- mCallback.onDiscoverServicesStarted(key2, reply1);
+ mCallback.onDiscoverServicesStarted(key2, request1);
verify(listener, timeout(mTimeoutMs).times(1)).onDiscoveryStarted("a_type");
@@ -345,7 +349,7 @@
int key3 = getRequestKey(req ->
verify(mServiceConn, times(3)).discoverServices(req.capture(), any()));
- mCallback.onDiscoverServicesStarted(key3, reply1);
+ mCallback.onDiscoverServicesStarted(key3, request1);
verify(listener, timeout(mTimeoutMs).times(1)).onDiscoveryStarted("a_type");
// Client unregisters immediately, it fails
@@ -369,6 +373,9 @@
NsdManager.RegistrationListener listener1 = mock(NsdManager.RegistrationListener.class);
NsdManager.DiscoveryListener listener2 = mock(NsdManager.DiscoveryListener.class);
NsdManager.ResolveListener listener3 = mock(NsdManager.ResolveListener.class);
+ NsdManager.RegistrationListener listener4 = mock(NsdManager.RegistrationListener.class);
+ NsdManager.RegistrationListener listener5 = mock(NsdManager.RegistrationListener.class);
+ NsdManager.RegistrationListener listener6 = mock(NsdManager.RegistrationListener.class);
NsdServiceInfo invalidService = new NsdServiceInfo(null, null);
NsdServiceInfo validService = new NsdServiceInfo("a_name", "_a_type._tcp");
@@ -378,6 +385,7 @@
"_a_type._tcp,_sub1,_s2");
NsdServiceInfo otherSubtypeUpdate = new NsdServiceInfo("a_name", "_a_type._tcp,_sub1,_s3");
NsdServiceInfo dotSyntaxSubtypeUpdate = new NsdServiceInfo("a_name", "_sub1._a_type._tcp");
+
validService.setPort(2222);
otherServiceWithSubtype.setPort(2222);
validServiceDuplicate.setPort(2222);
@@ -385,6 +393,33 @@
otherSubtypeUpdate.setPort(2222);
dotSyntaxSubtypeUpdate.setPort(2222);
+ NsdServiceInfo invalidMissingHostnameWithAddresses = new NsdServiceInfo(null, null);
+ invalidMissingHostnameWithAddresses.setHostAddresses(
+ List.of(
+ InetAddress.parseNumericAddress("192.168.82.14"),
+ InetAddress.parseNumericAddress("2001::1")));
+
+ NsdServiceInfo validCustomHostWithAddresses = new NsdServiceInfo(null, null);
+ validCustomHostWithAddresses.setHostname("a_host");
+ validCustomHostWithAddresses.setHostAddresses(
+ List.of(
+ InetAddress.parseNumericAddress("192.168.82.14"),
+ InetAddress.parseNumericAddress("2001::1")));
+
+ NsdServiceInfo validServiceWithCustomHostAndAddresses =
+ new NsdServiceInfo("a_name", "_a_type._tcp");
+ validServiceWithCustomHostAndAddresses.setPort(2222);
+ validServiceWithCustomHostAndAddresses.setHostname("a_host");
+ validServiceWithCustomHostAndAddresses.setHostAddresses(
+ List.of(
+ InetAddress.parseNumericAddress("192.168.82.14"),
+ InetAddress.parseNumericAddress("2001::1")));
+
+ NsdServiceInfo validServiceWithCustomHostNoAddresses =
+ new NsdServiceInfo("a_name", "_a_type._tcp");
+ validServiceWithCustomHostNoAddresses.setPort(2222);
+ validServiceWithCustomHostNoAddresses.setHostname("a_host");
+
// Service registration
// - invalid arguments
mustFail(() -> { manager.unregisterService(null); });
@@ -393,6 +428,8 @@
mustFail(() -> { manager.registerService(invalidService, PROTOCOL, listener1); });
mustFail(() -> { manager.registerService(validService, -1, listener1); });
mustFail(() -> { manager.registerService(validService, PROTOCOL, null); });
+ mustFail(() -> {
+ manager.registerService(invalidMissingHostnameWithAddresses, PROTOCOL, listener1); });
manager.registerService(validService, PROTOCOL, listener1);
// - update without subtype is not allowed
mustFail(() -> { manager.registerService(validServiceDuplicate, PROTOCOL, listener1); });
@@ -414,6 +451,15 @@
// TODO: make listener immediately reusable
//mustFail(() -> { manager.unregisterService(listener1); });
//manager.registerService(validService, PROTOCOL, listener1);
+ // - registering a custom host without a service is valid
+ manager.registerService(validCustomHostWithAddresses, PROTOCOL, listener4);
+ manager.unregisterService(listener4);
+ // - registering a service with a custom host is valid
+ manager.registerService(validServiceWithCustomHostAndAddresses, PROTOCOL, listener5);
+ manager.unregisterService(listener5);
+ // - registering a service with a custom host with no addresses is valid
+ manager.registerService(validServiceWithCustomHostNoAddresses, PROTOCOL, listener6);
+ manager.unregisterService(listener6);
// Discover service
// - invalid arguments
diff --git a/tests/unit/java/android/net/util/KeepaliveUtilsTest.kt b/tests/unit/java/android/net/util/KeepaliveUtilsTest.kt
index cb3a315..470274d 100644
--- a/tests/unit/java/android/net/util/KeepaliveUtilsTest.kt
+++ b/tests/unit/java/android/net/util/KeepaliveUtilsTest.kt
@@ -95,11 +95,11 @@
// Check resource with invalid transport type.
assertRunWithException(arrayOf("-1,3"))
- assertRunWithException(arrayOf("10,3"))
+ assertRunWithException(arrayOf("11,3"))
// Check valid customization generates expected array.
val validRes = arrayOf("0,3", "1,0", "4,4")
- val expectedValidRes = intArrayOf(3, 0, 0, 0, 4, 0, 0, 0, 0, 0)
+ val expectedValidRes = intArrayOf(3, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0)
val mockContext = getMockedContextWithStringArrayRes(
R.array.config_networkSupportedKeepaliveCount,
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 7f821dd..6623bbd 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -157,6 +157,7 @@
import static android.telephony.DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH;
import static android.telephony.DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
+import static com.android.server.ConnectivityService.ALLOW_SATALLITE_NETWORK_FALLBACK;
import static com.android.server.ConnectivityService.DELAY_DESTROY_FROZEN_SOCKETS_VERSION;
import static com.android.net.module.util.DeviceConfigUtils.TETHERING_MODULE_NAME;
import static com.android.server.ConnectivityService.ALLOW_SYSUI_CONNECTIVITY_REPORTS;
@@ -172,6 +173,7 @@
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.server.connectivity.CarrierPrivilegeAuthenticator.CarrierPrivilegesLostListener;
import static com.android.testutils.Cleanup.testAndCleanup;
import static com.android.testutils.ConcurrentUtils.await;
import static com.android.testutils.ConcurrentUtils.durationOf;
@@ -420,6 +422,7 @@
import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
import com.android.server.connectivity.ProxyTracker;
import com.android.server.connectivity.QosCallbackTracker;
+import com.android.server.connectivity.SatelliteAccessController;
import com.android.server.connectivity.TcpKeepaliveController;
import com.android.server.connectivity.UidRangeUtils;
import com.android.server.connectivity.VpnProfileStore;
@@ -641,6 +644,7 @@
@Mock DestroySocketsWrapper mDestroySocketsWrapper;
@Mock SubscriptionManager mSubscriptionManager;
@Mock KeepaliveTracker.Dependencies mMockKeepaliveTrackerDependencies;
+ @Mock SatelliteAccessController mSatelliteAccessController;
// BatteryStatsManager is final and cannot be mocked with regular mockito, so just mock the
// underlying binder calls.
@@ -2053,11 +2057,21 @@
@Override
public CarrierPrivilegeAuthenticator makeCarrierPrivilegeAuthenticator(
@NonNull final Context context,
- @NonNull final TelephonyManager tm) {
+ @NonNull final TelephonyManager tm,
+ final boolean requestRestrictedWifiEnabled,
+ CarrierPrivilegesLostListener listener) {
return mDeps.isAtLeastT() ? mCarrierPrivilegeAuthenticator : null;
}
@Override
+ public SatelliteAccessController makeSatelliteAccessController(
+ @NonNull final Context context,
+ Consumer<Set<Integer>> updateSatelliteNetworkFallbackUidCallback,
+ @NonNull final Handler connectivityServiceInternalHandler) {
+ return mSatelliteAccessController;
+ }
+
+ @Override
public boolean intentFilterEquals(final PendingIntent a, final PendingIntent b) {
return runAsShell(GET_INTENT_SENDER_INTENT, () -> a.intentFilterEquals(b));
}
@@ -2147,6 +2161,8 @@
case ConnectivityFlags.NO_REMATCH_ALL_REQUESTS_ON_REGISTER:
case ConnectivityFlags.CARRIER_SERVICE_CHANGED_USE_CALLBACK:
return true;
+ case ConnectivityFlags.REQUEST_RESTRICTED_WIFI:
+ return true;
case KEY_DESTROY_FROZEN_SOCKETS_VERSION:
return true;
case DELAY_DESTROY_FROZEN_SOCKETS_VERSION:
@@ -2163,6 +2179,8 @@
return true;
case LOG_BPF_RC:
return true;
+ case ALLOW_SATALLITE_NETWORK_FALLBACK:
+ return true;
default:
return super.isFeatureNotChickenedOut(context, name);
}
@@ -12929,7 +12947,7 @@
mServiceContext.setPermission(NETWORK_STACK, PERMISSION_GRANTED);
assertTrue(
"NetworkStack permission not applied",
- mService.checkConnectivityDiagnosticsPermissions(
+ mService.hasConnectivityDiagnosticsPermissions(
Process.myPid(), Process.myUid(), naiWithoutUid,
mContext.getOpPackageName()));
}
@@ -12941,7 +12959,7 @@
mServiceContext.setPermission(STATUS_BAR_SERVICE, PERMISSION_GRANTED);
assertTrue(
"SysUi permission (STATUS_BAR_SERVICE) not applied",
- mService.checkConnectivityDiagnosticsPermissions(
+ mService.hasConnectivityDiagnosticsPermissions(
Process.myPid(), Process.myUid(), naiWithoutUid,
mContext.getOpPackageName()));
}
@@ -12958,7 +12976,7 @@
assertFalse(
"Mismatched uid/package name should not pass the location permission check",
- mService.checkConnectivityDiagnosticsPermissions(
+ mService.hasConnectivityDiagnosticsPermissions(
Process.myPid() + 1, wrongUid, naiWithUid, mContext.getOpPackageName()));
}
@@ -12969,7 +12987,7 @@
assertEquals(
"Unexpected ConnDiags permission",
expectPermission,
- mService.checkConnectivityDiagnosticsPermissions(
+ mService.hasConnectivityDiagnosticsPermissions(
Process.myPid(), Process.myUid(), info, mContext.getOpPackageName()));
}
@@ -13011,7 +13029,7 @@
waitForIdle();
assertTrue(
"Active VPN permission not applied",
- mService.checkConnectivityDiagnosticsPermissions(
+ mService.hasConnectivityDiagnosticsPermissions(
Process.myPid(), Process.myUid(), naiWithoutUid,
mContext.getOpPackageName()));
@@ -13019,7 +13037,7 @@
waitForIdle();
assertFalse(
"VPN shouldn't receive callback on non-underlying network",
- mService.checkConnectivityDiagnosticsPermissions(
+ mService.hasConnectivityDiagnosticsPermissions(
Process.myPid(), Process.myUid(), naiWithoutUid,
mContext.getOpPackageName()));
}
@@ -13036,7 +13054,7 @@
assertTrue(
"NetworkCapabilities administrator uid permission not applied",
- mService.checkConnectivityDiagnosticsPermissions(
+ mService.hasConnectivityDiagnosticsPermissions(
Process.myPid(), Process.myUid(), naiWithUid, mContext.getOpPackageName()));
}
@@ -13054,7 +13072,7 @@
// Use wrong pid and uid
assertFalse(
"Permissions allowed when they shouldn't be granted",
- mService.checkConnectivityDiagnosticsPermissions(
+ mService.hasConnectivityDiagnosticsPermissions(
Process.myPid() + 1, Process.myUid() + 1, naiWithUid,
mContext.getOpPackageName()));
}
@@ -17353,6 +17371,14 @@
.build();
}
+ private NetworkRequest getRestrictedRequestForWifiWithSubIds() {
+ return new NetworkRequest.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+ .setSubscriptionIds(Collections.singleton(Process.myUid()))
+ .build();
+ }
+
@Test
public void testNetworkRequestWithSubIdsWithNetworkFactoryPermission() throws Exception {
mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_GRANTED);
@@ -17386,6 +17412,134 @@
}
@Test
+ @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+ public void testCarrierConfigAppSendNetworkRequestForRestrictedWifi() throws Exception {
+ mServiceContext.setPermission(CONNECTIVITY_USE_RESTRICTED_NETWORKS, PERMISSION_DENIED);
+ doReturn(true).when(mCarrierPrivilegeAuthenticator)
+ .isCarrierServiceUidForNetworkCapabilities(anyInt(), any());
+ final PendingIntent pendingIntent = PendingIntent.getBroadcast(
+ mContext, 0 /* requestCode */, new Intent("a"), FLAG_IMMUTABLE);
+ final NetworkCallback networkCallback1 = new NetworkCallback();
+ final NetworkCallback networkCallback2 = new NetworkCallback();
+
+ mCm.requestNetwork(getRestrictedRequestForWifiWithSubIds(), networkCallback1);
+ mCm.requestNetwork(getRestrictedRequestForWifiWithSubIds(), pendingIntent);
+ mCm.registerNetworkCallback(getRestrictedRequestForWifiWithSubIds(), networkCallback2);
+
+ mCm.unregisterNetworkCallback(networkCallback1);
+ mCm.releaseNetworkRequest(pendingIntent);
+ mCm.unregisterNetworkCallback(networkCallback2);
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+ public void testRestrictedRequestRemovedDueToCarrierPrivilegesLost() throws Exception {
+ mServiceContext.setPermission(CONNECTIVITY_USE_RESTRICTED_NETWORKS, PERMISSION_DENIED);
+ NetworkCapabilities filter = getRestrictedRequestForWifiWithSubIds().networkCapabilities;
+ final HandlerThread handlerThread = new HandlerThread("testRestrictedFactoryRequests");
+ handlerThread.start();
+ final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(),
+ mServiceContext, "testFactory", filter, mCsHandlerThread);
+ testFactory.register();
+
+ testFactory.assertRequestCountEquals(0);
+ doReturn(true).when(mCarrierPrivilegeAuthenticator)
+ .isCarrierServiceUidForNetworkCapabilities(eq(Process.myUid()), any());
+ final TestNetworkCallback networkCallback1 = new TestNetworkCallback();
+ final NetworkRequest networkrequest1 = getRestrictedRequestForWifiWithSubIds();
+ mCm.requestNetwork(networkrequest1, networkCallback1);
+ testFactory.expectRequestAdd();
+ testFactory.assertRequestCountEquals(1);
+
+ NetworkCapabilities nc = new NetworkCapabilities.Builder(filter)
+ .setAllowedUids(Set.of(Process.myUid()))
+ .build();
+ mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, new LinkProperties(), nc);
+ mWiFiAgent.connect(true);
+ networkCallback1.expectAvailableThenValidatedCallbacks(mWiFiAgent);
+
+ final NetworkAgentInfo nai = mService.getNetworkAgentInfoForNetwork(
+ mWiFiAgent.getNetwork());
+
+ doReturn(false).when(mCarrierPrivilegeAuthenticator)
+ .isCarrierServiceUidForNetworkCapabilities(eq(Process.myUid()), any());
+ final CarrierPrivilegesLostListener carrierPrivilegesLostListener =
+ mService.getCarrierPrivilegesLostListener();
+ carrierPrivilegesLostListener.onCarrierPrivilegesLost(Process.myUid());
+ waitForIdle();
+
+ testFactory.expectRequestRemove();
+ testFactory.assertRequestCountEquals(0);
+ assertTrue(nai.networkCapabilities.getAllowedUidsNoCopy().isEmpty());
+ networkCallback1.expect(NETWORK_CAPS_UPDATED);
+ networkCallback1.expect(UNAVAILABLE);
+
+ handlerThread.quitSafely();
+ handlerThread.join();
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+ public void testRequestNotRemoved_MismatchUid() throws Exception {
+ mServiceContext.setPermission(CONNECTIVITY_USE_RESTRICTED_NETWORKS, PERMISSION_DENIED);
+ NetworkCapabilities filter = getRestrictedRequestForWifiWithSubIds().networkCapabilities;
+ final HandlerThread handlerThread = new HandlerThread("testRestrictedFactoryRequests");
+ handlerThread.start();
+
+ final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(),
+ mServiceContext, "testFactory", filter, mCsHandlerThread);
+ testFactory.register();
+
+ doReturn(true).when(mCarrierPrivilegeAuthenticator)
+ .isCarrierServiceUidForNetworkCapabilities(anyInt(), any());
+ final TestNetworkCallback networkCallback1 = new TestNetworkCallback();
+ final NetworkRequest networkrequest1 = getRestrictedRequestForWifiWithSubIds();
+ mCm.requestNetwork(networkrequest1, networkCallback1);
+ testFactory.expectRequestAdd();
+ testFactory.assertRequestCountEquals(1);
+
+ doReturn(false).when(mCarrierPrivilegeAuthenticator)
+ .isCarrierServiceUidForNetworkCapabilities(eq(Process.myUid()), any());
+ final CarrierPrivilegesLostListener carrierPrivilegesLostListener =
+ mService.getCarrierPrivilegesLostListener();
+ carrierPrivilegesLostListener.onCarrierPrivilegesLost(Process.myUid() + 1);
+ expectNoRequestChanged(testFactory);
+
+ handlerThread.quitSafely();
+ handlerThread.join();
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+ public void testRequestNotRemoved_HasRestrictedNetworkPermission() throws Exception {
+ mServiceContext.setPermission(CONNECTIVITY_USE_RESTRICTED_NETWORKS, PERMISSION_GRANTED);
+ NetworkCapabilities filter = getRestrictedRequestForWifiWithSubIds().networkCapabilities;
+ final HandlerThread handlerThread = new HandlerThread("testRestrictedFactoryRequests");
+ handlerThread.start();
+
+ final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(),
+ mServiceContext, "testFactory", filter, mCsHandlerThread);
+ testFactory.register();
+
+ doReturn(true).when(mCarrierPrivilegeAuthenticator)
+ .isCarrierServiceUidForNetworkCapabilities(anyInt(), any());
+ final TestNetworkCallback networkCallback1 = new TestNetworkCallback();
+ final NetworkRequest networkrequest1 = getRestrictedRequestForWifiWithSubIds();
+ mCm.requestNetwork(networkrequest1, networkCallback1);
+ testFactory.expectRequestAdd();
+ testFactory.assertRequestCountEquals(1);
+
+ doReturn(false).when(mCarrierPrivilegeAuthenticator)
+ .isCarrierServiceUidForNetworkCapabilities(eq(Process.myUid()), any());
+ final CarrierPrivilegesLostListener carrierPrivilegesLostListener =
+ mService.getCarrierPrivilegesLostListener();
+ carrierPrivilegesLostListener.onCarrierPrivilegesLost(Process.myUid());
+ expectNoRequestChanged(testFactory);
+
+ handlerThread.quitSafely();
+ handlerThread.join();
+ }
+ @Test
public void testAllowedUids() throws Exception {
final int preferenceOrder =
ConnectivityService.PREFERENCE_ORDER_IRRELEVANT_BECAUSE_NOT_DEFAULT;
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index 87e7967..624855e 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -34,6 +34,7 @@
import static android.net.connectivity.ConnectivityCompatChanges.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER;
import static android.net.nsd.NsdManager.FAILURE_BAD_PARAMETERS;
import static android.net.nsd.NsdManager.FAILURE_INTERNAL_ERROR;
+import static android.net.nsd.NsdManager.FAILURE_MAX_LIMIT;
import static android.net.nsd.NsdManager.FAILURE_OPERATION_NOT_RUNNING;
import static com.android.networkstack.apishim.api33.ConstantsShim.REGISTER_NSD_OFFLOAD_ENGINE;
@@ -131,10 +132,12 @@
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.net.InetAddress;
import java.net.UnknownHostException;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
@@ -234,7 +237,8 @@
doReturn(mSocketProvider).when(mDeps).makeMdnsSocketProvider(any(), any(), any(), any());
doReturn(DEFAULT_RUNNING_APP_ACTIVE_IMPORTANCE_CUTOFF).when(mDeps).getDeviceConfigInt(
eq(NsdService.MDNS_CONFIG_RUNNING_APP_ACTIVE_IMPORTANCE_CUTOFF), anyInt());
- doReturn(mAdvertiser).when(mDeps).makeMdnsAdvertiser(any(), any(), any(), any(), any());
+ doReturn(mAdvertiser).when(mDeps).makeMdnsAdvertiser(any(), any(), any(), any(), any(),
+ any());
doReturn(mMetrics).when(mDeps).makeNetworkNsdReportedMetrics(anyInt());
doReturn(mClock).when(mDeps).makeClock();
doReturn(TEST_TIME_MS).when(mClock).elapsedRealtime();
@@ -256,6 +260,10 @@
mThread.quitSafely();
mThread.join();
}
+
+ // Clear inline mocks as there are possible memory leaks if not done (see mockito
+ // doc for clearInlineMocks), and some tests create many of them.
+ Mockito.framework().clearInlineMocks();
}
// Native mdns provided by Netd is removed after U.
@@ -716,6 +724,86 @@
true /* isLegacy */, getAddrId, 10L /* durationMs */);
}
+ @EnableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
+ @Test
+ public void testPerClientListenerLimit() throws Exception {
+ final NsdManager client1 = connectClient(mService);
+ final NsdManager client2 = connectClient(mService);
+
+ final String testType1 = "_testtype1._tcp";
+ final NsdServiceInfo testServiceInfo1 = new NsdServiceInfo("MyTestService1", testType1);
+ testServiceInfo1.setPort(12345);
+ final String testType2 = "_testtype2._tcp";
+ final NsdServiceInfo testServiceInfo2 = new NsdServiceInfo("MyTestService2", testType2);
+ testServiceInfo2.setPort(12345);
+
+ // Each client can register 200 requests (for example 100 discover and 100 register).
+ final int numEachListener = 100;
+ final ArrayList<DiscoveryListener> discListeners = new ArrayList<>(numEachListener);
+ final ArrayList<RegistrationListener> regListeners = new ArrayList<>(numEachListener);
+ for (int i = 0; i < numEachListener; i++) {
+ final DiscoveryListener discListener1 = mock(DiscoveryListener.class);
+ discListeners.add(discListener1);
+ final RegistrationListener regListener1 = mock(RegistrationListener.class);
+ regListeners.add(regListener1);
+ final DiscoveryListener discListener2 = mock(DiscoveryListener.class);
+ discListeners.add(discListener2);
+ final RegistrationListener regListener2 = mock(RegistrationListener.class);
+ regListeners.add(regListener2);
+ client1.discoverServices(testType1, NsdManager.PROTOCOL_DNS_SD,
+ (Network) null, Runnable::run, discListener1);
+ client1.registerService(testServiceInfo1, NsdManager.PROTOCOL_DNS_SD, Runnable::run,
+ regListener1);
+
+ client2.registerService(testServiceInfo2, NsdManager.PROTOCOL_DNS_SD, Runnable::run,
+ regListener2);
+ client2.discoverServices(testType2, NsdManager.PROTOCOL_DNS_SD,
+ (Network) null, Runnable::run, discListener2);
+ }
+
+ // Use a longer timeout than usual for the handler to process all the events. The
+ // registrations take about 1s on a high-end 2013 device.
+ HandlerUtils.waitForIdle(mHandler, 30_000L);
+ for (int i = 0; i < discListeners.size(); i++) {
+ // Callbacks are sent on the manager handler which is different from mHandler, so use
+ // a short timeout (each callback should come quickly after the previous one).
+ verify(discListeners.get(i), timeout(TEST_TIME_MS))
+ .onDiscoveryStarted(i % 2 == 0 ? testType1 : testType2);
+
+ // registerService does not get a callback before probing finishes (will not happen as
+ // this is mocked)
+ verifyNoMoreInteractions(regListeners.get(i));
+ }
+
+ // The next registrations should fail
+ final DiscoveryListener failDiscListener1 = mock(DiscoveryListener.class);
+ final RegistrationListener failRegListener1 = mock(RegistrationListener.class);
+ final DiscoveryListener failDiscListener2 = mock(DiscoveryListener.class);
+ final RegistrationListener failRegListener2 = mock(RegistrationListener.class);
+
+ client1.discoverServices(testType1, NsdManager.PROTOCOL_DNS_SD,
+ (Network) null, Runnable::run, failDiscListener1);
+ verify(failDiscListener1, timeout(TEST_TIME_MS))
+ .onStartDiscoveryFailed(testType1, FAILURE_MAX_LIMIT);
+
+ client1.registerService(testServiceInfo1, NsdManager.PROTOCOL_DNS_SD, Runnable::run,
+ failRegListener1);
+ verify(failRegListener1, timeout(TEST_TIME_MS)).onRegistrationFailed(
+ argThat(a -> testServiceInfo1.getServiceName().equals(a.getServiceName())),
+ eq(FAILURE_MAX_LIMIT));
+
+ client1.discoverServices(testType2, NsdManager.PROTOCOL_DNS_SD,
+ (Network) null, Runnable::run, failDiscListener2);
+ verify(failDiscListener2, timeout(TEST_TIME_MS))
+ .onStartDiscoveryFailed(testType2, FAILURE_MAX_LIMIT);
+
+ client1.registerService(testServiceInfo2, NsdManager.PROTOCOL_DNS_SD, Runnable::run,
+ failRegListener2);
+ verify(failRegListener2, timeout(TEST_TIME_MS)).onRegistrationFailed(
+ argThat(a -> testServiceInfo2.getServiceName().equals(a.getServiceName())),
+ eq(FAILURE_MAX_LIMIT));
+ }
+
@Test
@DisableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
@DevSdkIgnoreRule.IgnoreAfter(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@@ -1138,7 +1226,7 @@
verify(mAdvertiser).addOrUpdateService(anyInt(), argThat(s ->
"Instance".equals(s.getServiceName())
&& SERVICE_TYPE.equals(s.getServiceType())
- && s.getSubtypes().equals(Set.of("_subtype"))), any());
+ && s.getSubtypes().equals(Set.of("_subtype"))), any(), anyInt());
final DiscoveryListener discListener = mock(DiscoveryListener.class);
client.discoverServices(typeWithSubtype, PROTOCOL, network, Runnable::run, discListener);
@@ -1245,7 +1333,7 @@
final ArgumentCaptor<Integer> serviceIdCaptor = ArgumentCaptor.forClass(Integer.class);
verify(mAdvertiser).addOrUpdateService(serviceIdCaptor.capture(),
- argThat(info -> matches(info, regInfo)), any());
+ argThat(info -> matches(info, regInfo)), any(), anyInt());
client.unregisterService(regListenerWithoutFeature);
waitForIdle();
@@ -1274,7 +1362,7 @@
service1.setHostAddresses(List.of(parseNumericAddress("2001:db8::123")));
service1.setPort(1234);
final NsdServiceInfo service2 = new NsdServiceInfo(SERVICE_NAME, "_type2._tcp");
- service2.setHostAddresses(List.of(parseNumericAddress("2001:db8::123")));
+ service1.setHostAddresses(List.of(parseNumericAddress("2001:db8::123")));
service2.setPort(1234);
client.discoverServices(service1.getServiceType(),
@@ -1306,9 +1394,9 @@
// The advertiser is enabled for _type2 but not _type1
verify(mAdvertiser, never()).addOrUpdateService(anyInt(),
- argThat(info -> matches(info, service1)), any());
+ argThat(info -> matches(info, service1)), any(), anyInt());
verify(mAdvertiser).addOrUpdateService(anyInt(), argThat(info -> matches(info, service2)),
- any());
+ any(), anyInt());
}
@Test
@@ -1320,7 +1408,7 @@
// final String serviceTypeWithLocalDomain = SERVICE_TYPE + ".local";
final ArgumentCaptor<MdnsAdvertiser.AdvertiserCallback> cbCaptor =
ArgumentCaptor.forClass(MdnsAdvertiser.AdvertiserCallback.class);
- verify(mDeps).makeMdnsAdvertiser(any(), any(), cbCaptor.capture(), any(), any());
+ verify(mDeps).makeMdnsAdvertiser(any(), any(), cbCaptor.capture(), any(), any(), any());
final NsdServiceInfo regInfo = new NsdServiceInfo(SERVICE_NAME, SERVICE_TYPE);
regInfo.setHost(parseNumericAddress("192.0.2.123"));
@@ -1333,7 +1421,7 @@
verify(mSocketProvider).startMonitoringSockets();
final ArgumentCaptor<Integer> idCaptor = ArgumentCaptor.forClass(Integer.class);
verify(mAdvertiser).addOrUpdateService(idCaptor.capture(), argThat(info ->
- matches(info, regInfo)), any());
+ matches(info, regInfo)), any(), anyInt());
// Verify onServiceRegistered callback
final MdnsAdvertiser.AdvertiserCallback cb = cbCaptor.getValue();
@@ -1371,7 +1459,7 @@
// final String serviceTypeWithLocalDomain = SERVICE_TYPE + ".local";
final ArgumentCaptor<MdnsAdvertiser.AdvertiserCallback> cbCaptor =
ArgumentCaptor.forClass(MdnsAdvertiser.AdvertiserCallback.class);
- verify(mDeps).makeMdnsAdvertiser(any(), any(), cbCaptor.capture(), any(), any());
+ verify(mDeps).makeMdnsAdvertiser(any(), any(), cbCaptor.capture(), any(), any(), any());
final NsdServiceInfo regInfo = new NsdServiceInfo(SERVICE_NAME, "invalid_type");
regInfo.setHost(parseNumericAddress("192.0.2.123"));
@@ -1381,7 +1469,7 @@
client.registerService(regInfo, NsdManager.PROTOCOL_DNS_SD, Runnable::run, regListener);
waitForIdle();
- verify(mAdvertiser, never()).addOrUpdateService(anyInt(), any(), any());
+ verify(mAdvertiser, never()).addOrUpdateService(anyInt(), any(), any(), anyInt());
verify(regListener, timeout(TIMEOUT_MS)).onRegistrationFailed(
argThat(info -> matches(info, regInfo)), eq(FAILURE_INTERNAL_ERROR));
@@ -1398,7 +1486,7 @@
// final String serviceTypeWithLocalDomain = SERVICE_TYPE + ".local";
final ArgumentCaptor<MdnsAdvertiser.AdvertiserCallback> cbCaptor =
ArgumentCaptor.forClass(MdnsAdvertiser.AdvertiserCallback.class);
- verify(mDeps).makeMdnsAdvertiser(any(), any(), cbCaptor.capture(), any(), any());
+ verify(mDeps).makeMdnsAdvertiser(any(), any(), cbCaptor.capture(), any(), any(), any());
final NsdServiceInfo regInfo = new NsdServiceInfo("a".repeat(70), SERVICE_TYPE);
regInfo.setHost(parseNumericAddress("192.0.2.123"));
@@ -1410,8 +1498,12 @@
waitForIdle();
final ArgumentCaptor<Integer> idCaptor = ArgumentCaptor.forClass(Integer.class);
// Service name is truncated to 63 characters
- verify(mAdvertiser).addOrUpdateService(idCaptor.capture(),
- argThat(info -> info.getServiceName().equals("a".repeat(63))), any());
+ verify(mAdvertiser)
+ .addOrUpdateService(
+ idCaptor.capture(),
+ argThat(info -> info.getServiceName().equals("a".repeat(63))),
+ any(),
+ anyInt());
// Verify onServiceRegistered callback
final MdnsAdvertiser.AdvertiserCallback cb = cbCaptor.getValue();
@@ -1509,7 +1601,7 @@
client.registerService(regInfo, NsdManager.PROTOCOL_DNS_SD, Runnable::run, regListener);
waitForIdle();
verify(mSocketProvider).startMonitoringSockets();
- verify(mAdvertiser).addOrUpdateService(anyInt(), any(), any());
+ verify(mAdvertiser).addOrUpdateService(anyInt(), any(), any(), anyInt());
// Verify the discovery uses MdnsDiscoveryManager
final DiscoveryListener discListener = mock(DiscoveryListener.class);
@@ -1542,7 +1634,7 @@
client.registerService(regInfo, NsdManager.PROTOCOL_DNS_SD, Runnable::run, regListener);
waitForIdle();
verify(mSocketProvider).startMonitoringSockets();
- verify(mAdvertiser).addOrUpdateService(anyInt(), any(), any());
+ verify(mAdvertiser).addOrUpdateService(anyInt(), any(), any(), anyInt());
final Network wifiNetwork1 = new Network(123);
final Network wifiNetwork2 = new Network(124);
diff --git a/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java b/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java
index f07593e..9f0ec30 100644
--- a/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java
@@ -20,6 +20,7 @@
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.telephony.TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED;
+import static com.android.server.connectivity.CarrierPrivilegeAuthenticator.CarrierPrivilegesLostListener;
import static com.android.server.connectivity.ConnectivityFlags.CARRIER_SERVICE_CHANGED_USE_CALLBACK;
import static org.junit.Assert.assertEquals;
@@ -54,10 +55,12 @@
import com.android.networkstack.apishim.common.TelephonyManagerShim.CarrierPrivilegesListenerShim;
import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
import com.android.server.connectivity.CarrierPrivilegeAuthenticator.Dependencies;
+import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.After;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -67,6 +70,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
+import java.util.Set;
/**
* Tests for CarrierPrivilegeAuthenticatorTest.
@@ -77,6 +81,9 @@
@RunWith(DevSdkIgnoreRunner.class)
@IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class CarrierPrivilegeAuthenticatorTest {
+ @Rule
+ public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
+
private static final int SUBSCRIPTION_COUNT = 2;
private static final int TEST_SUBSCRIPTION_ID = 1;
@@ -85,7 +92,9 @@
@NonNull private final TelephonyManagerShimImpl mTelephonyManagerShim;
@NonNull private final PackageManager mPackageManager;
@NonNull private TestCarrierPrivilegeAuthenticator mCarrierPrivilegeAuthenticator;
+ @NonNull private final CarrierPrivilegesLostListener mListener;
private final int mCarrierConfigPkgUid = 12345;
+ private final boolean mUseCallbacks;
private final String mTestPkg = "com.android.server.connectivity.test";
private final BroadcastReceiver mMultiSimBroadcastReceiver;
@NonNull private final HandlerThread mHandlerThread;
@@ -94,7 +103,8 @@
TestCarrierPrivilegeAuthenticator(@NonNull final Context c,
@NonNull final Dependencies deps,
@NonNull final TelephonyManager t) {
- super(c, deps, t, mTelephonyManagerShim);
+ super(c, deps, t, mTelephonyManagerShim, true /* requestRestrictedWifiEnabled */,
+ mListener);
}
@Override
protected int getSlotIndex(int subId) {
@@ -119,7 +129,9 @@
mTelephonyManager = mock(TelephonyManager.class);
mTelephonyManagerShim = mock(TelephonyManagerShimImpl.class);
mPackageManager = mock(PackageManager.class);
+ mListener = mock(CarrierPrivilegesLostListener.class);
mHandlerThread = new HandlerThread(CarrierPrivilegeAuthenticatorTest.class.getSimpleName());
+ mUseCallbacks = useCallbacks;
final Dependencies deps = mock(Dependencies.class);
doReturn(useCallbacks).when(deps).isFeatureEnabled(any() /* context */,
eq(CARRIER_SERVICE_CHANGED_USE_CALLBACK));
@@ -220,6 +232,18 @@
}
@Test
+ @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+ public void testCarrierPrivilegesLostDueToCarrierServiceUpdate() throws Exception {
+ final CarrierPrivilegesListenerShim l = getCarrierPrivilegesListeners().get(0);
+
+ l.onCarrierServiceChanged(null, mCarrierConfigPkgUid);
+ l.onCarrierServiceChanged(null, mCarrierConfigPkgUid + 1);
+ if (mUseCallbacks) {
+ verify(mListener).onCarrierPrivilegesLost(eq(mCarrierConfigPkgUid));
+ }
+ }
+
+ @Test
public void testOnCarrierPrivilegesChanged() throws Exception {
final CarrierPrivilegesListenerShim listener = getCarrierPrivilegesListeners().get(0);
@@ -264,4 +288,32 @@
assertFalse(mCarrierPrivilegeAuthenticator.isCarrierServiceUidForNetworkCapabilities(
mCarrierConfigPkgUid, ncBuilder.build()));
}
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+ public void testNetworkCapabilitiesContainOneSubId() throws Exception {
+ final CarrierPrivilegesListenerShim listener = getCarrierPrivilegesListeners().get(0);
+ listener.onCarrierServiceChanged(null, mCarrierConfigPkgUid);
+
+ final NetworkCapabilities.Builder ncBuilder = new NetworkCapabilities.Builder();
+ ncBuilder.addTransportType(TRANSPORT_WIFI);
+ ncBuilder.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+ ncBuilder.setSubscriptionIds(Set.of(0));
+ assertTrue(mCarrierPrivilegeAuthenticator.isCarrierServiceUidForNetworkCapabilities(
+ mCarrierConfigPkgUid, ncBuilder.build()));
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+ public void testNetworkCapabilitiesContainTwoSubIds() throws Exception {
+ final CarrierPrivilegesListenerShim listener = getCarrierPrivilegesListeners().get(0);
+ listener.onCarrierServiceChanged(null, mCarrierConfigPkgUid);
+
+ final NetworkCapabilities.Builder ncBuilder = new NetworkCapabilities.Builder();
+ ncBuilder.addTransportType(TRANSPORT_WIFI);
+ ncBuilder.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+ ncBuilder.setSubscriptionIds(Set.of(0, 1));
+ assertFalse(mCarrierPrivilegeAuthenticator.isCarrierServiceUidForNetworkCapabilities(
+ mCarrierConfigPkgUid, ncBuilder.build()));
+ }
}
diff --git a/tests/unit/java/com/android/server/connectivity/KeepaliveStatsTrackerTest.java b/tests/unit/java/com/android/server/connectivity/KeepaliveStatsTrackerTest.java
index 1b964e2..294dacb 100644
--- a/tests/unit/java/com/android/server/connectivity/KeepaliveStatsTrackerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/KeepaliveStatsTrackerTest.java
@@ -1297,8 +1297,8 @@
assertTrue(mKeepaliveStatsTracker.allMetricsExpected(dailyKeepaliveInfoReported));
- // Write time after 26 hours.
- final int writeTime2 = 26 * 60 * 60 * 1000;
+ // Write time after 27 hours.
+ final int writeTime2 = 27 * 60 * 60 * 1000;
setElapsedRealtime(writeTime2);
visibleOnHandlerThread(mTestHandler, () -> mKeepaliveStatsTracker.writeAndResetMetrics());
diff --git a/tests/unit/java/com/android/server/connectivity/SatelliteAccessControllerTest.kt b/tests/unit/java/com/android/server/connectivity/SatelliteAccessControllerTest.kt
new file mode 100644
index 0000000..64a515a
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/SatelliteAccessControllerTest.kt
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.connectivity
+
+import android.Manifest
+import android.app.role.OnRoleHoldersChangedListener
+import android.app.role.RoleManager
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.os.Build
+import android.os.Handler
+import android.os.UserHandle
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import java.util.concurrent.Executor
+import java.util.function.Consumer
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+private const val DEFAULT_MESSAGING_APP1 = "default_messaging_app_1"
+private const val DEFAULT_MESSAGING_APP2 = "default_messaging_app_2"
+private const val DEFAULT_MESSAGING_APP1_UID = 1234
+private const val DEFAULT_MESSAGING_APP2_UID = 5678
+
+@RunWith(DevSdkIgnoreRunner::class)
+@IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+class SatelliteAccessControllerTest {
+ private val context = mock(Context::class.java)
+ private val mPackageManager = mock(PackageManager::class.java)
+ private val mHandler = mock(Handler::class.java)
+ private val mRoleManager =
+ mock(SatelliteAccessController.Dependencies::class.java)
+ private val mCallback = mock(Consumer::class.java) as Consumer<Set<Int>>
+ private val mSatelliteAccessController by lazy {
+ SatelliteAccessController(context, mRoleManager, mCallback, mHandler)}
+ private var mRoleHolderChangedListener: OnRoleHoldersChangedListener? = null
+ @Before
+ @Throws(PackageManager.NameNotFoundException::class)
+ fun setup() {
+ doReturn(mPackageManager).`when`(context).packageManager
+ doReturn(PackageManager.PERMISSION_GRANTED)
+ .`when`(mPackageManager)
+ .checkPermission(Manifest.permission.SATELLITE_COMMUNICATION, DEFAULT_MESSAGING_APP1)
+ doReturn(PackageManager.PERMISSION_GRANTED)
+ .`when`(mPackageManager)
+ .checkPermission(Manifest.permission.SATELLITE_COMMUNICATION, DEFAULT_MESSAGING_APP2)
+
+ // Initialise default message application package1
+ val applicationInfo1 = ApplicationInfo()
+ applicationInfo1.uid = DEFAULT_MESSAGING_APP1_UID
+ doReturn(applicationInfo1)
+ .`when`(mPackageManager)
+ .getApplicationInfo(eq(DEFAULT_MESSAGING_APP1), anyInt())
+
+ // Initialise default message application package2
+ val applicationInfo2 = ApplicationInfo()
+ applicationInfo2.uid = DEFAULT_MESSAGING_APP2_UID
+ doReturn(applicationInfo2)
+ .`when`(mPackageManager)
+ .getApplicationInfo(eq(DEFAULT_MESSAGING_APP2), anyInt())
+
+ // Get registered listener using captor
+ val listenerCaptor = ArgumentCaptor.forClass(
+ OnRoleHoldersChangedListener::class.java
+ )
+ mSatelliteAccessController.start()
+ verify(mRoleManager).addOnRoleHoldersChangedListenerAsUser(
+ any(Executor::class.java), listenerCaptor.capture(), any(UserHandle::class.java))
+ mRoleHolderChangedListener = listenerCaptor.value
+ }
+
+ @Test
+ fun test_onRoleHoldersChanged_SatellitePreferredUid_Changed() {
+ doReturn(listOf<String>()).`when`(mRoleManager).getRoleHolders(RoleManager.ROLE_SMS)
+ val satelliteNetworkPreferredSet =
+ ArgumentCaptor.forClass(Set::class.java) as ArgumentCaptor<Set<Int>>
+ mRoleHolderChangedListener?.onRoleHoldersChanged(RoleManager.ROLE_SMS, UserHandle.ALL)
+ verify(mCallback, never()).accept(satelliteNetworkPreferredSet.capture())
+
+ // check DEFAULT_MESSAGING_APP1 is available as satellite network preferred uid
+ doReturn(listOf(DEFAULT_MESSAGING_APP1))
+ .`when`(mRoleManager).getRoleHolders(RoleManager.ROLE_SMS)
+ mRoleHolderChangedListener?.onRoleHoldersChanged(RoleManager.ROLE_SMS, UserHandle.ALL)
+ verify(mCallback).accept(satelliteNetworkPreferredSet.capture())
+ var satelliteNetworkPreferredUids = satelliteNetworkPreferredSet.value
+ assertEquals(1, satelliteNetworkPreferredUids.size)
+ assertTrue(satelliteNetworkPreferredUids.contains(DEFAULT_MESSAGING_APP1_UID))
+ assertFalse(satelliteNetworkPreferredUids.contains(DEFAULT_MESSAGING_APP2_UID))
+
+ // check DEFAULT_MESSAGING_APP1 and DEFAULT_MESSAGING_APP2 is available
+ // as satellite network preferred uid
+ val dmas: MutableList<String> = ArrayList()
+ dmas.add(DEFAULT_MESSAGING_APP1)
+ dmas.add(DEFAULT_MESSAGING_APP2)
+ doReturn(dmas).`when`(mRoleManager).getRoleHolders(RoleManager.ROLE_SMS)
+ mRoleHolderChangedListener?.onRoleHoldersChanged(RoleManager.ROLE_SMS, UserHandle.ALL)
+ verify(mCallback, times(2))
+ .accept(satelliteNetworkPreferredSet.capture())
+ satelliteNetworkPreferredUids = satelliteNetworkPreferredSet.value
+ assertEquals(2, satelliteNetworkPreferredUids.size)
+ assertTrue(satelliteNetworkPreferredUids.contains(DEFAULT_MESSAGING_APP1_UID))
+ assertTrue(satelliteNetworkPreferredUids.contains(DEFAULT_MESSAGING_APP2_UID))
+
+ // check no uid is available as satellite network preferred uid
+ doReturn(listOf<String>()).`when`(mRoleManager).getRoleHolders(RoleManager.ROLE_SMS)
+ mRoleHolderChangedListener?.onRoleHoldersChanged(RoleManager.ROLE_SMS, UserHandle.ALL)
+ verify(mCallback, times(3))
+ .accept(satelliteNetworkPreferredSet.capture())
+ satelliteNetworkPreferredUids = satelliteNetworkPreferredSet.value
+ assertEquals(0, satelliteNetworkPreferredUids.size)
+ assertFalse(satelliteNetworkPreferredUids.contains(DEFAULT_MESSAGING_APP1_UID))
+ assertFalse(satelliteNetworkPreferredUids.contains(DEFAULT_MESSAGING_APP2_UID))
+
+ // No Change received at OnRoleSmsChanged, check callback not triggered
+ doReturn(listOf<String>()).`when`(mRoleManager).getRoleHolders(RoleManager.ROLE_SMS)
+ mRoleHolderChangedListener?.onRoleHoldersChanged(RoleManager.ROLE_SMS, UserHandle.ALL)
+ verify(mCallback, times(3))
+ .accept(satelliteNetworkPreferredSet.capture())
+ }
+
+ @Test
+ fun test_onRoleHoldersChanged_NoSatelliteCommunicationPermission() {
+ doReturn(listOf<Any>()).`when`(mRoleManager).getRoleHolders(RoleManager.ROLE_SMS)
+ val satelliteNetworkPreferredSet =
+ ArgumentCaptor.forClass(Set::class.java) as ArgumentCaptor<Set<Int>>
+ mRoleHolderChangedListener?.onRoleHoldersChanged(RoleManager.ROLE_SMS, UserHandle.ALL)
+ verify(mCallback, never()).accept(satelliteNetworkPreferredSet.capture())
+
+ // check DEFAULT_MESSAGING_APP1 is not available as satellite network preferred uid
+ // since satellite communication permission not available.
+ doReturn(PackageManager.PERMISSION_DENIED)
+ .`when`(mPackageManager)
+ .checkPermission(Manifest.permission.SATELLITE_COMMUNICATION, DEFAULT_MESSAGING_APP1)
+ doReturn(listOf(DEFAULT_MESSAGING_APP1))
+ .`when`(mRoleManager).getRoleHolders(RoleManager.ROLE_SMS)
+ mRoleHolderChangedListener?.onRoleHoldersChanged(RoleManager.ROLE_SMS, UserHandle.ALL)
+ verify(mCallback, never()).accept(satelliteNetworkPreferredSet.capture())
+ }
+
+ @Test
+ fun test_onRoleHoldersChanged_RoleSms_NotAvailable() {
+ doReturn(listOf(DEFAULT_MESSAGING_APP1))
+ .`when`(mRoleManager).getRoleHolders(RoleManager.ROLE_SMS)
+ val satelliteNetworkPreferredSet =
+ ArgumentCaptor.forClass(Set::class.java) as ArgumentCaptor<Set<Int>>
+ mRoleHolderChangedListener?.onRoleHoldersChanged(RoleManager.ROLE_BROWSER, UserHandle.ALL)
+ verify(mCallback, never()).accept(satelliteNetworkPreferredSet.capture())
+ }
+}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
index 121f844..f753c93 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
@@ -16,6 +16,8 @@
package com.android.server.connectivity.mdns
+import android.content.Context
+import android.content.res.Resources
import android.net.InetAddresses.parseNumericAddress
import android.net.LinkAddress
import android.net.Network
@@ -26,8 +28,11 @@
import android.os.Build
import android.os.Handler
import android.os.HandlerThread
+import com.android.connectivity.resources.R
import com.android.net.module.util.SharedLog
+import com.android.server.connectivity.ConnectivityResources
import com.android.server.connectivity.mdns.MdnsAdvertiser.AdvertiserCallback
+import com.android.server.connectivity.mdns.MdnsInterfaceAdvertiser.CONFLICT_SERVICE
import com.android.server.connectivity.mdns.MdnsSocketProvider.SocketCallback
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.DevSdkIgnoreRunner
@@ -55,9 +60,10 @@
private const val SERVICE_ID_1 = 1
private const val SERVICE_ID_2 = 2
-private const val LONG_SERVICE_ID_1 = 3
-private const val LONG_SERVICE_ID_2 = 4
-private const val CASE_INSENSITIVE_TEST_SERVICE_ID = 5
+private const val SERVICE_ID_3 = 3
+private const val LONG_SERVICE_ID_1 = 4
+private const val LONG_SERVICE_ID_2 = 5
+private const val CASE_INSENSITIVE_TEST_SERVICE_ID = 6
private const val TIMEOUT_MS = 10_000L
private val TEST_ADDR = parseNumericAddress("2001:db8::123")
private val TEST_ADDR2 = parseNumericAddress("2001:db8::124")
@@ -71,6 +77,7 @@
private const val TEST_SUBTYPE2 = "_subtype2"
private val TEST_INTERFACE1 = "test_iface1"
private val TEST_INTERFACE2 = "test_iface2"
+private val TEST_CLIENT_UID_1 = 10010
private val TEST_OFFLOAD_PACKET1 = byteArrayOf(0x01, 0x02, 0x03)
private val TEST_OFFLOAD_PACKET2 = byteArrayOf(0x02, 0x03, 0x04)
private val DEFAULT_ADVERTISING_OPTION = MdnsAdvertisingOptions.getDefaultOptions()
@@ -150,6 +157,12 @@
OffloadEngine.OFFLOAD_TYPE_REPLY.toLong()
)
+private val SERVICES_PRIORITY_LIST = arrayOf(
+ "0:_advertisertest._tcp",
+ "5:_prioritytest._udp",
+ "5:_otherprioritytest._tcp"
+)
+
@RunWith(DevSdkIgnoreRunner::class)
@IgnoreUpTo(Build.VERSION_CODES.S_V2)
class MdnsAdvertiserTest {
@@ -164,6 +177,8 @@
private val mockInterfaceAdvertiser1 = mock(MdnsInterfaceAdvertiser::class.java)
private val mockInterfaceAdvertiser2 = mock(MdnsInterfaceAdvertiser::class.java)
private val mockDeps = mock(MdnsAdvertiser.Dependencies::class.java)
+ private val context = mock(Context::class.java)
+ private val resources = mock(Resources::class.java)
private val flags = MdnsFeatureFlags.newBuilder().setIsMdnsOffloadFeatureEnabled(true).build()
@Before
@@ -184,12 +199,21 @@
doReturn(TEST_INTERFACE2).`when`(mockInterfaceAdvertiser2).socketInterfaceName
doReturn(TEST_OFFLOAD_PACKET1).`when`(mockInterfaceAdvertiser1).getRawOffloadPayload(
SERVICE_ID_1)
+ doReturn(TEST_OFFLOAD_PACKET1).`when`(mockInterfaceAdvertiser1).getRawOffloadPayload(
+ SERVICE_ID_2)
+ doReturn(TEST_OFFLOAD_PACKET1).`when`(mockInterfaceAdvertiser1).getRawOffloadPayload(
+ SERVICE_ID_3)
doReturn(TEST_OFFLOAD_PACKET1).`when`(mockInterfaceAdvertiser2).getRawOffloadPayload(
SERVICE_ID_1)
+ doReturn(resources).`when`(context).getResources()
+ doReturn(SERVICES_PRIORITY_LIST).`when`(resources).getStringArray(
+ R.array.config_nsdOffloadServicesPriority)
+ ConnectivityResources.setResourcesContextForTest(context)
}
@After
fun tearDown() {
+ ConnectivityResources.setResourcesContextForTest(null)
thread.quitSafely()
thread.join()
}
@@ -203,9 +227,9 @@
@Test
fun testAddService_OneNetwork() {
val advertiser =
- MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog, flags)
+ MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog, flags, context)
postSync { advertiser.addOrUpdateService(SERVICE_ID_1, SERVICE_1,
- DEFAULT_ADVERTISING_OPTION) }
+ DEFAULT_ADVERTISING_OPTION, TEST_CLIENT_UID_1) }
val socketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
verify(socketProvider).requestSocket(eq(TEST_NETWORK_1), socketCbCaptor.capture())
@@ -232,7 +256,10 @@
verify(cb).onOffloadStartOrUpdate(eq(TEST_INTERFACE1), eq(OFFLOAD_SERVICEINFO_NO_SUBTYPE))
// Service is conflicted.
- postSync { intAdvCbCaptor.value.onServiceConflict(mockInterfaceAdvertiser1, SERVICE_ID_1) }
+ postSync {
+ intAdvCbCaptor.value
+ .onServiceConflict(mockInterfaceAdvertiser1, SERVICE_ID_1, CONFLICT_SERVICE)
+ }
// Verify the metrics data
doReturn(25).`when`(mockInterfaceAdvertiser1).getServiceRepliedRequestsCount(SERVICE_ID_1)
@@ -265,9 +292,9 @@
@Test
fun testAddService_AllNetworksWithSubType() {
val advertiser =
- MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog, flags)
+ MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog, flags, context)
postSync { advertiser.addOrUpdateService(SERVICE_ID_1, ALL_NETWORKS_SERVICE_SUBTYPE,
- DEFAULT_ADVERTISING_OPTION) }
+ DEFAULT_ADVERTISING_OPTION, TEST_CLIENT_UID_1) }
val socketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
verify(socketProvider).requestSocket(eq(ALL_NETWORKS_SERVICE_SUBTYPE.network),
@@ -305,9 +332,18 @@
argThat { it.matches(ALL_NETWORKS_SERVICE_SUBTYPE) })
// Services are conflicted.
- postSync { intAdvCbCaptor1.value.onServiceConflict(mockInterfaceAdvertiser1, SERVICE_ID_1) }
- postSync { intAdvCbCaptor1.value.onServiceConflict(mockInterfaceAdvertiser1, SERVICE_ID_1) }
- postSync { intAdvCbCaptor2.value.onServiceConflict(mockInterfaceAdvertiser2, SERVICE_ID_1) }
+ postSync {
+ intAdvCbCaptor1.value
+ .onServiceConflict(mockInterfaceAdvertiser1, SERVICE_ID_1, CONFLICT_SERVICE)
+ }
+ postSync {
+ intAdvCbCaptor1.value
+ .onServiceConflict(mockInterfaceAdvertiser1, SERVICE_ID_1, CONFLICT_SERVICE)
+ }
+ postSync {
+ intAdvCbCaptor2.value
+ .onServiceConflict(mockInterfaceAdvertiser2, SERVICE_ID_1, CONFLICT_SERVICE)
+ }
// Verify the metrics data
doReturn(10).`when`(mockInterfaceAdvertiser1).getServiceRepliedRequestsCount(SERVICE_ID_1)
@@ -335,11 +371,70 @@
}
@Test
+ fun testAddService_OffloadPriority() {
+ val advertiser =
+ MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog, flags, context)
+ postSync {
+ advertiser.addOrUpdateService(SERVICE_ID_1, SERVICE_1, DEFAULT_ADVERTISING_OPTION,
+ TEST_CLIENT_UID_1)
+ advertiser.addOrUpdateService(SERVICE_ID_2,
+ NsdServiceInfo("TestService2", "_PRIORITYTEST._udp").apply {
+ port = 12345
+ hostAddresses = listOf(TEST_ADDR)
+ }, DEFAULT_ADVERTISING_OPTION, TEST_CLIENT_UID_1)
+ advertiser.addOrUpdateService(
+ SERVICE_ID_3,
+ NsdServiceInfo("TestService3", "_notprioritized._tcp").apply {
+ port = 12345
+ hostAddresses = listOf(TEST_ADDR)
+ }, DEFAULT_ADVERTISING_OPTION, TEST_CLIENT_UID_1)
+ }
+
+ val socketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
+ verify(socketProvider).requestSocket(eq(SERVICE_1.network), socketCbCaptor.capture())
+
+ val socketCb = socketCbCaptor.value
+ postSync { socketCb.onSocketCreated(TEST_SOCKETKEY_1, mockSocket1, listOf(TEST_LINKADDR)) }
+
+ val intAdvCbCaptor1 = ArgumentCaptor.forClass(MdnsInterfaceAdvertiser.Callback::class.java)
+ verify(mockDeps).makeAdvertiser(eq(mockSocket1), eq(listOf(TEST_LINKADDR)),
+ eq(thread.looper), any(), intAdvCbCaptor1.capture(), eq(TEST_HOSTNAME), any(), any()
+ )
+
+ doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_1)
+ doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_2)
+ doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_3)
+ postSync {
+ intAdvCbCaptor1.value.onServiceProbingSucceeded(mockInterfaceAdvertiser1, SERVICE_ID_1)
+ intAdvCbCaptor1.value.onServiceProbingSucceeded(mockInterfaceAdvertiser1, SERVICE_ID_2)
+ intAdvCbCaptor1.value.onServiceProbingSucceeded(mockInterfaceAdvertiser1, SERVICE_ID_3)
+ }
+
+ verify(cb).onOffloadStartOrUpdate(eq(TEST_INTERFACE1), eq(OFFLOAD_SERVICEINFO_NO_SUBTYPE))
+ verify(cb).onOffloadStartOrUpdate(eq(TEST_INTERFACE1), eq(OffloadServiceInfo(
+ OffloadServiceInfo.Key("TestService2", "_PRIORITYTEST._udp"),
+ emptyList() /* subtypes */,
+ "Android_test.local",
+ TEST_OFFLOAD_PACKET1,
+ 5, /* priority */
+ OffloadEngine.OFFLOAD_TYPE_REPLY.toLong()
+ )))
+ verify(cb).onOffloadStartOrUpdate(eq(TEST_INTERFACE1), eq(OffloadServiceInfo(
+ OffloadServiceInfo.Key("TestService3", "_notprioritized._tcp"),
+ emptyList() /* subtypes */,
+ "Android_test.local",
+ TEST_OFFLOAD_PACKET1,
+ Integer.MAX_VALUE, /* priority */
+ OffloadEngine.OFFLOAD_TYPE_REPLY.toLong()
+ )))
+ }
+
+ @Test
fun testAddService_Conflicts() {
val advertiser =
- MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog, flags)
+ MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog, flags, context)
postSync { advertiser.addOrUpdateService(SERVICE_ID_1, SERVICE_1,
- DEFAULT_ADVERTISING_OPTION) }
+ DEFAULT_ADVERTISING_OPTION, TEST_CLIENT_UID_1) }
val oneNetSocketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
verify(socketProvider).requestSocket(eq(TEST_NETWORK_1), oneNetSocketCbCaptor.capture())
@@ -347,18 +442,18 @@
// Register a service with the same name on all networks (name conflict)
postSync { advertiser.addOrUpdateService(SERVICE_ID_2, ALL_NETWORKS_SERVICE,
- DEFAULT_ADVERTISING_OPTION) }
+ DEFAULT_ADVERTISING_OPTION, TEST_CLIENT_UID_1) }
val allNetSocketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
verify(socketProvider).requestSocket(eq(null), allNetSocketCbCaptor.capture())
val allNetSocketCb = allNetSocketCbCaptor.value
postSync { advertiser.addOrUpdateService(LONG_SERVICE_ID_1, LONG_SERVICE_1,
- DEFAULT_ADVERTISING_OPTION) }
+ DEFAULT_ADVERTISING_OPTION, TEST_CLIENT_UID_1) }
postSync { advertiser.addOrUpdateService(LONG_SERVICE_ID_2, LONG_ALL_NETWORKS_SERVICE,
- DEFAULT_ADVERTISING_OPTION) }
+ DEFAULT_ADVERTISING_OPTION, TEST_CLIENT_UID_1) }
postSync { advertiser.addOrUpdateService(CASE_INSENSITIVE_TEST_SERVICE_ID,
- ALL_NETWORKS_SERVICE_2, DEFAULT_ADVERTISING_OPTION) }
+ ALL_NETWORKS_SERVICE_2, DEFAULT_ADVERTISING_OPTION, TEST_CLIENT_UID_1) }
// Callbacks for matching network and all networks both get the socket
postSync {
@@ -425,9 +520,10 @@
@Test
fun testAddOrUpdateService_Updates() {
val advertiser =
- MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog, flags)
+ MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog, flags,
+ context)
postSync { advertiser.addOrUpdateService(SERVICE_ID_1, ALL_NETWORKS_SERVICE,
- DEFAULT_ADVERTISING_OPTION) }
+ DEFAULT_ADVERTISING_OPTION, TEST_CLIENT_UID_1) }
val socketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
verify(socketProvider).requestSocket(eq(null), socketCbCaptor.capture())
@@ -442,16 +538,17 @@
// Update with serviceId that is not registered yet should fail
postSync { advertiser.addOrUpdateService(SERVICE_ID_2, ALL_NETWORKS_SERVICE_SUBTYPE,
- updateOptions) }
+ updateOptions, TEST_CLIENT_UID_1) }
verify(cb).onRegisterServiceFailed(SERVICE_ID_2, NsdManager.FAILURE_INTERNAL_ERROR)
// Update service with different NsdServiceInfo should fail
- postSync { advertiser.addOrUpdateService(SERVICE_ID_1, SERVICE_1_SUBTYPE, updateOptions) }
+ postSync { advertiser.addOrUpdateService(SERVICE_ID_1, SERVICE_1_SUBTYPE, updateOptions,
+ TEST_CLIENT_UID_1) }
verify(cb).onRegisterServiceFailed(SERVICE_ID_1, NsdManager.FAILURE_INTERNAL_ERROR)
// Update service with same NsdServiceInfo but different subType should succeed
postSync { advertiser.addOrUpdateService(SERVICE_ID_1, ALL_NETWORKS_SERVICE_SUBTYPE,
- updateOptions) }
+ updateOptions, TEST_CLIENT_UID_1) }
verify(mockInterfaceAdvertiser1).updateService(eq(SERVICE_ID_1), eq(setOf(TEST_SUBTYPE)))
// Newly created MdnsInterfaceAdvertiser will get addService() call.
@@ -463,10 +560,10 @@
@Test
fun testRemoveService_whenAllServiceRemoved_thenUpdateHostName() {
val advertiser =
- MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog, flags)
+ MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog, flags, context)
verify(mockDeps, times(1)).generateHostname()
postSync { advertiser.addOrUpdateService(SERVICE_ID_1, SERVICE_1,
- DEFAULT_ADVERTISING_OPTION) }
+ DEFAULT_ADVERTISING_OPTION, TEST_CLIENT_UID_1) }
postSync { advertiser.removeService(SERVICE_ID_1) }
verify(mockDeps, times(2)).generateHostname()
}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt
index 0e5cc50..0637ad1 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt
@@ -18,6 +18,7 @@
import android.net.InetAddresses.parseNumericAddress
import android.net.LinkAddress
+import android.net.nsd.NsdManager
import android.net.nsd.NsdServiceInfo
import android.os.Build
import android.os.HandlerThread
@@ -26,6 +27,7 @@
import com.android.server.connectivity.mdns.MdnsAnnouncer.AnnouncementInfo
import com.android.server.connectivity.mdns.MdnsAnnouncer.BaseAnnouncementInfo
import com.android.server.connectivity.mdns.MdnsAnnouncer.ExitAnnouncementInfo
+import com.android.server.connectivity.mdns.MdnsInterfaceAdvertiser.CONFLICT_SERVICE
import com.android.server.connectivity.mdns.MdnsInterfaceAdvertiser.EXIT_ANNOUNCEMENT_DELAY_MS
import com.android.server.connectivity.mdns.MdnsPacketRepeater.PacketRepeaterCallback
import com.android.server.connectivity.mdns.MdnsProber.ProbingInfo
@@ -347,7 +349,8 @@
@Test
fun testConflict() {
addServiceAndFinishProbing(TEST_SERVICE_ID_1, TEST_SERVICE_1)
- doReturn(setOf(TEST_SERVICE_ID_1)).`when`(repository).getConflictingServices(any())
+ doReturn(mapOf(TEST_SERVICE_ID_1 to CONFLICT_SERVICE))
+ .`when`(repository).getConflictingServices(any())
// Reply obtained with:
// scapy.raw(scapy.DNS(
@@ -373,7 +376,7 @@
}
thread.waitForIdle(TIMEOUT_MS)
- verify(cb).onServiceConflict(advertiser, TEST_SERVICE_ID_1)
+ verify(cb).onServiceConflict(advertiser, TEST_SERVICE_ID_1, CONFLICT_SERVICE)
}
@Test
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
index 06f12fe..fd8d98b 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
@@ -22,6 +22,8 @@
import android.os.Build
import android.os.HandlerThread
import com.android.server.connectivity.mdns.MdnsAnnouncer.AnnouncementInfo
+import com.android.server.connectivity.mdns.MdnsInterfaceAdvertiser.CONFLICT_HOST
+import com.android.server.connectivity.mdns.MdnsInterfaceAdvertiser.CONFLICT_SERVICE
import com.android.server.connectivity.mdns.MdnsRecord.TYPE_A
import com.android.server.connectivity.mdns.MdnsRecord.TYPE_AAAA
import com.android.server.connectivity.mdns.MdnsRecord.TYPE_PTR
@@ -52,6 +54,9 @@
private const val TEST_SERVICE_ID_1 = 42
private const val TEST_SERVICE_ID_2 = 43
private const val TEST_SERVICE_ID_3 = 44
+private const val TEST_CUSTOM_HOST_ID_1 = 45
+private const val TEST_CUSTOM_HOST_ID_2 = 46
+private const val TEST_SERVICE_CUSTOM_HOST_ID_1 = 48
private const val TEST_PORT = 12345
private const val TEST_SUBTYPE = "_subtype"
private const val TEST_SUBTYPE2 = "_subtype2"
@@ -86,6 +91,26 @@
port = TEST_PORT
}
+private val TEST_CUSTOM_HOST_1 = NsdServiceInfo().apply {
+ hostname = "TestHost"
+ hostAddresses = listOf(parseNumericAddress("2001:db8::1"), parseNumericAddress("2001:db8::2"))
+}
+
+private val TEST_CUSTOM_HOST_1_NAME = arrayOf("TestHost", "local")
+
+private val TEST_CUSTOM_HOST_2 = NsdServiceInfo().apply {
+ hostname = "OtherTestHost"
+ hostAddresses = listOf(parseNumericAddress("2001:db8::3"), parseNumericAddress("2001:db8::4"))
+}
+
+private val TEST_SERVICE_CUSTOM_HOST_1 = NsdServiceInfo().apply {
+ hostname = "TestHost"
+ hostAddresses = listOf(parseNumericAddress("2001:db8::1"))
+ serviceType = "_testservice._tcp"
+ serviceName = "TestService"
+ port = TEST_PORT
+}
+
@RunWith(DevSdkIgnoreRunner::class)
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
class MdnsRecordRepositoryTest {
@@ -569,6 +594,92 @@
), reply.additionalAnswers)
}
+
+ @Test
+ fun testGetReply_ptrQuestionForServiceWithCustomHost_customHostUsedInAdditionalAnswers() {
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+ repository.initWithService(TEST_SERVICE_CUSTOM_HOST_ID_1, TEST_SERVICE_CUSTOM_HOST_1,
+ setOf(TEST_SUBTYPE, TEST_SUBTYPE2))
+ val src = InetSocketAddress(parseNumericAddress("fe80::1234"), 5353)
+ val serviceName = arrayOf("TestService", "_testservice", "_tcp", "local")
+
+ val query = makeQuery(TYPE_PTR to arrayOf("_testservice", "_tcp", "local"))
+ val reply = repository.getReply(query, src)
+
+ assertNotNull(reply)
+ assertEquals(listOf(
+ MdnsPointerRecord(
+ arrayOf("_testservice", "_tcp", "local"),
+ 0L, false, LONG_TTL, serviceName)),
+ reply.answers)
+ assertEquals(listOf(
+ MdnsTextRecord(serviceName, 0L, true, LONG_TTL, listOf()),
+ MdnsServiceRecord(serviceName, 0L, true, SHORT_TTL,
+ 0, 0, TEST_PORT, TEST_CUSTOM_HOST_1_NAME),
+ MdnsInetAddressRecord(
+ TEST_CUSTOM_HOST_1_NAME, 0L, true, SHORT_TTL,
+ parseNumericAddress("2001:db8::1")),
+ MdnsNsecRecord(serviceName, 0L, true, LONG_TTL, serviceName /* nextDomain */,
+ intArrayOf(TYPE_TXT, TYPE_SRV)),
+ MdnsNsecRecord(TEST_CUSTOM_HOST_1_NAME, 0L, true, SHORT_TTL,
+ TEST_CUSTOM_HOST_1_NAME /* nextDomain */,
+ intArrayOf(TYPE_AAAA)),
+ ), reply.additionalAnswers)
+ }
+
+ @Test
+ fun testGetReply_ptrQuestionForServicesWithSameCustomHost_customHostUsedInAdditionalAnswers() {
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+ val serviceWithCustomHost1 = NsdServiceInfo().apply {
+ hostname = "TestHost"
+ hostAddresses = listOf(
+ parseNumericAddress("2001:db8::1"),
+ parseNumericAddress("192.0.2.1"))
+ serviceType = "_testservice._tcp"
+ serviceName = "TestService1"
+ port = TEST_PORT
+ }
+ val serviceWithCustomHost2 = NsdServiceInfo().apply {
+ hostname = "TestHost"
+ hostAddresses = listOf(
+ parseNumericAddress("2001:db8::1"),
+ parseNumericAddress("2001:db8::3"))
+ }
+ repository.addServiceAndFinishProbing(TEST_SERVICE_ID_1, serviceWithCustomHost1)
+ repository.addServiceAndFinishProbing(TEST_SERVICE_ID_2, serviceWithCustomHost2)
+ val src = InetSocketAddress(parseNumericAddress("fe80::1234"), 5353)
+ val serviceName = arrayOf("TestService1", "_testservice", "_tcp", "local")
+
+ val query = makeQuery(TYPE_PTR to arrayOf("_testservice", "_tcp", "local"))
+ val reply = repository.getReply(query, src)
+
+ assertNotNull(reply)
+ assertEquals(listOf(
+ MdnsPointerRecord(
+ arrayOf("_testservice", "_tcp", "local"),
+ 0L, false, LONG_TTL, serviceName)),
+ reply.answers)
+ assertEquals(listOf(
+ MdnsTextRecord(serviceName, 0L, true, LONG_TTL, listOf()),
+ MdnsServiceRecord(serviceName, 0L, true, SHORT_TTL,
+ 0, 0, TEST_PORT, TEST_CUSTOM_HOST_1_NAME),
+ MdnsInetAddressRecord(
+ TEST_CUSTOM_HOST_1_NAME, 0L, true, SHORT_TTL,
+ parseNumericAddress("2001:db8::1")),
+ MdnsInetAddressRecord(
+ TEST_CUSTOM_HOST_1_NAME, 0L, true, SHORT_TTL,
+ parseNumericAddress("192.0.2.1")),
+ MdnsInetAddressRecord(
+ TEST_CUSTOM_HOST_1_NAME, 0L, true, SHORT_TTL,
+ parseNumericAddress("2001:db8::3")),
+ MdnsNsecRecord(serviceName, 0L, true, LONG_TTL, serviceName /* nextDomain */,
+ intArrayOf(TYPE_TXT, TYPE_SRV)),
+ MdnsNsecRecord(TEST_CUSTOM_HOST_1_NAME, 0L, true, SHORT_TTL,
+ TEST_CUSTOM_HOST_1_NAME /* nextDomain */,
+ intArrayOf(TYPE_A, TYPE_AAAA)),
+ ), reply.additionalAnswers)
+ }
+
@Test
fun testGetReply_singleSubtypePtrQuestion_returnsSrvTxtAddressNsecRecords() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
@@ -708,6 +819,90 @@
}
@Test
+ fun testGetReply_AAAAQuestionForCustomHost_returnsAAAARecords() {
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+ repository.initWithService(
+ TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1, subtypes = setOf(),
+ listOf(LinkAddress(parseNumericAddress("192.0.2.111"), 24)))
+ repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2)
+ val src = InetSocketAddress(parseNumericAddress("fe80::123"), 5353)
+
+ val query = makeQuery(TYPE_AAAA to TEST_CUSTOM_HOST_1_NAME)
+ val reply = repository.getReply(query, src)
+
+ assertNotNull(reply)
+ assertEquals(listOf(
+ MdnsInetAddressRecord(TEST_CUSTOM_HOST_1_NAME,
+ 0, false, LONG_TTL, parseNumericAddress("2001:db8::1")),
+ MdnsInetAddressRecord(TEST_CUSTOM_HOST_1_NAME,
+ 0, false, LONG_TTL, parseNumericAddress("2001:db8::2"))),
+ reply.answers)
+ assertEquals(
+ listOf(MdnsNsecRecord(TEST_CUSTOM_HOST_1_NAME,
+ 0L, true, SHORT_TTL,
+ TEST_CUSTOM_HOST_1_NAME /* nextDomain */,
+ intArrayOf(TYPE_AAAA))),
+ reply.additionalAnswers)
+ }
+
+
+ @Test
+ fun testGetReply_AAAAQuestionForCustomHostInMultipleRegistrations_returnsAAAARecords() {
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+
+ repository.addServiceAndFinishProbing(TEST_CUSTOM_HOST_ID_1, NsdServiceInfo().apply {
+ hostname = "TestHost"
+ hostAddresses = listOf(
+ parseNumericAddress("2001:db8::1"),
+ parseNumericAddress("2001:db8::2"))
+ })
+ repository.addServiceAndFinishProbing(TEST_CUSTOM_HOST_ID_2, NsdServiceInfo().apply {
+ hostname = "TestHost"
+ hostAddresses = listOf(
+ parseNumericAddress("2001:db8::1"),
+ parseNumericAddress("2001:db8::3"))
+ })
+ val src = InetSocketAddress(parseNumericAddress("fe80::123"), 5353)
+
+ val query = makeQuery(TYPE_AAAA to TEST_CUSTOM_HOST_1_NAME)
+ val reply = repository.getReply(query, src)
+
+ assertNotNull(reply)
+ assertEquals(listOf(
+ MdnsInetAddressRecord(TEST_CUSTOM_HOST_1_NAME,
+ 0, false, LONG_TTL, parseNumericAddress("2001:db8::1")),
+ MdnsInetAddressRecord(TEST_CUSTOM_HOST_1_NAME,
+ 0, false, LONG_TTL, parseNumericAddress("2001:db8::2")),
+ MdnsInetAddressRecord(TEST_CUSTOM_HOST_1_NAME,
+ 0, false, LONG_TTL, parseNumericAddress("2001:db8::3"))),
+ reply.answers)
+ assertEquals(
+ listOf(MdnsNsecRecord(TEST_CUSTOM_HOST_1_NAME,
+ 0L, true, SHORT_TTL,
+ TEST_CUSTOM_HOST_1_NAME /* nextDomain */,
+ intArrayOf(TYPE_AAAA))),
+ reply.additionalAnswers)
+ }
+
+ @Test
+ fun testGetReply_customHostRemoved_noAnswerToAAAAQuestion() {
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+ repository.initWithService(
+ TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1, subtypes = setOf(),
+ listOf(LinkAddress(parseNumericAddress("192.0.2.111"), 24)))
+ repository.addService(TEST_SERVICE_CUSTOM_HOST_ID_1, TEST_SERVICE_CUSTOM_HOST_1)
+ repository.removeService(TEST_CUSTOM_HOST_ID_1)
+ repository.removeService(TEST_SERVICE_CUSTOM_HOST_ID_1)
+
+ val src = InetSocketAddress(parseNumericAddress("fe80::123"), 5353)
+
+ val query = makeQuery(TYPE_AAAA to TEST_CUSTOM_HOST_1_NAME)
+ val reply = repository.getReply(query, src)
+
+ assertNull(reply)
+ }
+
+ @Test
fun testGetReply_ptrAndSrvQuestions_doesNotReturnSrvRecordInAdditionalAnswerSection() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1, setOf(TEST_SUBTYPE))
@@ -815,7 +1010,10 @@
emptyList() /* authorityRecords */,
emptyList() /* additionalRecords */)
- assertEquals(setOf(TEST_SERVICE_ID_1, TEST_SERVICE_ID_2),
+ assertEquals(
+ mapOf(
+ TEST_SERVICE_ID_1 to CONFLICT_SERVICE,
+ TEST_SERVICE_ID_2 to CONFLICT_SERVICE),
repository.getConflictingServices(packet))
}
@@ -843,8 +1041,131 @@
emptyList() /* authorityRecords */,
emptyList() /* additionalRecords */)
- assertEquals(setOf(TEST_SERVICE_ID_1, TEST_SERVICE_ID_2),
- repository.getConflictingServices(packet))
+ assertEquals(
+ mapOf(TEST_SERVICE_ID_1 to CONFLICT_SERVICE,
+ TEST_SERVICE_ID_2 to CONFLICT_SERVICE),
+ repository.getConflictingServices(packet))
+ }
+
+ @Test
+ fun testGetConflictingServices_customHosts_differentAddresses() {
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+ repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1)
+ repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2)
+
+ val packet = MdnsPacket(
+ 0, /* flags */
+ emptyList(), /* questions */
+ listOf(
+ MdnsInetAddressRecord(arrayOf("TestHost", "local"),
+ 0L /* receiptTimeMillis */, true /* cacheFlush */,
+ 0L /* ttlMillis */, parseNumericAddress("2001:db8::5")),
+ MdnsInetAddressRecord(arrayOf("TestHost", "local"),
+ 0L /* receiptTimeMillis */, true /* cacheFlush */,
+ 0L /* ttlMillis */, parseNumericAddress("2001:db8::6")),
+ ) /* answers */,
+ emptyList() /* authorityRecords */,
+ emptyList() /* additionalRecords */)
+
+ assertEquals(mapOf(TEST_CUSTOM_HOST_ID_1 to CONFLICT_HOST),
+ repository.getConflictingServices(packet))
+ }
+
+ @Test
+ fun testGetConflictingServices_customHosts_moreAddressesThanUs_conflict() {
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+ repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1)
+ repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2)
+
+ val packet = MdnsPacket(
+ 0, /* flags */
+ emptyList(), /* questions */
+ listOf(
+ MdnsInetAddressRecord(arrayOf("TestHost", "local"),
+ 0L /* receiptTimeMillis */, true /* cacheFlush */,
+ 0L /* ttlMillis */, parseNumericAddress("2001:db8::1")),
+ MdnsInetAddressRecord(arrayOf("TestHost", "local"),
+ 0L /* receiptTimeMillis */, true /* cacheFlush */,
+ 0L /* ttlMillis */, parseNumericAddress("2001:db8::2")),
+ MdnsInetAddressRecord(arrayOf("TestHost", "local"),
+ 0L /* receiptTimeMillis */, true /* cacheFlush */,
+ 0L /* ttlMillis */, parseNumericAddress("2001:db8::3")),
+ ) /* answers */,
+ emptyList() /* authorityRecords */,
+ emptyList() /* additionalRecords */)
+
+ assertEquals(mapOf(TEST_CUSTOM_HOST_ID_1 to CONFLICT_HOST),
+ repository.getConflictingServices(packet))
+ }
+
+ @Test
+ fun testGetConflictingServices_customHostsReplyHasFewerAddressesThanUs_noConflict() {
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+ repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1)
+ repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2)
+
+ val packet = MdnsPacket(
+ 0, /* flags */
+ emptyList(), /* questions */
+ listOf(
+ MdnsInetAddressRecord(arrayOf("TestHost", "local"),
+ 0L /* receiptTimeMillis */, true /* cacheFlush */,
+ 0L /* ttlMillis */, parseNumericAddress("2001:db8::2")),
+ ) /* answers */,
+ emptyList() /* authorityRecords */,
+ emptyList() /* additionalRecords */)
+
+ assertEquals(emptyMap(),
+ repository.getConflictingServices(packet))
+ }
+
+ @Test
+ fun testGetConflictingServices_customHostsReplyHasIdenticalHosts_noConflict() {
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+ repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1)
+ repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2)
+
+ val packet = MdnsPacket(
+ 0, /* flags */
+ emptyList(), /* questions */
+ listOf(
+ MdnsInetAddressRecord(arrayOf("TestHost", "local"),
+ 0L /* receiptTimeMillis */, true /* cacheFlush */,
+ 0L /* ttlMillis */, parseNumericAddress("2001:db8::1")),
+ MdnsInetAddressRecord(arrayOf("TestHost", "local"),
+ 0L /* receiptTimeMillis */, true /* cacheFlush */,
+ 0L /* ttlMillis */, parseNumericAddress("2001:db8::2")),
+ ) /* answers */,
+ emptyList() /* authorityRecords */,
+ emptyList() /* additionalRecords */)
+
+ assertEquals(emptyMap(),
+ repository.getConflictingServices(packet))
+ }
+
+
+ @Test
+ fun testGetConflictingServices_customHostsCaseInsensitiveReplyHasIdenticalHosts_noConflict() {
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+ repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1)
+ repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2)
+
+ val packet = MdnsPacket(
+ 0, /* flags */
+ emptyList(), /* questions */
+ listOf(
+ MdnsInetAddressRecord(arrayOf("TESTHOST", "local"),
+ 0L /* receiptTimeMillis */, true /* cacheFlush */,
+ 0L /* ttlMillis */, parseNumericAddress("2001:db8::1")),
+ MdnsInetAddressRecord(arrayOf("testhost", "local"),
+ 0L /* receiptTimeMillis */, true /* cacheFlush */,
+ 0L /* ttlMillis */, parseNumericAddress("2001:db8::2")),
+ ) /* answers */,
+ emptyList() /* authorityRecords */,
+ emptyList() /* additionalRecords */)
+
+ assertEquals(emptyMap(),
+ repository.getConflictingServices(packet))
}
@Test
@@ -873,7 +1194,7 @@
emptyList() /* additionalRecords */)
// Above records are identical to the actual registrations: no conflict
- assertEquals(emptySet(), repository.getConflictingServices(packet))
+ assertEquals(emptyMap(), repository.getConflictingServices(packet))
}
@Test
@@ -902,7 +1223,7 @@
emptyList() /* additionalRecords */)
// Above records are identical to the actual registrations: no conflict
- assertEquals(emptySet(), repository.getConflictingServices(packet))
+ assertEquals(emptyMap(), repository.getConflictingServices(packet))
}
@Test
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
index 7a2e4bf..58124f3 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
@@ -16,7 +16,13 @@
package com.android.server.connectivity.mdns;
+import static com.android.server.connectivity.mdns.MdnsSearchOptions.ACTIVE_QUERY_MODE;
+import static com.android.server.connectivity.mdns.MdnsSearchOptions.AGGRESSIVE_QUERY_MODE;
+import static com.android.server.connectivity.mdns.MdnsSearchOptions.PASSIVE_QUERY_MODE;
import static com.android.server.connectivity.mdns.MdnsServiceTypeClient.EVENT_START_QUERYTASK;
+import static com.android.server.connectivity.mdns.QueryTaskConfig.INITIAL_AGGRESSIVE_TIME_BETWEEN_BURSTS_MS;
+import static com.android.server.connectivity.mdns.QueryTaskConfig.MAX_TIME_BETWEEN_AGGRESSIVE_BURSTS_MS;
+import static com.android.server.connectivity.mdns.QueryTaskConfig.TIME_BETWEEN_RETRANSMISSION_QUERIES_IN_BURST_MS;
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static org.junit.Assert.assertArrayEquals;
@@ -145,8 +151,8 @@
MockitoAnnotations.initMocks(this);
doReturn(TEST_ELAPSED_REALTIME).when(mockDecoderClock).elapsedRealtime();
- expectedIPv4Packets = new DatagramPacket[16];
- expectedIPv6Packets = new DatagramPacket[16];
+ expectedIPv4Packets = new DatagramPacket[24];
+ expectedIPv6Packets = new DatagramPacket[24];
socketKey = new SocketKey(mockNetwork, INTERFACE_INDEX);
for (int i = 0; i < expectedIPv4Packets.length; ++i) {
@@ -171,7 +177,15 @@
.thenReturn(expectedIPv4Packets[12])
.thenReturn(expectedIPv4Packets[13])
.thenReturn(expectedIPv4Packets[14])
- .thenReturn(expectedIPv4Packets[15]);
+ .thenReturn(expectedIPv4Packets[15])
+ .thenReturn(expectedIPv4Packets[16])
+ .thenReturn(expectedIPv4Packets[17])
+ .thenReturn(expectedIPv4Packets[18])
+ .thenReturn(expectedIPv4Packets[19])
+ .thenReturn(expectedIPv4Packets[20])
+ .thenReturn(expectedIPv4Packets[21])
+ .thenReturn(expectedIPv4Packets[22])
+ .thenReturn(expectedIPv4Packets[23]);
when(mockPacketWriter.getPacket(IPV6_ADDRESS))
.thenReturn(expectedIPv6Packets[0])
@@ -189,7 +203,15 @@
.thenReturn(expectedIPv6Packets[12])
.thenReturn(expectedIPv6Packets[13])
.thenReturn(expectedIPv6Packets[14])
- .thenReturn(expectedIPv6Packets[15]);
+ .thenReturn(expectedIPv6Packets[15])
+ .thenReturn(expectedIPv6Packets[16])
+ .thenReturn(expectedIPv6Packets[17])
+ .thenReturn(expectedIPv6Packets[18])
+ .thenReturn(expectedIPv6Packets[19])
+ .thenReturn(expectedIPv6Packets[20])
+ .thenReturn(expectedIPv6Packets[21])
+ .thenReturn(expectedIPv6Packets[22])
+ .thenReturn(expectedIPv6Packets[23]);
thread = new HandlerThread("MdnsServiceTypeClientTests");
thread.start();
@@ -219,15 +241,22 @@
return true;
}).when(mockDeps).sendMessage(any(Handler.class), any(Message.class));
- client =
- new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
- mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps,
- serviceCache) {
- @Override
- MdnsPacketWriter createMdnsPacketWriter() {
- return mockPacketWriter;
- }
- };
+ client = makeMdnsServiceTypeClient(mockPacketWriter);
+ }
+
+ private MdnsServiceTypeClient makeMdnsServiceTypeClient(
+ @Nullable MdnsPacketWriter packetWriter) {
+ return new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
+ mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps,
+ serviceCache) {
+ @Override
+ MdnsPacketWriter createMdnsPacketWriter() {
+ if (packetWriter == null) {
+ return super.createMdnsPacketWriter();
+ }
+ return packetWriter;
+ }
+ };
}
@After
@@ -267,8 +296,8 @@
@Test
public void sendQueries_activeScanMode() {
- MdnsSearchOptions searchOptions =
- MdnsSearchOptions.newBuilder().addSubtype(SUBTYPE).setIsPassiveMode(false).build();
+ MdnsSearchOptions searchOptions = MdnsSearchOptions.newBuilder()
+ .addSubtype(SUBTYPE).setQueryMode(ACTIVE_QUERY_MODE).build();
startSendAndReceive(mockListenerOne, searchOptions);
// Always try to remove the task.
verify(mockDeps, times(1)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
@@ -319,8 +348,8 @@
@Test
public void sendQueries_reentry_activeScanMode() {
- MdnsSearchOptions searchOptions =
- MdnsSearchOptions.newBuilder().addSubtype(SUBTYPE).setIsPassiveMode(false).build();
+ MdnsSearchOptions searchOptions = MdnsSearchOptions.newBuilder()
+ .addSubtype(SUBTYPE).setQueryMode(ACTIVE_QUERY_MODE).build();
startSendAndReceive(mockListenerOne, searchOptions);
// Always try to remove the task.
verify(mockDeps, times(1)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
@@ -333,7 +362,7 @@
MdnsSearchOptions.newBuilder()
.addSubtype(SUBTYPE)
.addSubtype("_subtype2")
- .setIsPassiveMode(false)
+ .setQueryMode(ACTIVE_QUERY_MODE)
.build();
startSendAndReceive(mockListenerOne, searchOptions);
// The previous scheduled task should be canceled.
@@ -353,8 +382,8 @@
@Test
public void sendQueries_passiveScanMode() {
- MdnsSearchOptions searchOptions =
- MdnsSearchOptions.newBuilder().addSubtype(SUBTYPE).setIsPassiveMode(true).build();
+ MdnsSearchOptions searchOptions = MdnsSearchOptions.newBuilder()
+ .addSubtype(SUBTYPE).setQueryMode(PASSIVE_QUERY_MODE).build();
startSendAndReceive(mockListenerOne, searchOptions);
// Always try to remove the task.
verify(mockDeps, times(1)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
@@ -380,8 +409,10 @@
@Test
public void sendQueries_activeScanWithQueryBackoff() {
MdnsSearchOptions searchOptions =
- MdnsSearchOptions.newBuilder().addSubtype(SUBTYPE).setIsPassiveMode(
- false).setNumOfQueriesBeforeBackoff(11).build();
+ MdnsSearchOptions.newBuilder()
+ .addSubtype(SUBTYPE)
+ .setQueryMode(ACTIVE_QUERY_MODE)
+ .setNumOfQueriesBeforeBackoff(11).build();
startSendAndReceive(mockListenerOne, searchOptions);
// Always try to remove the task.
verify(mockDeps, times(1)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
@@ -439,8 +470,10 @@
@Test
public void sendQueries_passiveScanWithQueryBackoff() {
MdnsSearchOptions searchOptions =
- MdnsSearchOptions.newBuilder().addSubtype(SUBTYPE).setIsPassiveMode(
- true).setNumOfQueriesBeforeBackoff(3).build();
+ MdnsSearchOptions.newBuilder()
+ .addSubtype(SUBTYPE)
+ .setQueryMode(PASSIVE_QUERY_MODE)
+ .setNumOfQueriesBeforeBackoff(3).build();
startSendAndReceive(mockListenerOne, searchOptions);
// Always try to remove the task.
verify(mockDeps, times(1)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
@@ -497,8 +530,8 @@
@Test
public void sendQueries_reentry_passiveScanMode() {
- MdnsSearchOptions searchOptions =
- MdnsSearchOptions.newBuilder().addSubtype(SUBTYPE).setIsPassiveMode(true).build();
+ MdnsSearchOptions searchOptions = MdnsSearchOptions.newBuilder()
+ .addSubtype(SUBTYPE).setQueryMode(PASSIVE_QUERY_MODE).build();
startSendAndReceive(mockListenerOne, searchOptions);
// Always try to remove the task.
verify(mockDeps, times(1)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
@@ -511,7 +544,7 @@
MdnsSearchOptions.newBuilder()
.addSubtype(SUBTYPE)
.addSubtype("_subtype2")
- .setIsPassiveMode(true)
+ .setQueryMode(PASSIVE_QUERY_MODE)
.build();
startSendAndReceive(mockListenerOne, searchOptions);
// The previous scheduled task should be canceled.
@@ -533,16 +566,15 @@
@Ignore("MdnsConfigs is not configurable currently.")
public void testQueryTaskConfig_alwaysAskForUnicastResponse() {
//MdnsConfigsFlagsImpl.alwaysAskForUnicastResponseInEachBurst.override(true);
- MdnsSearchOptions searchOptions =
- MdnsSearchOptions.newBuilder().addSubtype(SUBTYPE).setIsPassiveMode(false).build();
+ MdnsSearchOptions searchOptions = MdnsSearchOptions.newBuilder()
+ .addSubtype(SUBTYPE).setQueryMode(ACTIVE_QUERY_MODE).build();
QueryTaskConfig config = new QueryTaskConfig(
- searchOptions.getSubtypes(), searchOptions.isPassiveMode(),
+ searchOptions.getQueryMode(),
false /* onlyUseIpv6OnIpv6OnlyNetworks */, 3 /* numOfQueriesBeforeBackoff */,
socketKey);
// This is the first query. We will ask for unicast response.
assertTrue(config.expectUnicastResponse);
- assertEquals(config.subtypes, searchOptions.getSubtypes());
assertEquals(config.transactionId, 1);
// For the rest of queries in this burst, we will NOT ask for unicast response.
@@ -550,7 +582,6 @@
int oldTransactionId = config.transactionId;
config = config.getConfigForNextRun();
assertFalse(config.expectUnicastResponse);
- assertEquals(config.subtypes, searchOptions.getSubtypes());
assertEquals(config.transactionId, oldTransactionId + 1);
}
@@ -558,22 +589,20 @@
int oldTransactionId = config.transactionId;
config = config.getConfigForNextRun();
assertTrue(config.expectUnicastResponse);
- assertEquals(config.subtypes, searchOptions.getSubtypes());
assertEquals(config.transactionId, oldTransactionId + 1);
}
@Test
public void testQueryTaskConfig_askForUnicastInFirstQuery() {
- MdnsSearchOptions searchOptions =
- MdnsSearchOptions.newBuilder().addSubtype(SUBTYPE).setIsPassiveMode(false).build();
+ MdnsSearchOptions searchOptions = MdnsSearchOptions.newBuilder()
+ .addSubtype(SUBTYPE).setQueryMode(ACTIVE_QUERY_MODE).build();
QueryTaskConfig config = new QueryTaskConfig(
- searchOptions.getSubtypes(), searchOptions.isPassiveMode(),
+ searchOptions.getQueryMode(),
false /* onlyUseIpv6OnIpv6OnlyNetworks */, 3 /* numOfQueriesBeforeBackoff */,
socketKey);
// This is the first query. We will ask for unicast response.
assertTrue(config.expectUnicastResponse);
- assertEquals(config.subtypes, searchOptions.getSubtypes());
assertEquals(config.transactionId, 1);
// For the rest of queries in this burst, we will NOT ask for unicast response.
@@ -581,7 +610,6 @@
int oldTransactionId = config.transactionId;
config = config.getConfigForNextRun();
assertFalse(config.expectUnicastResponse);
- assertEquals(config.subtypes, searchOptions.getSubtypes());
assertEquals(config.transactionId, oldTransactionId + 1);
}
@@ -589,14 +617,13 @@
int oldTransactionId = config.transactionId;
config = config.getConfigForNextRun();
assertFalse(config.expectUnicastResponse);
- assertEquals(config.subtypes, searchOptions.getSubtypes());
assertEquals(config.transactionId, oldTransactionId + 1);
}
@Test
public void testIfPreviousTaskIsCanceledWhenNewSessionStarts() {
- MdnsSearchOptions searchOptions =
- MdnsSearchOptions.newBuilder().addSubtype(SUBTYPE).setIsPassiveMode(true).build();
+ MdnsSearchOptions searchOptions = MdnsSearchOptions.newBuilder()
+ .addSubtype(SUBTYPE).setQueryMode(PASSIVE_QUERY_MODE).build();
startSendAndReceive(mockListenerOne, searchOptions);
Runnable firstMdnsTask = currentThreadExecutor.getAndClearSubmittedRunnable();
@@ -605,7 +632,7 @@
MdnsSearchOptions.newBuilder()
.addSubtype(SUBTYPE)
.addSubtype("_subtype2")
- .setIsPassiveMode(true)
+ .setQueryMode(PASSIVE_QUERY_MODE)
.build();
startSendAndReceive(mockListenerOne, searchOptions);
@@ -624,8 +651,8 @@
@Ignore("MdnsConfigs is not configurable currently.")
public void testIfPreviousTaskIsCanceledWhenSessionStops() {
//MdnsConfigsFlagsImpl.shouldCancelScanTaskWhenFutureIsNull.override(true);
- MdnsSearchOptions searchOptions =
- MdnsSearchOptions.newBuilder().addSubtype(SUBTYPE).setIsPassiveMode(true).build();
+ MdnsSearchOptions searchOptions = MdnsSearchOptions.newBuilder()
+ .addSubtype(SUBTYPE).setQueryMode(PASSIVE_QUERY_MODE).build();
startSendAndReceive(mockListenerOne, searchOptions);
// Change the sutypes and start a new session.
stopSendAndReceive(mockListenerOne);
@@ -667,6 +694,81 @@
any(), any(), eq(MdnsConfigs.timeBetweenQueriesInBurstMs()));
}
+ @Test
+ public void testCombinedSubtypesQueriedWithMultipleListeners() throws Exception {
+ client = makeMdnsServiceTypeClient(/* packetWriter= */ null);
+ final MdnsSearchOptions searchOptions1 = MdnsSearchOptions.newBuilder()
+ .addSubtype("subtype1").build();
+ final MdnsSearchOptions searchOptions2 = MdnsSearchOptions.newBuilder()
+ .addSubtype("subtype2").build();
+ startSendAndReceive(mockListenerOne, searchOptions1);
+ currentThreadExecutor.getAndClearSubmittedRunnable().run();
+
+ InOrder inOrder = inOrder(mockListenerOne, mockSocketClient, mockDeps);
+
+ // Verify the query asks for subtype1
+ final ArgumentCaptor<DatagramPacket> subtype1QueryCaptor =
+ ArgumentCaptor.forClass(DatagramPacket.class);
+ currentThreadExecutor.getAndClearLastScheduledRunnable().run();
+ // Send twice for IPv4 and IPv6
+ inOrder.verify(mockSocketClient, times(2)).sendPacketRequestingUnicastResponse(
+ subtype1QueryCaptor.capture(),
+ eq(socketKey), eq(false));
+
+ final MdnsPacket subtype1Query = MdnsPacket.parse(
+ new MdnsPacketReader(subtype1QueryCaptor.getValue()));
+
+ assertEquals(2, subtype1Query.questions.size());
+ assertTrue(hasQuestion(subtype1Query, MdnsRecord.TYPE_PTR, SERVICE_TYPE_LABELS));
+ assertTrue(hasQuestion(subtype1Query, MdnsRecord.TYPE_PTR,
+ getServiceTypeWithSubtype("_subtype1")));
+
+ // Add subtype2
+ startSendAndReceive(mockListenerTwo, searchOptions2);
+ inOrder.verify(mockDeps).removeMessages(any(), eq(EVENT_START_QUERYTASK));
+ currentThreadExecutor.getAndClearLastScheduledRunnable().run();
+
+ final ArgumentCaptor<DatagramPacket> combinedSubtypesQueryCaptor =
+ ArgumentCaptor.forClass(DatagramPacket.class);
+ inOrder.verify(mockSocketClient, times(2)).sendPacketRequestingUnicastResponse(
+ combinedSubtypesQueryCaptor.capture(),
+ eq(socketKey), eq(false));
+ // The next query must have been scheduled
+ inOrder.verify(mockDeps).sendMessageDelayed(any(), any(), anyLong());
+
+ final MdnsPacket combinedSubtypesQuery = MdnsPacket.parse(
+ new MdnsPacketReader(combinedSubtypesQueryCaptor.getValue()));
+
+ assertEquals(3, combinedSubtypesQuery.questions.size());
+ assertTrue(hasQuestion(combinedSubtypesQuery, MdnsRecord.TYPE_PTR, SERVICE_TYPE_LABELS));
+ assertTrue(hasQuestion(combinedSubtypesQuery, MdnsRecord.TYPE_PTR,
+ getServiceTypeWithSubtype("_subtype1")));
+ assertTrue(hasQuestion(combinedSubtypesQuery, MdnsRecord.TYPE_PTR,
+ getServiceTypeWithSubtype("_subtype2")));
+
+ // Remove subtype1
+ stopSendAndReceive(mockListenerOne);
+
+ // Queries are not rescheduled, but the next query is affected
+ dispatchMessage();
+ currentThreadExecutor.getAndClearLastScheduledRunnable().run();
+
+ final ArgumentCaptor<DatagramPacket> subtype2QueryCaptor =
+ ArgumentCaptor.forClass(DatagramPacket.class);
+ // Send twice for IPv4 and IPv6
+ inOrder.verify(mockSocketClient, times(2)).sendPacketRequestingMulticastResponse(
+ subtype2QueryCaptor.capture(),
+ eq(socketKey), eq(false));
+
+ final MdnsPacket subtype2Query = MdnsPacket.parse(
+ new MdnsPacketReader(subtype2QueryCaptor.getValue()));
+
+ assertEquals(2, subtype2Query.questions.size());
+ assertTrue(hasQuestion(subtype2Query, MdnsRecord.TYPE_PTR, SERVICE_TYPE_LABELS));
+ assertTrue(hasQuestion(subtype2Query, MdnsRecord.TYPE_PTR,
+ getServiceTypeWithSubtype("_subtype2")));
+ }
+
private static void verifyServiceInfo(MdnsServiceInfo serviceInfo, String serviceName,
String[] serviceType, List<String> ipv4Addresses, List<String> ipv6Addresses, int port,
List<String> subTypes, Map<String, String> attributes, SocketKey socketKey) {
@@ -919,15 +1021,7 @@
public void processResponse_searchOptionsEnableServiceRemoval_shouldRemove()
throws Exception {
final String serviceInstanceName = "service-instance-1";
- client =
- new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
- mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps,
- serviceCache) {
- @Override
- MdnsPacketWriter createMdnsPacketWriter() {
- return mockPacketWriter;
- }
- };
+ client = makeMdnsServiceTypeClient(mockPacketWriter);
MdnsSearchOptions searchOptions = MdnsSearchOptions.newBuilder()
.setRemoveExpiredService(true)
.setNumOfQueriesBeforeBackoff(Integer.MAX_VALUE)
@@ -965,15 +1059,7 @@
public void processResponse_searchOptionsNotEnableServiceRemoval_shouldNotRemove()
throws Exception {
final String serviceInstanceName = "service-instance-1";
- client =
- new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
- mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps,
- serviceCache) {
- @Override
- MdnsPacketWriter createMdnsPacketWriter() {
- return mockPacketWriter;
- }
- };
+ client = makeMdnsServiceTypeClient(mockPacketWriter);
startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
Runnable firstMdnsTask = currentThreadExecutor.getAndClearSubmittedRunnable();
@@ -999,15 +1085,7 @@
throws Exception {
//MdnsConfigsFlagsImpl.removeServiceAfterTtlExpires.override(true);
final String serviceInstanceName = "service-instance-1";
- client =
- new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
- mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps,
- serviceCache) {
- @Override
- MdnsPacketWriter createMdnsPacketWriter() {
- return mockPacketWriter;
- }
- };
+ client = makeMdnsServiceTypeClient(mockPacketWriter);
startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
Runnable firstMdnsTask = currentThreadExecutor.getAndClearSubmittedRunnable();
@@ -1122,9 +1200,7 @@
@Test
public void testProcessResponse_Resolve() throws Exception {
- client = new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
- mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps,
- serviceCache);
+ client = makeMdnsServiceTypeClient(/* packetWriter= */ null);
final String instanceName = "service-instance";
final String[] hostname = new String[] { "testhost "};
@@ -1217,9 +1293,7 @@
@Test
public void testRenewTxtSrvInResolve() throws Exception {
- client = new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
- mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps,
- serviceCache);
+ client = makeMdnsServiceTypeClient(/* packetWriter= */ null);
final String instanceName = "service-instance";
final String[] hostname = new String[] { "testhost "};
@@ -1333,9 +1407,7 @@
@Test
public void testProcessResponse_ResolveExcludesOtherServices() {
- client = new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
- mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps,
- serviceCache);
+ client = makeMdnsServiceTypeClient(/* packetWriter= */ null);
final String requestedInstance = "instance1";
final String otherInstance = "instance2";
@@ -1403,9 +1475,7 @@
@Test
public void testProcessResponse_SubtypeDiscoveryLimitedToSubtype() {
- client = new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
- mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps,
- serviceCache);
+ client = makeMdnsServiceTypeClient(/* packetWriter= */ null);
final String matchingInstance = "instance1";
final String subtype = "_subtype";
@@ -1492,10 +1562,91 @@
}
@Test
+ public void testProcessResponse_SubtypeChange() {
+ client = makeMdnsServiceTypeClient(/* packetWriter= */ null);
+
+ final String matchingInstance = "instance1";
+ final String subtype = "_subtype";
+ final String ipV4Address = "192.0.2.0";
+ final String ipV6Address = "2001:db8::";
+
+ final MdnsSearchOptions options = MdnsSearchOptions.newBuilder()
+ .addSubtype("othersub").build();
+
+ startSendAndReceive(mockListenerOne, options);
+
+ // Complete response from instanceName
+ final MdnsPacket packetWithoutSubtype = createResponse(
+ matchingInstance, ipV4Address, 5353, SERVICE_TYPE_LABELS,
+ Collections.emptyMap() /* textAttributes */, TEST_TTL);
+ final MdnsPointerRecord originalPtr = (MdnsPointerRecord) CollectionUtils.findFirst(
+ packetWithoutSubtype.answers, r -> r instanceof MdnsPointerRecord);
+
+ // Add a subtype PTR record
+ final ArrayList<MdnsRecord> newAnswers = new ArrayList<>(packetWithoutSubtype.answers);
+ newAnswers.add(new MdnsPointerRecord(
+ // PTR should be _subtype._sub._type._tcp.local -> instance1._type._tcp.local
+ Stream.concat(Stream.of(subtype, "_sub"), Arrays.stream(SERVICE_TYPE_LABELS))
+ .toArray(String[]::new),
+ originalPtr.getReceiptTime(), originalPtr.getCacheFlush(), originalPtr.getTtl(),
+ originalPtr.getPointer()));
+ processResponse(new MdnsPacket(
+ packetWithoutSubtype.flags,
+ packetWithoutSubtype.questions,
+ newAnswers,
+ packetWithoutSubtype.authorityRecords,
+ packetWithoutSubtype.additionalRecords), socketKey);
+
+ // The subtype does not match
+ final InOrder inOrder = inOrder(mockListenerOne);
+ inOrder.verify(mockListenerOne, never()).onServiceNameDiscovered(any(), anyBoolean());
+
+ // Add another matching subtype
+ newAnswers.add(new MdnsPointerRecord(
+ // PTR should be _subtype._sub._type._tcp.local -> instance1._type._tcp.local
+ Stream.concat(Stream.of("_othersub", "_sub"), Arrays.stream(SERVICE_TYPE_LABELS))
+ .toArray(String[]::new),
+ originalPtr.getReceiptTime(), originalPtr.getCacheFlush(), originalPtr.getTtl(),
+ originalPtr.getPointer()));
+ processResponse(new MdnsPacket(
+ packetWithoutSubtype.flags,
+ packetWithoutSubtype.questions,
+ newAnswers,
+ packetWithoutSubtype.authorityRecords,
+ packetWithoutSubtype.additionalRecords), socketKey);
+
+ final ArgumentMatcher<MdnsServiceInfo> subtypeInstanceMatcher = info ->
+ info.getServiceInstanceName().equals(matchingInstance)
+ && info.getSubtypes().equals(List.of("_subtype", "_othersub"));
+
+ // Service found callbacks are sent now
+ inOrder.verify(mockListenerOne).onServiceNameDiscovered(
+ argThat(subtypeInstanceMatcher), eq(false) /* isServiceFromCache */);
+ inOrder.verify(mockListenerOne).onServiceFound(
+ argThat(subtypeInstanceMatcher), eq(false) /* isServiceFromCache */);
+
+ // Address update: update callbacks are sent
+ processResponse(createResponse(
+ matchingInstance, ipV6Address, 5353, SERVICE_TYPE_LABELS,
+ Collections.emptyMap(), TEST_TTL), socketKey);
+
+ inOrder.verify(mockListenerOne).onServiceUpdated(argThat(info ->
+ subtypeInstanceMatcher.matches(info)
+ && info.getIpv4Addresses().equals(List.of(ipV4Address))
+ && info.getIpv6Addresses().equals(List.of(ipV6Address))));
+
+ // Goodbye: service removed callbacks are sent
+ processResponse(createResponse(
+ matchingInstance, ipV6Address, 5353, SERVICE_TYPE_LABELS,
+ Collections.emptyMap(), 0L /* ttl */), socketKey);
+
+ inOrder.verify(mockListenerOne).onServiceRemoved(matchServiceName(matchingInstance));
+ inOrder.verify(mockListenerOne).onServiceNameRemoved(matchServiceName(matchingInstance));
+ }
+
+ @Test
public void testNotifySocketDestroyed() throws Exception {
- client = new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
- mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps,
- serviceCache);
+ client = makeMdnsServiceTypeClient(/* packetWriter= */ null);
final String requestedInstance = "instance1";
final String otherInstance = "instance2";
@@ -1666,6 +1817,111 @@
socketKey);
}
+ @Test
+ public void sendQueries_aggressiveScanMode() {
+ final MdnsSearchOptions searchOptions = MdnsSearchOptions.newBuilder()
+ .addSubtype(SUBTYPE).setQueryMode(AGGRESSIVE_QUERY_MODE).build();
+ startSendAndReceive(mockListenerOne, searchOptions);
+ // Always try to remove the task.
+ verify(mockDeps, times(1)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
+
+ int burstCounter = 0;
+ int betweenBurstTime = 0;
+ for (int i = 0; i < expectedIPv4Packets.length; i += 3) {
+ verifyAndSendQuery(i, betweenBurstTime, /* expectsUnicastResponse= */ true);
+ verifyAndSendQuery(i + 1, /* timeInMs= */ 0, /* expectsUnicastResponse= */ false);
+ verifyAndSendQuery(i + 2, TIME_BETWEEN_RETRANSMISSION_QUERIES_IN_BURST_MS,
+ /* expectsUnicastResponse= */ false);
+ betweenBurstTime = Math.min(
+ INITIAL_AGGRESSIVE_TIME_BETWEEN_BURSTS_MS * (int) Math.pow(2, burstCounter),
+ MAX_TIME_BETWEEN_AGGRESSIVE_BURSTS_MS);
+ burstCounter++;
+ }
+ // Verify that Task is not removed before stopSendAndReceive was called.
+ verify(mockDeps, times(1)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
+
+ // Stop sending packets.
+ stopSendAndReceive(mockListenerOne);
+ verify(mockDeps, times(2)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
+ }
+
+ @Test
+ public void sendQueries_reentry_aggressiveScanMode() {
+ final MdnsSearchOptions searchOptions = MdnsSearchOptions.newBuilder()
+ .addSubtype(SUBTYPE).setQueryMode(AGGRESSIVE_QUERY_MODE).build();
+ startSendAndReceive(mockListenerOne, searchOptions);
+ // Always try to remove the task.
+ verify(mockDeps, times(1)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
+
+ // First burst, first query is sent.
+ verifyAndSendQuery(0, /* timeInMs= */ 0, /* expectsUnicastResponse= */ true);
+
+ // After the first query is sent, change the subtypes, and restart.
+ final MdnsSearchOptions searchOptions2 = MdnsSearchOptions.newBuilder().addSubtype(SUBTYPE)
+ .addSubtype("_subtype2").setQueryMode(AGGRESSIVE_QUERY_MODE).build();
+ startSendAndReceive(mockListenerOne, searchOptions2);
+ // The previous scheduled task should be canceled.
+ verify(mockDeps, times(2)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
+
+ // Queries should continue to be sent.
+ verifyAndSendQuery(1, /* timeInMs= */ 0, /* expectsUnicastResponse= */ true);
+ verifyAndSendQuery(2, /* timeInMs= */ 0, /* expectsUnicastResponse= */ false);
+ verifyAndSendQuery(3, TIME_BETWEEN_RETRANSMISSION_QUERIES_IN_BURST_MS,
+ /* expectsUnicastResponse= */ false);
+
+ // Stop sending packets.
+ stopSendAndReceive(mockListenerOne);
+ verify(mockDeps, times(3)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
+ }
+
+ @Test
+ public void sendQueries_blendScanWithQueryBackoff() {
+ final int numOfQueriesBeforeBackoff = 11;
+ final MdnsSearchOptions searchOptions = MdnsSearchOptions.newBuilder()
+ .addSubtype(SUBTYPE)
+ .setQueryMode(AGGRESSIVE_QUERY_MODE)
+ .setNumOfQueriesBeforeBackoff(numOfQueriesBeforeBackoff)
+ .build();
+ startSendAndReceive(mockListenerOne, searchOptions);
+ // Always try to remove the task.
+ verify(mockDeps, times(1)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
+
+ int burstCounter = 0;
+ int betweenBurstTime = 0;
+ for (int i = 0; i < numOfQueriesBeforeBackoff; i += 3) {
+ verifyAndSendQuery(i, betweenBurstTime, /* expectsUnicastResponse= */ true);
+ verifyAndSendQuery(i + 1, /* timeInMs= */ 0, /* expectsUnicastResponse= */ false);
+ verifyAndSendQuery(i + 2, TIME_BETWEEN_RETRANSMISSION_QUERIES_IN_BURST_MS,
+ /* expectsUnicastResponse= */ false);
+ betweenBurstTime = Math.min(
+ INITIAL_AGGRESSIVE_TIME_BETWEEN_BURSTS_MS * (int) Math.pow(2, burstCounter),
+ MAX_TIME_BETWEEN_AGGRESSIVE_BURSTS_MS);
+ burstCounter++;
+ }
+ // In backoff mode, the current scheduled task will be canceled and reschedule if the
+ // 0.8 * smallestRemainingTtl is larger than time to next run.
+ long currentTime = TEST_TTL / 2 + TEST_ELAPSED_REALTIME;
+ doReturn(currentTime).when(mockDecoderClock).elapsedRealtime();
+ doReturn(true).when(mockDeps).hasMessages(any(), eq(EVENT_START_QUERYTASK));
+ processResponse(createResponse(
+ "service-instance-1", "192.0.2.123", 5353,
+ SERVICE_TYPE_LABELS,
+ Collections.emptyMap(), TEST_TTL), socketKey);
+ verify(mockDeps, times(2)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
+ assertNotNull(delayMessage);
+ verifyAndSendQuery(12 /* index */, (long) (TEST_TTL / 2 * 0.8) /* timeInMs */,
+ true /* expectsUnicastResponse */, true /* multipleSocketDiscovery */,
+ 14 /* scheduledCount */);
+ currentTime += (long) (TEST_TTL / 2 * 0.8);
+ doReturn(currentTime).when(mockDecoderClock).elapsedRealtime();
+ verifyAndSendQuery(13 /* index */, 0 /* timeInMs */,
+ false /* expectsUnicastResponse */, true /* multipleSocketDiscovery */,
+ 15 /* scheduledCount */);
+ verifyAndSendQuery(14 /* index */, TIME_BETWEEN_RETRANSMISSION_QUERIES_IN_BURST_MS,
+ false /* expectsUnicastResponse */, true /* multipleSocketDiscovery */,
+ 16 /* scheduledCount */);
+ }
+
private static MdnsServiceInfo matchServiceName(String name) {
return argThat(info -> info.getServiceInstanceName().equals(name));
}
@@ -1712,6 +1968,11 @@
Arrays.stream(SERVICE_TYPE_LABELS)).toArray(String[]::new);
}
+ private static String[] getServiceTypeWithSubtype(String subtype) {
+ return Stream.concat(Stream.of(subtype, "_sub"),
+ Arrays.stream(SERVICE_TYPE_LABELS)).toArray(String[]::new);
+ }
+
private static boolean hasQuestion(MdnsPacket packet, int type) {
return hasQuestion(packet, type, null);
}
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkFallbackTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkFallbackTest.kt
new file mode 100644
index 0000000..9024641
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkFallbackTest.kt
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 android.net.IpPrefix
+import android.net.INetd
+import android.net.LinkAddress
+import android.net.LinkProperties
+import android.net.NativeNetworkConfig
+import android.net.NativeNetworkType
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED
+import android.net.NetworkScore
+import android.net.NetworkCapabilities.TRANSPORT_SATELLITE
+import android.net.NetworkScore.KEEP_CONNECTED_FOR_TEST
+import android.net.RouteInfo
+import android.net.UidRange
+import android.net.UidRangeParcel
+import android.net.VpnManager
+import android.net.netd.aidl.NativeUidRangeConfig
+import android.os.Build
+import android.os.UserHandle
+import android.util.ArraySet
+import com.android.net.module.util.CollectionUtils
+import com.android.server.ConnectivityService.PREFERENCE_ORDER_SATELLITE_FALLBACK
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.visibleOnHandlerThread
+import org.junit.Assert
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.never
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+private const val SECONDARY_USER = 10
+private val SECONDARY_USER_HANDLE = UserHandle(SECONDARY_USER)
+private const val TEST_PACKAGE_UID = 123
+private const val TEST_PACKAGE_UID2 = 321
+
+@DevSdkIgnoreRunner.MonitorThreadLeak
+@RunWith(DevSdkIgnoreRunner::class)
+@IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+class CSSatelliteNetworkPreferredTest : CSTest() {
+ /**
+ * Test createMultiLayerNrisFromSatelliteNetworkPreferredUids returns correct
+ * NetworkRequestInfo.
+ */
+ @Test
+ fun testCreateMultiLayerNrisFromSatelliteNetworkPreferredUids() {
+ // Verify that empty uid set should not create any NRI for it.
+ val nrisNoUid = service.createMultiLayerNrisFromSatelliteNetworkFallbackUids(emptySet())
+ Assert.assertEquals(0, nrisNoUid.size.toLong())
+ val uid1 = PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID)
+ val uid2 = PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID2)
+ val uid3 = SECONDARY_USER_HANDLE.getUid(TEST_PACKAGE_UID)
+ assertCreateMultiLayerNrisFromSatelliteNetworkPreferredUids(mutableSetOf(uid1))
+ assertCreateMultiLayerNrisFromSatelliteNetworkPreferredUids(mutableSetOf(uid1, uid3))
+ assertCreateMultiLayerNrisFromSatelliteNetworkPreferredUids(mutableSetOf(uid1, uid2))
+ }
+
+ /**
+ * Test that SATELLITE_NETWORK_PREFERENCE_UIDS changes will send correct net id and uid ranges
+ * to netd.
+ */
+ @Test
+ fun testSatelliteNetworkPreferredUidsChanged() {
+ val netdInOrder = inOrder(netd)
+
+ val satelliteAgent = createSatelliteAgent("satellite0")
+ satelliteAgent.connect()
+
+ val satelliteNetId = satelliteAgent.network.netId
+ netdInOrder.verify(netd).networkCreate(
+ nativeNetworkConfigPhysical(satelliteNetId, INetd.PERMISSION_NONE))
+
+ val uid1 = PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID)
+ val uid2 = PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID2)
+ val uid3 = SECONDARY_USER_HANDLE.getUid(TEST_PACKAGE_UID)
+
+ // Initial satellite network preferred uids status.
+ setAndUpdateSatelliteNetworkPreferredUids(setOf())
+ netdInOrder.verify(netd, never()).networkAddUidRangesParcel(any())
+ netdInOrder.verify(netd, never()).networkRemoveUidRangesParcel(any())
+
+ // Set SATELLITE_NETWORK_PREFERENCE_UIDS setting and verify that net id and uid ranges
+ // send to netd
+ var uids = mutableSetOf(uid1, uid2, uid3)
+ val uidRanges1 = toUidRangeStableParcels(uidRangesForUids(uids))
+ val config1 = NativeUidRangeConfig(
+ satelliteNetId, uidRanges1,
+ PREFERENCE_ORDER_SATELLITE_FALLBACK
+ )
+ setAndUpdateSatelliteNetworkPreferredUids(uids)
+ netdInOrder.verify(netd).networkAddUidRangesParcel(config1)
+ netdInOrder.verify(netd, never()).networkRemoveUidRangesParcel(any())
+
+ // Set SATELLITE_NETWORK_PREFERENCE_UIDS setting again and verify that old rules are removed
+ // and new rules are added.
+ uids = mutableSetOf(uid1)
+ val uidRanges2: Array<UidRangeParcel?> = toUidRangeStableParcels(uidRangesForUids(uids))
+ val config2 = NativeUidRangeConfig(
+ satelliteNetId, uidRanges2,
+ PREFERENCE_ORDER_SATELLITE_FALLBACK
+ )
+ setAndUpdateSatelliteNetworkPreferredUids(uids)
+ netdInOrder.verify(netd).networkRemoveUidRangesParcel(config1)
+ netdInOrder.verify(netd).networkAddUidRangesParcel(config2)
+ }
+
+ private fun assertCreateMultiLayerNrisFromSatelliteNetworkPreferredUids(uids: Set<Int>) {
+ val nris: Set<ConnectivityService.NetworkRequestInfo> =
+ service.createMultiLayerNrisFromSatelliteNetworkFallbackUids(uids)
+ val nri = nris.iterator().next()
+ // Verify that one NRI is created with multilayer requests. Because one NRI can contain
+ // multiple uid ranges, so it only need create one NRI here.
+ assertEquals(1, nris.size.toLong())
+ assertTrue(nri.isMultilayerRequest)
+ assertEquals(nri.uids, uidRangesForUids(uids))
+ assertEquals(PREFERENCE_ORDER_SATELLITE_FALLBACK, nri.mPreferenceOrder)
+ }
+
+ private fun setAndUpdateSatelliteNetworkPreferredUids(uids: Set<Int>) {
+ visibleOnHandlerThread(csHandler) {
+ deps.satelliteNetworkFallbackUidUpdate!!.accept(uids)
+ }
+ }
+
+ private fun nativeNetworkConfigPhysical(netId: Int, permission: Int) =
+ NativeNetworkConfig(netId, NativeNetworkType.PHYSICAL, permission,
+ false /* secure */, VpnManager.TYPE_VPN_NONE, false /* excludeLocalRoutes */)
+
+ private fun createSatelliteAgent(name: String): CSAgentWrapper {
+ return Agent(score = keepScore(), lp = lp(name),
+ nc = nc(TRANSPORT_SATELLITE, NET_CAPABILITY_INTERNET)
+ )
+ }
+
+ private fun toUidRangeStableParcels(ranges: Set<UidRange>): Array<UidRangeParcel?> {
+ val stableRanges = arrayOfNulls<UidRangeParcel>(ranges.size)
+ for ((index, range) in ranges.withIndex()) {
+ stableRanges[index] = UidRangeParcel(range.start, range.stop)
+ }
+ return stableRanges
+ }
+
+ private fun uidRangesForUids(vararg uids: Int): Set<UidRange> {
+ val ranges = ArraySet<UidRange>()
+ for (uid in uids) {
+ ranges.add(UidRange(uid, uid))
+ }
+ return ranges
+ }
+
+ private fun uidRangesForUids(uids: Collection<Int>): Set<UidRange> {
+ return uidRangesForUids(*CollectionUtils.toIntArray(uids))
+ }
+
+ private fun nc(transport: Int, vararg caps: Int) = NetworkCapabilities.Builder().apply {
+ addTransportType(transport)
+ caps.forEach {
+ addCapability(it)
+ }
+ // Useful capabilities for everybody
+ addCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ addCapability(NET_CAPABILITY_NOT_SUSPENDED)
+ addCapability(NET_CAPABILITY_NOT_ROAMING)
+ addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ }.build()
+
+ private fun lp(iface: String) = LinkProperties().apply {
+ interfaceName = iface
+ addLinkAddress(LinkAddress(LOCAL_IPV4_ADDRESS, 32))
+ addRoute(RouteInfo(IpPrefix("0.0.0.0/0"), null, null))
+ }
+
+ // This allows keeping all the networks connected without having to file individual requests
+ // for them.
+ private fun keepScore() = FromS(
+ NetworkScore.Builder().setKeepConnectedReason(KEEP_CONNECTED_FOR_TEST).build()
+ )
+}
diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
index 0708669..b15c684 100644
--- a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
@@ -16,6 +16,7 @@
package com.android.server
+import android.app.AlarmManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
@@ -59,6 +60,7 @@
import com.android.networkstack.apishim.common.UnsupportedApiLevelException
import com.android.server.connectivity.AutomaticOnOffKeepaliveTracker
import com.android.server.connectivity.CarrierPrivilegeAuthenticator
+import com.android.server.connectivity.CarrierPrivilegeAuthenticator.CarrierPrivilegesLostListener
import com.android.server.connectivity.ClatCoordinator
import com.android.server.connectivity.ConnectivityFlags
import com.android.server.connectivity.MulticastRoutingCoordinatorService
@@ -66,13 +68,15 @@
import com.android.server.connectivity.MultinetworkPolicyTrackerTestDependencies
import com.android.server.connectivity.NetworkRequestStateStatsMetrics
import com.android.server.connectivity.ProxyTracker
-import com.android.server.connectivity.RoutingCoordinatorService
+import com.android.server.connectivity.SatelliteAccessController
import com.android.testutils.visibleOnHandlerThread
import com.android.testutils.waitForIdle
import java.util.concurrent.Executors
+import java.util.function.Consumer
import kotlin.test.assertNull
import kotlin.test.fail
import org.junit.After
+import org.junit.Before
import org.mockito.AdditionalAnswers.delegatesTo
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.doReturn
@@ -133,10 +137,12 @@
// permissions using static contexts.
val enabledFeatures = HashMap<String, Boolean>().also {
it[ConnectivityFlags.NO_REMATCH_ALL_REQUESTS_ON_REGISTER] = true
+ it[ConnectivityFlags.REQUEST_RESTRICTED_WIFI] = true
it[ConnectivityService.KEY_DESTROY_FROZEN_SOCKETS_VERSION] = true
it[ConnectivityService.DELAY_DESTROY_FROZEN_SOCKETS_VERSION] = true
it[ConnectivityService.ALLOW_SYSUI_CONNECTIVITY_REPORTS] = true
it[ConnectivityService.LOG_BPF_RC] = true
+ it[ConnectivityService.ALLOW_SATALLITE_NETWORK_FALLBACK] = true
}
fun enableFeature(f: String) = enabledFeatures.set(f, true)
fun disableFeature(f: String) = enabledFeatures.set(f, false)
@@ -163,8 +169,6 @@
val clatCoordinator = mock<ClatCoordinator>()
val networkRequestStateStatsMetrics = mock<NetworkRequestStateStatsMetrics>()
val proxyTracker = ProxyTracker(context, mock<Handler>(), 16 /* EVENT_PROXY_HAS_CHANGED */)
- val alrmHandlerThread = HandlerThread("TestAlarmManager").also { it.start() }
- val alarmManager = makeMockAlarmManager(alrmHandlerThread)
val systemConfigManager = makeMockSystemConfigManager()
val batteryStats = mock<IBatteryStats>()
val batteryManager = BatteryStatsManager(batteryStats)
@@ -173,18 +177,34 @@
}
val multicastRoutingCoordinatorService = mock<MulticastRoutingCoordinatorService>()
+ val satelliteAccessController = mock<SatelliteAccessController>()
val deps = CSDeps()
- val service = makeConnectivityService(context, netd, deps).also { it.systemReadyInternal() }
- val cm = ConnectivityManager(context, service)
- val csHandler = Handler(csHandlerThread.looper)
+
+ // Initializations that start threads are done from setUp to avoid thread leak
+ lateinit var alarmHandlerThread: HandlerThread
+ lateinit var alarmManager: AlarmManager
+ lateinit var service: ConnectivityService
+ lateinit var cm: ConnectivityManager
+ lateinit var csHandler: Handler
+
+ @Before
+ fun setUp() {
+ alarmHandlerThread = HandlerThread("TestAlarmManager").also { it.start() }
+ alarmManager = makeMockAlarmManager(alarmHandlerThread)
+ service = makeConnectivityService(context, netd, deps).also { it.systemReadyInternal() }
+ cm = ConnectivityManager(context, service)
+ // csHandler initialization must be after makeConnectivityService since ConnectivityService
+ // constructor starts csHandlerThread
+ csHandler = Handler(csHandlerThread.looper)
+ }
@After
fun tearDown() {
csHandlerThread.quitSafely()
csHandlerThread.join()
- alrmHandlerThread.quitSafely()
- alrmHandlerThread.join()
+ alarmHandlerThread.quitSafely()
+ alarmHandlerThread.join()
}
inner class CSDeps : ConnectivityService.Dependencies() {
@@ -200,9 +220,21 @@
override fun makeCarrierPrivilegeAuthenticator(
context: Context,
- tm: TelephonyManager
+ tm: TelephonyManager,
+ requestRestrictedWifiEnabled: Boolean,
+ listener: CarrierPrivilegesLostListener
) = if (SdkLevel.isAtLeastT()) mock<CarrierPrivilegeAuthenticator>() else null
+ var satelliteNetworkFallbackUidUpdate: Consumer<Set<Int>>? = null
+ override fun makeSatelliteAccessController(
+ context: Context,
+ updateSatelliteNetworkFallackUid: Consumer<Set<Int>>?,
+ csHandlerThread: Handler
+ ): SatelliteAccessController? {
+ satelliteNetworkFallbackUidUpdate = updateSatelliteNetworkFallackUid
+ return satelliteAccessController
+ }
+
private inner class AOOKTDeps(c: Context) : AutomaticOnOffKeepaliveTracker.Dependencies(c) {
override fun isTetheringFeatureNotChickenedOut(name: String): Boolean {
return isFeatureEnabled(context, name)
diff --git a/tests/unit/java/com/android/server/net/BpfInterfaceMapHelperTest.java b/tests/unit/java/com/android/server/net/BpfInterfaceMapHelperTest.java
new file mode 100644
index 0000000..7b3bea3
--- /dev/null
+++ b/tests/unit/java/com/android/server/net/BpfInterfaceMapHelperTest.java
@@ -0,0 +1,113 @@
+/*
+ * 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 static android.system.OsConstants.EPERM;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.spy;
+
+import android.os.Build;
+import android.system.ErrnoException;
+import android.util.IndentingPrintWriter;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
+import com.android.net.module.util.IBpfMap;
+import com.android.net.module.util.Struct.S32;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+import com.android.testutils.TestBpfMap;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+@SmallTest
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
+public final class BpfInterfaceMapHelperTest {
+ private static final int TEST_INDEX = 1;
+ private static final int TEST_INDEX2 = 2;
+ private static final String TEST_INTERFACE_NAME = "test1";
+ private static final String TEST_INTERFACE_NAME2 = "test2";
+
+ private BaseNetdUnsolicitedEventListener mListener;
+ private BpfInterfaceMapHelper mUpdater;
+ private IBpfMap<S32, InterfaceMapValue> mBpfMap =
+ spy(new TestBpfMap<>(S32.class, InterfaceMapValue.class));
+
+ private class TestDependencies extends BpfInterfaceMapHelper.Dependencies {
+ @Override
+ public IBpfMap<S32, InterfaceMapValue> getInterfaceMap() {
+ return mBpfMap;
+ }
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mUpdater = new BpfInterfaceMapHelper(new TestDependencies());
+ }
+
+ @Test
+ public void testGetIfNameByIndex() throws Exception {
+ mBpfMap.updateEntry(new S32(TEST_INDEX), new InterfaceMapValue(TEST_INTERFACE_NAME));
+ assertEquals(TEST_INTERFACE_NAME, mUpdater.getIfNameByIndex(TEST_INDEX));
+ }
+
+ @Test
+ public void testGetIfNameByIndexNoEntry() {
+ assertNull(mUpdater.getIfNameByIndex(TEST_INDEX));
+ }
+
+ @Test
+ public void testGetIfNameByIndexException() throws Exception {
+ doThrow(new ErrnoException("", EPERM)).when(mBpfMap).getValue(new S32(TEST_INDEX));
+ assertNull(mUpdater.getIfNameByIndex(TEST_INDEX));
+ }
+
+ private void assertDumpContains(final String dump, final String message) {
+ assertTrue(String.format("dump(%s) does not contain '%s'", dump, message),
+ dump.contains(message));
+ }
+
+ private String getDump() {
+ final StringWriter sw = new StringWriter();
+ mUpdater.dump(new IndentingPrintWriter(new PrintWriter(sw), " "));
+ return sw.toString();
+ }
+
+ @Test
+ public void testDump() throws ErrnoException {
+ mBpfMap.updateEntry(new S32(TEST_INDEX), new InterfaceMapValue(TEST_INTERFACE_NAME));
+ mBpfMap.updateEntry(new S32(TEST_INDEX2), new InterfaceMapValue(TEST_INTERFACE_NAME2));
+
+ final String dump = getDump();
+ assertDumpContains(dump, "IfaceIndexNameMap: OK");
+ assertDumpContains(dump, "ifaceIndex=1 ifaceName=test1");
+ assertDumpContains(dump, "ifaceIndex=2 ifaceName=test2");
+ }
+}
diff --git a/tests/unit/java/com/android/server/net/BpfInterfaceMapUpdaterTest.java b/tests/unit/java/com/android/server/net/BpfInterfaceMapUpdaterTest.java
deleted file mode 100644
index c730856..0000000
--- a/tests/unit/java/com/android/server/net/BpfInterfaceMapUpdaterTest.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * 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 static android.system.OsConstants.EPERM;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-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 android.system.ErrnoException;
-import android.util.IndentingPrintWriter;
-
-import androidx.test.filters.SmallTest;
-
-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.S32;
-import com.android.testutils.DevSdkIgnoreRule;
-import com.android.testutils.DevSdkIgnoreRunner;
-import com.android.testutils.TestBpfMap;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-
-@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;
- private static final String TEST_INTERFACE_NAME = "test1";
- private static final String TEST_INTERFACE_NAME2 = "test2";
-
- private final TestLooper mLooper = new TestLooper();
- private BaseNetdUnsolicitedEventListener mListener;
- private BpfInterfaceMapUpdater mUpdater;
- private IBpfMap<S32, InterfaceMapValue> mBpfMap =
- spy(new TestBpfMap<>(S32.class, InterfaceMapValue.class));
- @Mock private INetd mNetd;
- @Mock private Context mContext;
-
- private class TestDependencies extends BpfInterfaceMapUpdater.Dependencies {
- @Override
- public IBpfMap<S32, InterfaceMapValue> getInterfaceMap() {
- return mBpfMap;
- }
-
- @Override
- public InterfaceParams getInterfaceParams(String ifaceName) {
- if (ifaceName.equals(TEST_INTERFACE_NAME)) {
- return new InterfaceParams(TEST_INTERFACE_NAME, TEST_INDEX,
- MacAddress.ALL_ZEROS_ADDRESS);
- } else if (ifaceName.equals(TEST_INTERFACE_NAME2)) {
- return new InterfaceParams(TEST_INTERFACE_NAME2, TEST_INDEX2,
- MacAddress.ALL_ZEROS_ADDRESS);
- }
-
- return null;
- }
-
- @Override
- public INetd getINetd(Context ctx) {
- return mNetd;
- }
- }
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
- when(mNetd.interfaceGetList()).thenReturn(new String[] {TEST_INTERFACE_NAME});
- mUpdater = new BpfInterfaceMapUpdater(mContext, new Handler(mLooper.getLooper()),
- new TestDependencies());
- }
-
- private void verifyStartUpdater() throws Exception {
- mUpdater.start();
- mLooper.dispatchAll();
- final ArgumentCaptor<BaseNetdUnsolicitedEventListener> listenerCaptor =
- ArgumentCaptor.forClass(BaseNetdUnsolicitedEventListener.class);
- verify(mNetd).registerUnsolicitedEventListener(listenerCaptor.capture());
- mListener = listenerCaptor.getValue();
- verify(mBpfMap).updateEntry(eq(new S32(TEST_INDEX)),
- eq(new InterfaceMapValue(TEST_INTERFACE_NAME)));
- }
-
- @Test
- public void testUpdateInterfaceMap() throws Exception {
- verifyStartUpdater();
-
- mListener.onInterfaceAdded(TEST_INTERFACE_NAME2);
- mLooper.dispatchAll();
- verify(mBpfMap).updateEntry(eq(new S32(TEST_INDEX2)),
- eq(new InterfaceMapValue(TEST_INTERFACE_NAME2)));
-
- // Check that when onInterfaceRemoved is called, nothing happens.
- mListener.onInterfaceRemoved(TEST_INTERFACE_NAME);
- mLooper.dispatchAll();
- verifyNoMoreInteractions(mBpfMap);
- }
-
- @Test
- public void testGetIfNameByIndex() throws Exception {
- mBpfMap.updateEntry(new S32(TEST_INDEX), new InterfaceMapValue(TEST_INTERFACE_NAME));
- assertEquals(TEST_INTERFACE_NAME, mUpdater.getIfNameByIndex(TEST_INDEX));
- }
-
- @Test
- public void testGetIfNameByIndexNoEntry() {
- assertNull(mUpdater.getIfNameByIndex(TEST_INDEX));
- }
-
- @Test
- public void testGetIfNameByIndexException() throws Exception {
- doThrow(new ErrnoException("", EPERM)).when(mBpfMap).getValue(new S32(TEST_INDEX));
- assertNull(mUpdater.getIfNameByIndex(TEST_INDEX));
- }
-
- private void assertDumpContains(final String dump, final String message) {
- assertTrue(String.format("dump(%s) does not contain '%s'", dump, message),
- dump.contains(message));
- }
-
- private String getDump() {
- final StringWriter sw = new StringWriter();
- mUpdater.dump(new IndentingPrintWriter(new PrintWriter(sw), " "));
- return sw.toString();
- }
-
- @Test
- public void testDump() throws ErrnoException {
- mBpfMap.updateEntry(new S32(TEST_INDEX), new InterfaceMapValue(TEST_INTERFACE_NAME));
- mBpfMap.updateEntry(new S32(TEST_INDEX2), new InterfaceMapValue(TEST_INTERFACE_NAME2));
-
- final String dump = getDump();
- assertDumpContains(dump, "IfaceIndexNameMap: OK");
- assertDumpContains(dump, "ifaceIndex=1 ifaceName=test1");
- assertDumpContains(dump, "ifaceIndex=2 ifaceName=test2");
- }
-}
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index a5fee5b..3ed51bc 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -254,7 +254,7 @@
private @Mock AlarmManager mAlarmManager;
@Mock
private NetworkStatsSubscriptionsMonitor mNetworkStatsSubscriptionsMonitor;
- private @Mock BpfInterfaceMapUpdater mBpfInterfaceMapUpdater;
+ private @Mock BpfInterfaceMapHelper mBpfInterfaceMapHelper;
private HandlerThread mHandlerThread;
@Mock
private LocationPermissionChecker mLocationPermissionChecker;
@@ -519,9 +519,8 @@
}
@Override
- public BpfInterfaceMapUpdater makeBpfInterfaceMapUpdater(
- @NonNull Context ctx, @NonNull Handler handler) {
- return mBpfInterfaceMapUpdater;
+ public BpfInterfaceMapHelper makeBpfInterfaceMapHelper() {
+ return mBpfInterfaceMapHelper;
}
@Override
@@ -2764,13 +2763,13 @@
@Test
public void testDumpStatsMap() throws ErrnoException {
- doReturn("wlan0").when(mBpfInterfaceMapUpdater).getIfNameByIndex(10 /* index */);
+ doReturn("wlan0").when(mBpfInterfaceMapHelper).getIfNameByIndex(10 /* index */);
doTestDumpStatsMap("wlan0");
}
@Test
public void testDumpStatsMapUnknownInterface() throws ErrnoException {
- doReturn(null).when(mBpfInterfaceMapUpdater).getIfNameByIndex(10 /* index */);
+ doReturn(null).when(mBpfInterfaceMapHelper).getIfNameByIndex(10 /* index */);
doTestDumpStatsMap("unknown");
}
@@ -2785,13 +2784,13 @@
@Test
public void testDumpIfaceStatsMap() throws Exception {
- doReturn("wlan0").when(mBpfInterfaceMapUpdater).getIfNameByIndex(10 /* index */);
+ doReturn("wlan0").when(mBpfInterfaceMapHelper).getIfNameByIndex(10 /* index */);
doTestDumpIfaceStatsMap("wlan0");
}
@Test
public void testDumpIfaceStatsMapUnknownInterface() throws Exception {
- doReturn(null).when(mBpfInterfaceMapUpdater).getIfNameByIndex(10 /* index */);
+ doReturn(null).when(mBpfInterfaceMapHelper).getIfNameByIndex(10 /* index */);
doTestDumpIfaceStatsMap("unknown");
}
diff --git a/tests/unit/jni/Android.bp b/tests/unit/jni/Android.bp
index 616da81..57a157d 100644
--- a/tests/unit/jni/Android.bp
+++ b/tests/unit/jni/Android.bp
@@ -1,4 +1,5 @@
package {
+ default_team: "trendy_team_fwk_core_networking",
// See: http://go/android-license-faq
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/thread/apex/Android.bp b/thread/apex/Android.bp
index 28854f2..edf000a 100644
--- a/thread/apex/Android.bp
+++ b/thread/apex/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_fwk_thread_network",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/thread/demoapp/Android.bp b/thread/demoapp/Android.bp
index da7a5f8..fcfd469 100644
--- a/thread/demoapp/Android.bp
+++ b/thread/demoapp/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_fwk_thread_network",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/thread/flags/thread_base.aconfig b/thread/flags/thread_base.aconfig
index f73ea6b..09595a6 100644
--- a/thread/flags/thread_base.aconfig
+++ b/thread/flags/thread_base.aconfig
@@ -1,4 +1,5 @@
package: "com.android.net.thread.flags"
+container: "system"
flag {
name: "thread_enabled"
@@ -6,10 +7,3 @@
description: "Controls whether the Android Thread feature is enabled"
bug: "301473012"
}
-
-flag {
- name: "thread_user_restriction_enabled"
- namespace: "thread_network"
- description: "Controls whether user restriction on thread networks is enabled"
- bug: "307679182"
-}
diff --git a/thread/framework/Android.bp b/thread/framework/Android.bp
index cc598d8..846253c 100644
--- a/thread/framework/Android.bp
+++ b/thread/framework/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_fwk_thread_network",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/thread/framework/java/android/net/thread/IStateCallback.aidl b/thread/framework/java/android/net/thread/IStateCallback.aidl
index d7cbda9..9d0a571 100644
--- a/thread/framework/java/android/net/thread/IStateCallback.aidl
+++ b/thread/framework/java/android/net/thread/IStateCallback.aidl
@@ -22,4 +22,5 @@
oneway interface IStateCallback {
void onDeviceRoleChanged(int deviceRole);
void onPartitionIdChanged(long partitionId);
+ void onThreadEnableStateChanged(int enabledState);
}
diff --git a/thread/framework/java/android/net/thread/IThreadNetworkController.aidl b/thread/framework/java/android/net/thread/IThreadNetworkController.aidl
index a9da8d6..485e25d 100644
--- a/thread/framework/java/android/net/thread/IThreadNetworkController.aidl
+++ b/thread/framework/java/android/net/thread/IThreadNetworkController.aidl
@@ -42,4 +42,6 @@
int getThreadVersion();
void createRandomizedDataset(String networkName, IActiveOperationalDatasetReceiver receiver);
+
+ void setEnabled(boolean enabled, in IOperationReceiver receiver);
}
diff --git a/thread/framework/java/android/net/thread/ThreadNetworkController.java b/thread/framework/java/android/net/thread/ThreadNetworkController.java
index 7242ed7..db761a3 100644
--- a/thread/framework/java/android/net/thread/ThreadNetworkController.java
+++ b/thread/framework/java/android/net/thread/ThreadNetworkController.java
@@ -68,6 +68,15 @@
/** The device is a Thread Leader. */
public static final int DEVICE_ROLE_LEADER = 4;
+ /** The Thread radio is disabled. */
+ public static final int STATE_DISABLED = 0;
+
+ /** The Thread radio is enabled. */
+ public static final int STATE_ENABLED = 1;
+
+ /** The Thread radio is being disabled. */
+ public static final int STATE_DISABLING = 2;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef({
@@ -79,6 +88,13 @@
})
public @interface DeviceRole {}
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = {"STATE_"},
+ value = {STATE_DISABLED, STATE_ENABLED, STATE_DISABLING})
+ public @interface EnabledState {}
+
/** Thread standard version 1.3. */
public static final int THREAD_VERSION_1_3 = 4;
@@ -106,6 +122,40 @@
mControllerService = controllerService;
}
+ /**
+ * Enables/Disables the radio of this ThreadNetworkController. The requested enabled state will
+ * be persistent and survives device reboots.
+ *
+ * <p>When Thread is in {@code STATE_DISABLED}, {@link ThreadNetworkController} APIs which
+ * require the Thread radio will fail with error code {@link
+ * ThreadNetworkException#ERROR_THREAD_DISABLED}. When Thread is in {@code STATE_DISABLING},
+ * {@link ThreadNetworkController} APIs that return a {@link ThreadNetworkException} will fail
+ * with error code {@link ThreadNetworkException#ERROR_BUSY}.
+ *
+ * <p>On success, {@link OutcomeReceiver#onResult} of {@code receiver} is called. It indicates
+ * the operation has completed. But there maybe subsequent calls to update the enabled state,
+ * callers of this method should use {@link #registerStateCallback} to subscribe to the Thread
+ * enabled state changes.
+ *
+ * <p>On failure, {@link OutcomeReceiver#onError} of {@code receiver} will be invoked with a
+ * specific error in {@link ThreadNetworkException#ERROR_}.
+ *
+ * @param enabled {@code true} for enabling Thread
+ * @param executor the executor to execute {@code receiver}
+ * @param receiver the receiver to receive result of this operation
+ */
+ @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED")
+ public void setEnabled(
+ boolean enabled,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver) {
+ try {
+ mControllerService.setEnabled(enabled, new OperationReceiverProxy(executor, receiver));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/** Returns the Thread version this device is operating on. */
@ThreadVersion
public int getThreadVersion() {
@@ -170,6 +220,16 @@
* @param partitionId the new Thread partition ID
*/
default void onPartitionIdChanged(long partitionId) {}
+
+ /**
+ * The Thread enabled state has changed.
+ *
+ * <p>The Thread enabled state can be set with {@link setEnabled}, it may also be updated by
+ * airplane mode or admin control.
+ *
+ * @param enabledState the new Thread enabled state
+ */
+ default void onThreadEnableStateChanged(@EnabledState int enabledState) {}
}
private static final class StateCallbackProxy extends IStateCallback.Stub {
@@ -200,6 +260,16 @@
Binder.restoreCallingIdentity(identity);
}
}
+
+ @Override
+ public void onThreadEnableStateChanged(@EnabledState int enabled) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onThreadEnableStateChanged(enabled));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
}
/**
diff --git a/thread/framework/java/android/net/thread/ThreadNetworkException.java b/thread/framework/java/android/net/thread/ThreadNetworkException.java
index 4fd445b..66f13ce 100644
--- a/thread/framework/java/android/net/thread/ThreadNetworkException.java
+++ b/thread/framework/java/android/net/thread/ThreadNetworkException.java
@@ -48,6 +48,7 @@
ERROR_RESPONSE_BAD_FORMAT,
ERROR_RESOURCE_EXHAUSTED,
ERROR_UNKNOWN,
+ ERROR_THREAD_DISABLED,
})
public @interface ErrorCode {}
@@ -124,16 +125,41 @@
public static final int ERROR_RESOURCE_EXHAUSTED = 10;
/**
- * The operation failed because of an unknown error in the system. This typically indicates
- * that the caller doesn't understand error codes added in newer Android versions.
+ * The operation failed because of an unknown error in the system. This typically indicates that
+ * the caller doesn't understand error codes added in newer Android versions.
*/
public static final int ERROR_UNKNOWN = 11;
+ /**
+ * The operation failed because the Thread radio is disabled by {@link
+ * ThreadNetworkController#setEnabled}, airplane mode or device admin. The caller should retry
+ * only after Thread is enabled.
+ */
+ public static final int ERROR_THREAD_DISABLED = 12;
+
+ private static final int ERROR_MIN = ERROR_INTERNAL_ERROR;
+ private static final int ERROR_MAX = ERROR_THREAD_DISABLED;
+
private final int mErrorCode;
- /** Creates a new {@link ThreadNetworkException} object with given error code and message. */
- public ThreadNetworkException(@ErrorCode int errorCode, @NonNull String errorMessage) {
- super(requireNonNull(errorMessage, "errorMessage cannot be null"));
+ /**
+ * Creates a new {@link ThreadNetworkException} object with given error code and message.
+ *
+ * @throws IllegalArgumentException if {@code errorCode} is not a value in {@link #ERROR_}
+ * @throws NullPointerException if {@code message} is {@code null}
+ */
+ public ThreadNetworkException(@ErrorCode int errorCode, @NonNull String message) {
+ super(requireNonNull(message, "message cannot be null"));
+ if (errorCode < ERROR_MIN || errorCode > ERROR_MAX) {
+ throw new IllegalArgumentException(
+ "errorCode cannot be "
+ + errorCode
+ + " (allowedRange = ["
+ + ERROR_MIN
+ + ", "
+ + ERROR_MAX
+ + "])");
+ }
this.mErrorCode = errorCode;
}
diff --git a/thread/scripts/make-pretty.sh b/thread/scripts/make-pretty.sh
index e4bd459..c176bfa 100755
--- a/thread/scripts/make-pretty.sh
+++ b/thread/scripts/make-pretty.sh
@@ -3,5 +3,7 @@
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
GOOGLE_JAVA_FORMAT=$SCRIPT_DIR/../../../../../prebuilts/tools/common/google-java-format/google-java-format
+ANDROID_BP_FORMAT=$SCRIPT_DIR/../../../../../prebuilts/build-tools/linux-x86/bin/bpfmt
$GOOGLE_JAVA_FORMAT --aosp -i $(find $SCRIPT_DIR/../ -name "*.java")
+$ANDROID_BP_FORMAT -w $(find $SCRIPT_DIR/../ -name "*.bp")
diff --git a/thread/service/Android.bp b/thread/service/Android.bp
index 69295cc..6e2fac1 100644
--- a/thread/service/Android.bp
+++ b/thread/service/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_fwk_thread_network",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -47,9 +48,6 @@
"ot-daemon-aidl-java",
],
apex_available: ["com.android.tethering"],
- optimize: {
- proguard_flags_files: ["proguard.flags"],
- },
}
cc_library_shared {
diff --git a/thread/service/java/com/android/server/thread/NsdPublisher.java b/thread/service/java/com/android/server/thread/NsdPublisher.java
new file mode 100644
index 0000000..c74c023
--- /dev/null
+++ b/thread/service/java/com/android/server/thread/NsdPublisher.java
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.thread;
+
+import static android.net.nsd.NsdManager.PROTOCOL_DNS_SD;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.net.nsd.NsdManager;
+import android.net.nsd.NsdServiceInfo;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.thread.openthread.DnsTxtAttribute;
+import com.android.server.thread.openthread.INsdPublisher;
+import com.android.server.thread.openthread.INsdStatusReceiver;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.HashSet;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Implementation of {@link INsdPublisher}.
+ *
+ * <p>This class provides API for service registration and discovery over mDNS. This class is a
+ * proxy between ot-daemon and NsdManager.
+ *
+ * <p>All the data members of this class MUST be accessed in the {@code mHandler}'s Thread except
+ * {@code mHandler} itself.
+ *
+ * <p>TODO: b/323300118 - Remove the following mechanism when the race condition in NsdManager is
+ * fixed.
+ *
+ * <p>There's always only one running registration job at any timepoint. All other pending jobs are
+ * queued in {@code mRegistrationJobs}. When a registration job is complete (i.e. the according
+ * method in {@link NsdManager.RegistrationListener} is called), it will start the next registration
+ * job in the queue.
+ */
+public final class NsdPublisher extends INsdPublisher.Stub {
+ // TODO: b/321883491 - specify network for mDNS operations
+ private static final String TAG = NsdPublisher.class.getSimpleName();
+ private final NsdManager mNsdManager;
+ private final Handler mHandler;
+ private final Executor mExecutor;
+ private final SparseArray<RegistrationListener> mRegistrationListeners = new SparseArray<>(0);
+ private final Deque<Runnable> mRegistrationJobs = new ArrayDeque<>();
+
+ @VisibleForTesting
+ public NsdPublisher(NsdManager nsdManager, Handler handler) {
+ mNsdManager = nsdManager;
+ mHandler = handler;
+ mExecutor = runnable -> mHandler.post(runnable);
+ }
+
+ public static NsdPublisher newInstance(Context context, Handler handler) {
+ return new NsdPublisher(context.getSystemService(NsdManager.class), handler);
+ }
+
+ @Override
+ public void registerService(
+ String hostname,
+ String name,
+ String type,
+ List<String> subTypeList,
+ int port,
+ List<DnsTxtAttribute> txt,
+ INsdStatusReceiver receiver,
+ int listenerId) {
+ postRegistrationJob(
+ () -> {
+ NsdServiceInfo serviceInfo =
+ buildServiceInfoForService(
+ hostname, name, type, subTypeList, port, txt);
+ registerInternal(serviceInfo, receiver, listenerId, "service");
+ });
+ }
+
+ private static NsdServiceInfo buildServiceInfoForService(
+ String hostname,
+ String name,
+ String type,
+ List<String> subTypeList,
+ int port,
+ List<DnsTxtAttribute> txt) {
+ NsdServiceInfo serviceInfo = new NsdServiceInfo();
+
+ serviceInfo.setServiceName(name);
+ if (!TextUtils.isEmpty(hostname)) {
+ serviceInfo.setHostname(hostname);
+ }
+ serviceInfo.setServiceType(type);
+ serviceInfo.setPort(port);
+ serviceInfo.setSubtypes(new HashSet<>(subTypeList));
+ for (DnsTxtAttribute attribute : txt) {
+ serviceInfo.setAttribute(attribute.name, attribute.value);
+ }
+
+ return serviceInfo;
+ }
+
+ private void registerInternal(
+ NsdServiceInfo serviceInfo,
+ INsdStatusReceiver receiver,
+ int listenerId,
+ String registrationType) {
+ checkOnHandlerThread();
+ Log.i(
+ TAG,
+ "Registering "
+ + registrationType
+ + ". Listener ID: "
+ + listenerId
+ + ", serviceInfo: "
+ + serviceInfo);
+ RegistrationListener listener = new RegistrationListener(serviceInfo, listenerId, receiver);
+ mRegistrationListeners.append(listenerId, listener);
+ try {
+ mNsdManager.registerService(serviceInfo, PROTOCOL_DNS_SD, mExecutor, listener);
+ } catch (IllegalArgumentException e) {
+ Log.i(TAG, "Failed to register service. serviceInfo: " + serviceInfo, e);
+ listener.onRegistrationFailed(serviceInfo, NsdManager.FAILURE_INTERNAL_ERROR);
+ }
+ }
+
+ public void unregister(INsdStatusReceiver receiver, int listenerId) {
+ postRegistrationJob(() -> unregisterInternal(receiver, listenerId));
+ }
+
+ public void unregisterInternal(INsdStatusReceiver receiver, int listenerId) {
+ checkOnHandlerThread();
+ RegistrationListener registrationListener = mRegistrationListeners.get(listenerId);
+ if (registrationListener == null) {
+ Log.w(
+ TAG,
+ "Failed to unregister service."
+ + " Listener ID: "
+ + listenerId
+ + " The registrationListener is empty.");
+
+ return;
+ }
+ Log.i(
+ TAG,
+ "Unregistering service."
+ + " Listener ID: "
+ + listenerId
+ + " serviceInfo: "
+ + registrationListener.mServiceInfo);
+ registrationListener.addUnregistrationReceiver(receiver);
+ mNsdManager.unregisterService(registrationListener);
+ }
+
+ private void checkOnHandlerThread() {
+ if (mHandler.getLooper().getThread() != Thread.currentThread()) {
+ throw new IllegalStateException(
+ "Not running on handler Thread: " + Thread.currentThread().getName());
+ }
+ }
+
+ /** On ot-daemon died, unregister all registrations. */
+ public void onOtDaemonDied() {
+ checkOnHandlerThread();
+ for (int i = 0; i < mRegistrationListeners.size(); ++i) {
+ try {
+ mNsdManager.unregisterService(mRegistrationListeners.valueAt(i));
+ } catch (IllegalArgumentException e) {
+ Log.i(
+ TAG,
+ "Failed to unregister."
+ + " Listener ID: "
+ + mRegistrationListeners.keyAt(i)
+ + " serviceInfo: "
+ + mRegistrationListeners.valueAt(i).mServiceInfo,
+ e);
+ }
+ }
+ mRegistrationListeners.clear();
+ }
+
+ // TODO: b/323300118 - Remove this mechanism when the race condition in NsdManager is fixed.
+ /** Fetch the first job from the queue and run it. See the class doc for more details. */
+ private void peekAndRun() {
+ if (mRegistrationJobs.isEmpty()) {
+ return;
+ }
+ Runnable job = mRegistrationJobs.getFirst();
+ job.run();
+ }
+
+ // TODO: b/323300118 - Remove this mechanism when the race condition in NsdManager is fixed.
+ /**
+ * Pop the first job from the queue and run the next job. See the class doc for more details.
+ */
+ private void popAndRunNext() {
+ if (mRegistrationJobs.isEmpty()) {
+ Log.i(TAG, "No registration jobs when trying to pop and run next.");
+ return;
+ }
+ mRegistrationJobs.removeFirst();
+ peekAndRun();
+ }
+
+ private void postRegistrationJob(Runnable registrationJob) {
+ mHandler.post(
+ () -> {
+ mRegistrationJobs.addLast(registrationJob);
+ if (mRegistrationJobs.size() == 1) {
+ peekAndRun();
+ }
+ });
+ }
+
+ private final class RegistrationListener implements NsdManager.RegistrationListener {
+ private final NsdServiceInfo mServiceInfo;
+ private final int mListenerId;
+ private final INsdStatusReceiver mRegistrationReceiver;
+ private final List<INsdStatusReceiver> mUnregistrationReceivers;
+
+ RegistrationListener(
+ @NonNull NsdServiceInfo serviceInfo,
+ int listenerId,
+ @NonNull INsdStatusReceiver registrationReceiver) {
+ mServiceInfo = serviceInfo;
+ mListenerId = listenerId;
+ mRegistrationReceiver = registrationReceiver;
+ mUnregistrationReceivers = new ArrayList<>();
+ }
+
+ void addUnregistrationReceiver(@NonNull INsdStatusReceiver unregistrationReceiver) {
+ mUnregistrationReceivers.add(unregistrationReceiver);
+ }
+
+ @Override
+ public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
+ checkOnHandlerThread();
+ mRegistrationListeners.remove(mListenerId);
+ Log.i(
+ TAG,
+ "Failed to register listener ID: "
+ + mListenerId
+ + " error code: "
+ + errorCode
+ + " serviceInfo: "
+ + serviceInfo);
+ try {
+ mRegistrationReceiver.onError(errorCode);
+ } catch (RemoteException ignored) {
+ // do nothing if the client is dead
+ }
+ popAndRunNext();
+ }
+
+ @Override
+ public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
+ checkOnHandlerThread();
+ for (INsdStatusReceiver receiver : mUnregistrationReceivers) {
+ Log.i(
+ TAG,
+ "Failed to unregister."
+ + "Listener ID: "
+ + mListenerId
+ + ", error code: "
+ + errorCode
+ + ", serviceInfo: "
+ + serviceInfo);
+ try {
+ receiver.onError(errorCode);
+ } catch (RemoteException ignored) {
+ // do nothing if the client is dead
+ }
+ }
+ popAndRunNext();
+ }
+
+ @Override
+ public void onServiceRegistered(NsdServiceInfo serviceInfo) {
+ checkOnHandlerThread();
+ Log.i(
+ TAG,
+ "Registered successfully. "
+ + "Listener ID: "
+ + mListenerId
+ + ", serviceInfo: "
+ + serviceInfo);
+ try {
+ mRegistrationReceiver.onSuccess();
+ } catch (RemoteException ignored) {
+ // do nothing if the client is dead
+ }
+ popAndRunNext();
+ }
+
+ @Override
+ public void onServiceUnregistered(NsdServiceInfo serviceInfo) {
+ checkOnHandlerThread();
+ for (INsdStatusReceiver receiver : mUnregistrationReceivers) {
+ Log.i(
+ TAG,
+ "Unregistered successfully. "
+ + "Listener ID: "
+ + mListenerId
+ + ", serviceInfo: "
+ + serviceInfo);
+ try {
+ receiver.onSuccess();
+ } catch (RemoteException ignored) {
+ // do nothing if the client is dead
+ }
+ }
+ mRegistrationListeners.remove(mListenerId);
+ popAndRunNext();
+ }
+ }
+}
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
index 1c51c42..21e3927 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
@@ -27,6 +27,9 @@
import static android.net.thread.ActiveOperationalDataset.MESH_LOCAL_PREFIX_FIRST_BYTE;
import static android.net.thread.ActiveOperationalDataset.SecurityPolicy.DEFAULT_ROTATION_TIME_HOURS;
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_DETACHED;
+import static android.net.thread.ThreadNetworkController.STATE_DISABLED;
+import static android.net.thread.ThreadNetworkController.STATE_DISABLING;
+import static android.net.thread.ThreadNetworkController.STATE_ENABLED;
import static android.net.thread.ThreadNetworkController.THREAD_VERSION_1_3;
import static android.net.thread.ThreadNetworkException.ERROR_ABORTED;
import static android.net.thread.ThreadNetworkException.ERROR_BUSY;
@@ -35,6 +38,7 @@
import static android.net.thread.ThreadNetworkException.ERROR_REJECTED_BY_PEER;
import static android.net.thread.ThreadNetworkException.ERROR_RESOURCE_EXHAUSTED;
import static android.net.thread.ThreadNetworkException.ERROR_RESPONSE_BAD_FORMAT;
+import static android.net.thread.ThreadNetworkException.ERROR_THREAD_DISABLED;
import static android.net.thread.ThreadNetworkException.ERROR_TIMEOUT;
import static android.net.thread.ThreadNetworkException.ERROR_UNSUPPORTED_CHANNEL;
import static android.net.thread.ThreadNetworkManager.PERMISSION_THREAD_NETWORK_PRIVILEGED;
@@ -48,7 +52,11 @@
import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_REASSEMBLY_TIMEOUT;
import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_REJECTED;
import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_RESPONSE_TIMEOUT;
+import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_THREAD_DISABLED;
import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_UNSUPPORTED_CHANNEL;
+import static com.android.server.thread.openthread.IOtDaemon.OT_STATE_DISABLED;
+import static com.android.server.thread.openthread.IOtDaemon.OT_STATE_DISABLING;
+import static com.android.server.thread.openthread.IOtDaemon.OT_STATE_ENABLED;
import static com.android.server.thread.openthread.IOtDaemon.TUN_IF_NAME;
import android.Manifest.permission;
@@ -58,7 +66,6 @@
import android.annotation.TargetApi;
import android.content.Context;
import android.net.ConnectivityManager;
-import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.LocalNetworkConfig;
@@ -71,7 +78,6 @@
import android.net.NetworkProvider;
import android.net.NetworkRequest;
import android.net.NetworkScore;
-import android.net.RouteInfo;
import android.net.TestNetworkSpecifier;
import android.net.thread.ActiveOperationalDataset;
import android.net.thread.ActiveOperationalDataset.SecurityPolicy;
@@ -143,7 +149,7 @@
private final ConnectivityManager mConnectivityManager;
private final TunInterfaceController mTunIfController;
private final InfraInterfaceController mInfraIfController;
- private final LinkProperties mLinkProperties = new LinkProperties();
+ private final NsdPublisher mNsdPublisher;
private final OtDaemonCallbackProxy mOtDaemonCallbackProxy = new OtDaemonCallbackProxy();
// TODO(b/308310823): read supported channel from Thread dameon
@@ -160,6 +166,7 @@
private UpstreamNetworkCallback mUpstreamNetworkCallback;
private TestNetworkSpecifier mUpstreamTestNetworkSpecifier;
private final HashMap<Network, String> mNetworkToInterface;
+ private final ThreadPersistentSettings mPersistentSettings;
private BorderRouterConfigurationParcel mBorderRouterConfig;
@@ -171,7 +178,9 @@
Supplier<IOtDaemon> otDaemonSupplier,
ConnectivityManager connectivityManager,
TunInterfaceController tunIfController,
- InfraInterfaceController infraIfController) {
+ InfraInterfaceController infraIfController,
+ ThreadPersistentSettings persistentSettings,
+ NsdPublisher nsdPublisher) {
mContext = context;
mHandler = handler;
mNetworkProvider = networkProvider;
@@ -182,22 +191,28 @@
mUpstreamNetworkRequest = newUpstreamNetworkRequest();
mNetworkToInterface = new HashMap<Network, String>();
mBorderRouterConfig = new BorderRouterConfigurationParcel();
+ mPersistentSettings = persistentSettings;
+ mNsdPublisher = nsdPublisher;
}
- public static ThreadNetworkControllerService newInstance(Context context) {
+ public static ThreadNetworkControllerService newInstance(
+ Context context, ThreadPersistentSettings persistentSettings) {
HandlerThread handlerThread = new HandlerThread("ThreadHandlerThread");
handlerThread.start();
+ Handler handler = new Handler(handlerThread.getLooper());
NetworkProvider networkProvider =
new NetworkProvider(context, handlerThread.getLooper(), "ThreadNetworkProvider");
return new ThreadNetworkControllerService(
context,
- new Handler(handlerThread.getLooper()),
+ handler,
networkProvider,
() -> IOtDaemon.Stub.asInterface(ServiceManagerWrapper.waitForService("ot_daemon")),
context.getSystemService(ConnectivityManager.class),
new TunInterfaceController(TUN_IF_NAME),
- new InfraInterfaceController());
+ new InfraInterfaceController(),
+ persistentSettings,
+ NsdPublisher.newInstance(context, handler));
}
private static Inet6Address bytesToInet6Address(byte[] addressBytes) {
@@ -273,19 +288,26 @@
if (otDaemon == null) {
throw new RemoteException("Internal error: failed to start OT daemon");
}
- otDaemon.initialize(mTunIfController.getTunFd());
+ otDaemon.initialize(
+ mTunIfController.getTunFd(),
+ mPersistentSettings.get(ThreadPersistentSettings.THREAD_ENABLED),
+ mNsdPublisher);
otDaemon.registerStateCallback(mOtDaemonCallbackProxy, -1);
otDaemon.asBinder().linkToDeath(() -> mHandler.post(this::onOtDaemonDied), 0);
mOtDaemon = otDaemon;
return mOtDaemon;
}
- // TODO(b/309792480): restarts the OT daemon service
private void onOtDaemonDied() {
- Log.w(TAG, "OT daemon became dead, clean up...");
+ checkOnHandlerThread();
+ Log.w(TAG, "OT daemon is dead, clean up and restart it...");
+
OperationReceiverWrapper.onOtDaemonDied();
mOtDaemonCallbackProxy.onOtDaemonDied();
+ mTunIfController.onOtDaemonDied();
+ mNsdPublisher.onOtDaemonDied();
mOtDaemon = null;
+ initializeOtDaemon();
}
public void initialize() {
@@ -298,8 +320,6 @@
throw new IllegalStateException(
"Failed to create Thread tunnel interface", e);
}
- mLinkProperties.setInterfaceName(TUN_IF_NAME);
- mLinkProperties.setMtu(TunInterfaceController.MTU);
mConnectivityManager.registerNetworkProvider(mNetworkProvider);
requestUpstreamNetwork();
requestThreadNetwork();
@@ -308,6 +328,26 @@
});
}
+ public void setEnabled(@NonNull boolean isEnabled, @NonNull IOperationReceiver receiver) {
+ enforceAllPermissionsGranted(PERMISSION_THREAD_NETWORK_PRIVILEGED);
+
+ mHandler.post(() -> setEnabledInternal(isEnabled, new OperationReceiverWrapper(receiver)));
+ }
+
+ private void setEnabledInternal(
+ @NonNull boolean isEnabled, @Nullable OperationReceiverWrapper receiver) {
+ // The persistent setting keeps the desired enabled state, thus it's set regardless
+ // the otDaemon set enabled state operation succeeded or not, so that it can recover
+ // to the desired value after reboot.
+ mPersistentSettings.put(ThreadPersistentSettings.THREAD_ENABLED.key, isEnabled);
+ try {
+ getOtDaemon().setThreadEnabled(isEnabled, newOtStatusReceiver(receiver));
+ } catch (RemoteException e) {
+ Log.e(TAG, "otDaemon.setThreadEnabled failed", e);
+ receiver.onError(ERROR_INTERNAL_ERROR, "Thread stack error");
+ }
+ }
+
private void requestUpstreamNetwork() {
if (mUpstreamNetworkCallback != null) {
throw new AssertionError("The upstream network request is already there.");
@@ -330,25 +370,31 @@
@Override
public void onAvailable(@NonNull Network network) {
checkOnHandlerThread();
- Log.i(TAG, "onAvailable: " + network);
+ Log.i(TAG, "Upstream network available: " + network);
}
@Override
public void onLost(@NonNull Network network) {
checkOnHandlerThread();
- Log.i(TAG, "onLost: " + network);
+ Log.i(TAG, "Upstream network lost: " + network);
+
+ // TODO: disable border routing when upsteam network disconnected
}
@Override
public void onLinkPropertiesChanged(
@NonNull Network network, @NonNull LinkProperties linkProperties) {
checkOnHandlerThread();
- Log.i(
- TAG,
- String.format(
- "onLinkPropertiesChanged: {network: %s, interface: %s}",
- network, linkProperties.getInterfaceName()));
- mNetworkToInterface.put(network, linkProperties.getInterfaceName());
+
+ String existingIfName = mNetworkToInterface.get(network);
+ String newIfName = linkProperties.getInterfaceName();
+ if (Objects.equals(existingIfName, newIfName)) {
+ return;
+ }
+ Log.i(TAG, "Upstream network changed: " + existingIfName + " -> " + newIfName);
+ mNetworkToInterface.put(network, newIfName);
+
+ // TODO: disable border routing if netIfName is null
if (network.equals(mUpstreamNetwork)) {
enableBorderRouting(mNetworkToInterface.get(mUpstreamNetwork));
}
@@ -359,14 +405,20 @@
@Override
public void onAvailable(@NonNull Network network) {
checkOnHandlerThread();
- Log.i(TAG, "onAvailable: Thread network Available");
+ Log.i(TAG, "Thread network available: " + network);
}
@Override
public void onLocalNetworkInfoChanged(
@NonNull Network network, @NonNull LocalNetworkInfo localNetworkInfo) {
checkOnHandlerThread();
- Log.i(TAG, "onLocalNetworkInfoChanged: " + localNetworkInfo);
+ Log.i(
+ TAG,
+ "LocalNetworkInfo of Thread network changed: {threadNetwork: "
+ + network
+ + ", localNetworkInfo: "
+ + localNetworkInfo
+ + "}");
if (localNetworkInfo.getUpstreamNetwork() == null) {
mUpstreamNetwork = null;
return;
@@ -418,7 +470,7 @@
mHandler.getLooper(),
TAG,
netCaps,
- mLinkProperties,
+ mTunIfController.getLinkProperties(),
newLocalNetworkConfig(),
score,
new NetworkAgentConfig.Builder().build(),
@@ -449,46 +501,6 @@
mNetworkAgent = null;
}
- private void updateTunInterfaceAddress(LinkAddress linkAddress, boolean isAdded) {
- try {
- if (isAdded) {
- mTunIfController.addAddress(linkAddress);
- } else {
- mTunIfController.removeAddress(linkAddress);
- }
- } catch (IOException e) {
- Log.e(
- TAG,
- String.format(
- "Failed to %s Thread tun interface address %s",
- (isAdded ? "add" : "remove"), linkAddress),
- e);
- }
- }
-
- private void updateNetworkLinkProperties(LinkAddress linkAddress, boolean isAdded) {
- RouteInfo routeInfo =
- new RouteInfo(
- new IpPrefix(linkAddress.getAddress(), 64),
- null,
- TUN_IF_NAME,
- RouteInfo.RTN_UNICAST,
- TunInterfaceController.MTU);
- if (isAdded) {
- mLinkProperties.addLinkAddress(linkAddress);
- mLinkProperties.addRoute(routeInfo);
- } else {
- mLinkProperties.removeLinkAddress(linkAddress);
- mLinkProperties.removeRoute(routeInfo);
- }
-
- // The Thread daemon can send link property updates before the networkAgent is
- // registered
- if (mNetworkAgent != null) {
- mNetworkAgent.sendLinkProperties(mLinkProperties);
- }
- }
-
@Override
public int getThreadVersion() {
return THREAD_VERSION_1_3;
@@ -658,6 +670,8 @@
return ERROR_REJECTED_BY_PEER;
case OT_ERROR_UNSUPPORTED_CHANNEL:
return ERROR_UNSUPPORTED_CHANNEL;
+ case OT_ERROR_THREAD_DISABLED:
+ return ERROR_THREAD_DISABLED;
default:
return ERROR_INTERNAL_ERROR;
}
@@ -792,7 +806,7 @@
&& infraIfName.equals(mBorderRouterConfig.infraInterfaceName)) {
return;
}
- Log.i(TAG, "enableBorderRouting on AIL: " + infraIfName);
+ Log.i(TAG, "Enable border routing on AIL: " + infraIfName);
try {
mBorderRouterConfig.infraInterfaceName = infraIfName;
mBorderRouterConfig.infraInterfaceIcmp6Socket =
@@ -823,7 +837,7 @@
private void handleThreadInterfaceStateChanged(boolean isUp) {
try {
mTunIfController.setInterfaceUp(isUp);
- Log.d(TAG, "Thread network interface becomes " + (isUp ? "up" : "down"));
+ Log.i(TAG, "Thread TUN interface becomes " + (isUp ? "up" : "down"));
} catch (IOException e) {
Log.e(TAG, "Failed to handle Thread interface state changes", e);
}
@@ -831,13 +845,13 @@
private void handleDeviceRoleChanged(@DeviceRole int deviceRole) {
if (ThreadNetworkController.isAttached(deviceRole)) {
- Log.d(TAG, "Attached to the Thread network");
+ Log.i(TAG, "Attached to the Thread network");
// This is an idempotent method which can be called for multiple times when the device
// is already attached (e.g. going from Child to Router)
registerThreadNetwork();
} else {
- Log.d(TAG, "Detached from the Thread network");
+ Log.i(TAG, "Detached from the Thread network");
// This is an idempotent method which can be called for multiple times when the device
// is already detached or stopped
@@ -854,10 +868,17 @@
}
LinkAddress linkAddress = newLinkAddress(addressInfo);
- Log.d(TAG, (isAdded ? "Adding" : "Removing") + " address " + linkAddress);
+ if (isAdded) {
+ mTunIfController.addAddress(linkAddress);
+ } else {
+ mTunIfController.removeAddress(linkAddress);
+ }
- updateTunInterfaceAddress(linkAddress, isAdded);
- updateNetworkLinkProperties(linkAddress, isAdded);
+ // The OT daemon can send link property updates before the networkAgent is
+ // registered
+ if (mNetworkAgent != null) {
+ mNetworkAgent.sendLinkProperties(mTunIfController.getLinkProperties());
+ }
}
private boolean isMulticastForwardingEnabled() {
@@ -878,6 +899,9 @@
if (isMulticastForwardingEnabled() == isEnabled) {
return;
}
+
+ Log.i(TAG, "Multicast forwaring is " + (isEnabled ? "enabled" : "disabled"));
+
if (isEnabled) {
// When multicast forwarding is enabled, setup upstream forwarding to any address
// with minimal scope 4
@@ -893,10 +917,6 @@
mDownstreamMulticastRoutingConfig = CONFIG_FORWARD_NONE;
}
sendLocalNetworkConfig();
- Log.d(
- TAG,
- "Sent updated localNetworkConfig with multicast forwarding "
- + (isEnabled ? "enabled" : "disabled"));
}
private void handleMulticastForwardingAddressChanged(byte[] addressBytes, boolean isAdded) {
@@ -1001,6 +1021,15 @@
}
}
+ private void notifyThreadEnabledUpdated(IStateCallback callback, int enabledState) {
+ try {
+ callback.onThreadEnableStateChanged(enabledState);
+ Log.i(TAG, "onThreadEnableStateChanged " + enabledState);
+ } catch (RemoteException ignored) {
+ // do nothing if the client is dead
+ }
+ }
+
public void unregisterStateCallback(IStateCallback callback) {
checkOnHandlerThread();
if (!mStateCallbacks.containsKey(callback)) {
@@ -1065,6 +1094,31 @@
}
@Override
+ public void onThreadEnabledChanged(int state) {
+ mHandler.post(() -> onThreadEnabledChangedInternal(state));
+ }
+
+ private void onThreadEnabledChangedInternal(int state) {
+ checkOnHandlerThread();
+ for (IStateCallback callback : mStateCallbacks.keySet()) {
+ notifyThreadEnabledUpdated(callback, otStateToAndroidState(state));
+ }
+ }
+
+ private static int otStateToAndroidState(int state) {
+ switch (state) {
+ case OT_STATE_ENABLED:
+ return STATE_ENABLED;
+ case OT_STATE_DISABLED:
+ return STATE_DISABLED;
+ case OT_STATE_DISABLING:
+ return STATE_DISABLING;
+ default:
+ throw new IllegalArgumentException("Unknown ot state " + state);
+ }
+ }
+
+ @Override
public void onStateChanged(OtDaemonState newState, long listenerId) {
mHandler.post(() -> onStateChangedInternal(newState, listenerId));
}
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkCountryCode.java b/thread/service/java/com/android/server/thread/ThreadNetworkCountryCode.java
index b7b6233..ffa7b44 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkCountryCode.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkCountryCode.java
@@ -31,6 +31,7 @@
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.ActiveCountryCodeChangedCallback;
import android.os.Build;
+import android.sysprop.ThreadNetworkProperties;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
@@ -53,8 +54,8 @@
/**
* Provide functions for making changes to Thread Network country code. This Country Code is from
- * location, WiFi or telephony configuration. This class sends Country Code to Thread Network native
- * layer.
+ * location, WiFi, telephony or OEM configuration. This class sends Country Code to Thread Network
+ * native layer.
*
* <p>This class is thread-safe.
*/
@@ -77,6 +78,7 @@
value = {
COUNTRY_CODE_SOURCE_DEFAULT,
COUNTRY_CODE_SOURCE_LOCATION,
+ COUNTRY_CODE_SOURCE_OEM,
COUNTRY_CODE_SOURCE_OVERRIDE,
COUNTRY_CODE_SOURCE_TELEPHONY,
COUNTRY_CODE_SOURCE_TELEPHONY_LAST,
@@ -86,6 +88,7 @@
private static final String COUNTRY_CODE_SOURCE_DEFAULT = "Default";
private static final String COUNTRY_CODE_SOURCE_LOCATION = "Location";
+ private static final String COUNTRY_CODE_SOURCE_OEM = "Oem";
private static final String COUNTRY_CODE_SOURCE_OVERRIDE = "Override";
private static final String COUNTRY_CODE_SOURCE_TELEPHONY = "Telephony";
private static final String COUNTRY_CODE_SOURCE_TELEPHONY_LAST = "TelephonyLast";
@@ -111,6 +114,7 @@
@Nullable private CountryCodeInfo mWifiCountryCodeInfo;
@Nullable private CountryCodeInfo mTelephonyCountryCodeInfo;
@Nullable private CountryCodeInfo mTelephonyLastCountryCodeInfo;
+ @Nullable private CountryCodeInfo mOemCountryCodeInfo;
/** Container class to store Thread country code information. */
private static final class CountryCodeInfo {
@@ -118,13 +122,35 @@
@CountryCodeSource private String mSource;
private final Instant mUpdatedTimestamp;
+ /**
+ * Constructs a new {@code CountryCodeInfo} from the given country code, country code source
+ * and country coode created time.
+ *
+ * @param countryCode a String representation of the country code as defined in ISO 3166.
+ * @param countryCodeSource a String representation of country code source.
+ * @param instant a Instant representation of the time when the country code was created.
+ * @throws IllegalArgumentException if {@code countryCode} contains invalid country code.
+ */
public CountryCodeInfo(
String countryCode, @CountryCodeSource String countryCodeSource, Instant instant) {
+ if (!isValidCountryCode(countryCode)) {
+ throw new IllegalArgumentException("Country code is invalid: " + countryCode);
+ }
+
mCountryCode = countryCode;
mSource = countryCodeSource;
mUpdatedTimestamp = instant;
}
+ /**
+ * Constructs a new {@code CountryCodeInfo} from the given country code, country code
+ * source. The updated timestamp of the country code will be set to the time when {@code
+ * CountryCodeInfo} was constructed.
+ *
+ * @param countryCode a String representation of the country code as defined in ISO 3166.
+ * @param countryCodeSource a String representation of country code source.
+ * @throws IllegalArgumentException if {@code countryCode} contains invalid country code.
+ */
public CountryCodeInfo(String countryCode, @CountryCodeSource String countryCodeSource) {
this(countryCode, countryCodeSource, Instant.now());
}
@@ -188,7 +214,8 @@
WifiManager wifiManager,
Context context,
TelephonyManager telephonyManager,
- SubscriptionManager subscriptionManager) {
+ SubscriptionManager subscriptionManager,
+ @Nullable String oemCountryCode) {
mLocationManager = locationManager;
mThreadNetworkControllerService = threadNetworkControllerService;
mGeocoder = geocoder;
@@ -197,6 +224,10 @@
mContext = context;
mTelephonyManager = telephonyManager;
mSubscriptionManager = subscriptionManager;
+
+ if (oemCountryCode != null) {
+ mOemCountryCodeInfo = new CountryCodeInfo(oemCountryCode, COUNTRY_CODE_SOURCE_OEM);
+ }
}
public static ThreadNetworkCountryCode newInstance(
@@ -209,7 +240,8 @@
context.getSystemService(WifiManager.class),
context,
context.getSystemService(TelephonyManager.class),
- context.getSystemService(SubscriptionManager.class));
+ context.getSystemService(SubscriptionManager.class),
+ ThreadNetworkProperties.country_code().orElse(null));
}
/** Sets up this country code module to listen to location country code changes. */
@@ -278,7 +310,14 @@
public void onActiveCountryCodeChanged(String countryCode) {
Log.d(TAG, "Wifi country code is changed to " + countryCode);
synchronized ("ThreadNetworkCountryCode.this") {
- mWifiCountryCodeInfo = new CountryCodeInfo(countryCode, COUNTRY_CODE_SOURCE_WIFI);
+ if (isValidCountryCode(countryCode)) {
+ mWifiCountryCodeInfo =
+ new CountryCodeInfo(countryCode, COUNTRY_CODE_SOURCE_WIFI);
+ } else {
+ Log.w(TAG, "WiFi country code " + countryCode + " is invalid");
+ mWifiCountryCodeInfo = null;
+ }
+
updateCountryCode(false /* forceUpdate */);
}
}
@@ -418,6 +457,9 @@
* number will be used.
* <li>5. Location country code - Country code retrieved from LocationManager passive location
* provider.
+ * <li>6. OEM country code - Country code retrieved from the system property
+ * `threadnetwork.country_code`.
+ * <li>7. Default country code `WW`.
* </ul>
*
* @return the selected country code information.
@@ -443,6 +485,10 @@
return mLocationCountryCodeInfo;
}
+ if (mOemCountryCodeInfo != null) {
+ return mOemCountryCodeInfo;
+ }
+
return DEFAULT_COUNTRY_CODE_INFO;
}
@@ -531,13 +577,14 @@
/** Dumps the current state of this ThreadNetworkCountryCode object. */
public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("---- Dump of ThreadNetworkCountryCode begin ----");
- pw.println("mOverrideCountryCodeInfo: " + mOverrideCountryCodeInfo);
+ pw.println("mOverrideCountryCodeInfo : " + mOverrideCountryCodeInfo);
pw.println("mTelephonyCountryCodeSlotInfoMap: " + mTelephonyCountryCodeSlotInfoMap);
- pw.println("mTelephonyCountryCodeInfo: " + mTelephonyCountryCodeInfo);
- pw.println("mWifiCountryCodeInfo: " + mWifiCountryCodeInfo);
- pw.println("mTelephonyLastCountryCodeInfo: " + mTelephonyLastCountryCodeInfo);
- pw.println("mLocationCountryCodeInfo: " + mLocationCountryCodeInfo);
- pw.println("mCurrentCountryCodeInfo: " + mCurrentCountryCodeInfo);
+ pw.println("mTelephonyCountryCodeInfo : " + mTelephonyCountryCodeInfo);
+ pw.println("mWifiCountryCodeInfo : " + mWifiCountryCodeInfo);
+ pw.println("mTelephonyLastCountryCodeInfo : " + mTelephonyLastCountryCodeInfo);
+ pw.println("mLocationCountryCodeInfo : " + mLocationCountryCodeInfo);
+ pw.println("mOemCountryCodeInfo : " + mOemCountryCodeInfo);
+ pw.println("mCurrentCountryCodeInfo : " + mCurrentCountryCodeInfo);
pw.println("---- Dump of ThreadNetworkCountryCode end ------");
}
}
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkService.java b/thread/service/java/com/android/server/thread/ThreadNetworkService.java
index 53f2d4f..5cf27f7 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkService.java
@@ -18,16 +18,21 @@
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static com.android.net.module.util.DeviceConfigUtils.TETHERING_MODULE_NAME;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.ApexEnvironment;
import android.content.Context;
import android.net.thread.IThreadNetworkController;
import android.net.thread.IThreadNetworkManager;
import android.os.Binder;
import android.os.ParcelFileDescriptor;
+import android.util.AtomicFile;
import com.android.server.SystemService;
+import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Collections;
@@ -40,11 +45,18 @@
private final Context mContext;
@Nullable private ThreadNetworkCountryCode mCountryCode;
@Nullable private ThreadNetworkControllerService mControllerService;
+ private final ThreadPersistentSettings mPersistentSettings;
@Nullable private ThreadNetworkShellCommand mShellCommand;
/** Creates a new {@link ThreadNetworkService} object. */
public ThreadNetworkService(Context context) {
mContext = context;
+ mPersistentSettings =
+ new ThreadPersistentSettings(
+ new AtomicFile(
+ new File(
+ getOrCreateThreadnetworkDir(),
+ ThreadPersistentSettings.FILE_NAME)));
}
/**
@@ -54,7 +66,9 @@
*/
public void onBootPhase(int phase) {
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
- mControllerService = ThreadNetworkControllerService.newInstance(mContext);
+ mPersistentSettings.initialize();
+ mControllerService =
+ ThreadNetworkControllerService.newInstance(mContext, mPersistentSettings);
mControllerService.initialize();
} else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
// Country code initialization is delayed to the BOOT_COMPLETED phase because it will
@@ -109,4 +123,19 @@
pw.println();
}
+
+ /** Get device protected storage dir for the tethering apex. */
+ private static File getOrCreateThreadnetworkDir() {
+ final File threadnetworkDir;
+ final File apexDataDir =
+ ApexEnvironment.getApexEnvironment(TETHERING_MODULE_NAME)
+ .getDeviceProtectedDataDir();
+ threadnetworkDir = new File(apexDataDir, "thread");
+
+ if (threadnetworkDir.exists() || threadnetworkDir.mkdirs()) {
+ return threadnetworkDir;
+ }
+ throw new IllegalStateException(
+ "Cannot write into thread network data directory: " + threadnetworkDir);
+ }
}
diff --git a/thread/service/java/com/android/server/thread/TunInterfaceController.java b/thread/service/java/com/android/server/thread/TunInterfaceController.java
index 7223b2a..b29a54f 100644
--- a/thread/service/java/com/android/server/thread/TunInterfaceController.java
+++ b/thread/service/java/com/android/server/thread/TunInterfaceController.java
@@ -17,7 +17,10 @@
package com.android.server.thread;
import android.annotation.Nullable;
+import android.net.IpPrefix;
import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.RouteInfo;
import android.net.util.SocketUtils;
import android.os.ParcelFileDescriptor;
import android.os.SystemClock;
@@ -31,6 +34,7 @@
import java.io.FileDescriptor;
import java.io.IOException;
+import java.io.InterruptedIOException;
/** Controller for virtual/tunnel network interfaces. */
public class TunInterfaceController {
@@ -43,13 +47,21 @@
}
private final String mIfName;
+ private final LinkProperties mLinkProperties = new LinkProperties();
private ParcelFileDescriptor mParcelTunFd;
private FileDescriptor mNetlinkSocket;
private static int sNetlinkSeqNo = 0;
/** Creates a new {@link TunInterfaceController} instance for given interface. */
public TunInterfaceController(String interfaceName) {
- this.mIfName = interfaceName;
+ mIfName = interfaceName;
+ mLinkProperties.setInterfaceName(mIfName);
+ mLinkProperties.setMtu(MTU);
+ }
+
+ /** Returns link properties of the Thread TUN interface. */
+ public LinkProperties getLinkProperties() {
+ return mLinkProperties;
}
/**
@@ -87,13 +99,18 @@
/** Sets the interface up or down according to {@code isUp}. */
public void setInterfaceUp(boolean isUp) throws IOException {
+ if (!isUp) {
+ for (LinkAddress address : mLinkProperties.getAllLinkAddresses()) {
+ removeAddress(address);
+ }
+ }
nativeSetInterfaceUp(mIfName, isUp);
}
private native void nativeSetInterfaceUp(String interfaceName, boolean isUp) throws IOException;
/** Adds a new address to the interface. */
- public void addAddress(LinkAddress address) throws IOException {
+ public void addAddress(LinkAddress address) {
Log.d(TAG, "Adding address " + address + " with flags: " + address.getFlags());
long validLifetimeSeconds;
@@ -121,7 +138,7 @@
byte[] message =
RtNetlinkAddressMessage.newRtmNewAddressMessage(
- sNetlinkSeqNo,
+ sNetlinkSeqNo++,
address.getAddress(),
(short) address.getPrefixLength(),
address.getFlags(),
@@ -131,13 +148,51 @@
preferredLifetimeSeconds);
try {
Os.write(mNetlinkSocket, message, 0, message.length);
- } catch (ErrnoException e) {
- throw new IOException("Failed to send netlink message", e);
+ } catch (ErrnoException | InterruptedIOException e) {
+ Log.e(TAG, "Failed to add address " + address, e);
+ return;
}
+ mLinkProperties.addLinkAddress(address);
+ mLinkProperties.addRoute(getRouteForAddress(address));
}
/** Removes an address from the interface. */
- public void removeAddress(LinkAddress address) throws IOException {
- // TODO(b/263222068): remove address with netlink
+ public void removeAddress(LinkAddress address) {
+ Log.d(TAG, "Removing address " + address);
+ byte[] message =
+ RtNetlinkAddressMessage.newRtmDelAddressMessage(
+ sNetlinkSeqNo++,
+ address.getAddress(),
+ (short) address.getPrefixLength(),
+ Os.if_nametoindex(mIfName));
+
+ // Intentionally update the mLinkProperties before send netlink message because the
+ // address is already removed from ot-daemon and apps can't reach to the address even
+ // when the netlink request below fails
+ mLinkProperties.removeLinkAddress(address);
+ mLinkProperties.removeRoute(getRouteForAddress(address));
+ try {
+ Os.write(mNetlinkSocket, message, 0, message.length);
+ } catch (ErrnoException | InterruptedIOException e) {
+ Log.e(TAG, "Failed to remove address " + address, e);
+ }
+ }
+
+ private RouteInfo getRouteForAddress(LinkAddress linkAddress) {
+ return new RouteInfo(
+ new IpPrefix(linkAddress.getAddress(), linkAddress.getPrefixLength()),
+ null,
+ mIfName,
+ RouteInfo.RTN_UNICAST,
+ MTU);
+ }
+
+ /** Called by {@link ThreadNetworkControllerService} to do clean up when ot-daemon is dead. */
+ public void onOtDaemonDied() {
+ try {
+ setInterfaceUp(false);
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to set Thread TUN interface down");
+ }
}
}
diff --git a/thread/service/proguard.flags b/thread/service/proguard.flags
deleted file mode 100644
index 5028982..0000000
--- a/thread/service/proguard.flags
+++ /dev/null
@@ -1,4 +0,0 @@
-# Ensure the callback methods are not stripped
--keepclassmembers class **.ThreadNetworkControllerService$ThreadNetworkCallback {
- *;
-}
diff --git a/thread/tests/cts/Android.bp b/thread/tests/cts/Android.bp
index 3cf31e5..522120c 100644
--- a/thread/tests/cts/Android.bp
+++ b/thread/tests/cts/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_fwk_thread_network",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -45,9 +46,10 @@
libs: [
"android.test.base",
"android.test.runner",
- "framework-connectivity-module-api-stubs-including-flagged"
+ "framework-connectivity-module-api-stubs-including-flagged",
],
// Test coverage system runs on different devices. Need to
// compile for all architectures.
compile_multilib: "both",
+ platform_apis: true,
}
diff --git a/thread/tests/cts/AndroidManifest.xml b/thread/tests/cts/AndroidManifest.xml
index 4370fe3..1541bf5 100644
--- a/thread/tests/cts/AndroidManifest.xml
+++ b/thread/tests/cts/AndroidManifest.xml
@@ -19,6 +19,9 @@
xmlns:android="http://schemas.android.com/apk/res/android"
package="android.net.thread.cts">
+ <uses-permission android:name="android.permission.INTERNET"/>
+ <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
+
<application android:debuggable="true">
<uses-library android:name="android.test.runner" />
</application>
diff --git a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
index 7a6c9aa..3bec36b 100644
--- a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
+++ b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
@@ -16,30 +16,44 @@
package android.net.thread.cts;
+import static android.Manifest.permission.ACCESS_NETWORK_STATE;
+import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
+import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_CHILD;
+import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_LEADER;
+import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_ROUTER;
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_STOPPED;
+import static android.net.thread.ThreadNetworkController.STATE_DISABLED;
+import static android.net.thread.ThreadNetworkController.STATE_DISABLING;
+import static android.net.thread.ThreadNetworkController.STATE_ENABLED;
import static android.net.thread.ThreadNetworkController.THREAD_VERSION_1_3;
import static android.net.thread.ThreadNetworkException.ERROR_ABORTED;
import static android.net.thread.ThreadNetworkException.ERROR_FAILED_PRECONDITION;
import static android.net.thread.ThreadNetworkException.ERROR_REJECTED_BY_PEER;
+import static android.net.thread.ThreadNetworkException.ERROR_THREAD_DISABLED;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static com.android.testutils.TestNetworkTrackerKt.initTestNetwork;
import static com.android.testutils.TestPermissionUtil.runAsShell;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.fail;
import static org.junit.Assume.assumeNotNull;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
-import android.Manifest.permission;
import android.content.Context;
import android.net.ConnectivityManager;
+import android.net.LinkAddress;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
+import android.net.nsd.NsdManager;
+import android.net.nsd.NsdServiceInfo;
import android.net.thread.ActiveOperationalDataset;
import android.net.thread.OperationalDatasetTimestamp;
import android.net.thread.PendingOperationalDataset;
@@ -51,14 +65,16 @@
import android.os.Build;
import android.os.OutcomeReceiver;
+import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.LargeTest;
+import com.android.net.module.util.ArrayTrackRecord;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DevSdkIgnoreRunner;
-
-import com.google.common.util.concurrent.SettableFuture;
+import com.android.testutils.FunctionalUtils.ThrowingRunnable;
+import com.android.testutils.TestNetworkTracker;
import org.junit.After;
import org.junit.Before;
@@ -66,14 +82,22 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
+import java.util.Random;
import java.util.Set;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Predicate;
/** CTS tests for {@link ThreadNetworkController}. */
@LargeTest
@@ -81,52 +105,719 @@
@IgnoreUpTo(Build.VERSION_CODES.TIRAMISU) // Thread is available on only U+
public class ThreadNetworkControllerTest {
private static final int JOIN_TIMEOUT_MILLIS = 30 * 1000;
+ private static final int LEAVE_TIMEOUT_MILLIS = 2_000;
+ private static final int MIGRATION_TIMEOUT_MILLIS = 40 * 1_000;
private static final int NETWORK_CALLBACK_TIMEOUT_MILLIS = 10 * 1000;
- private static final int CALLBACK_TIMEOUT_MILLIS = 1000;
- private static final String PERMISSION_THREAD_NETWORK_PRIVILEGED =
+ private static final int CALLBACK_TIMEOUT_MILLIS = 1_000;
+ private static final int ENABLED_TIMEOUT_MILLIS = 2_000;
+ private static final int SERVICE_DISCOVERY_TIMEOUT_MILLIS = 10 * 1000;
+ private static final String MESHCOP_SERVICE_TYPE = "_meshcop._udp";
+ private static final String THREAD_NETWORK_PRIVILEGED =
"android.permission.THREAD_NETWORK_PRIVILEGED";
@Rule public DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
private final Context mContext = ApplicationProvider.getApplicationContext();
private ExecutorService mExecutor;
- private ThreadNetworkManager mManager;
+ private ThreadNetworkController mController;
+ private NsdManager mNsdManager;
private Set<String> mGrantedPermissions;
@Before
- public void setUp() {
- mExecutor = Executors.newSingleThreadExecutor();
- mManager = mContext.getSystemService(ThreadNetworkManager.class);
+ public void setUp() throws Exception {
+
mGrantedPermissions = new HashSet<String>();
+ mExecutor = Executors.newSingleThreadExecutor();
+ ThreadNetworkManager manager = mContext.getSystemService(ThreadNetworkManager.class);
+ if (manager != null) {
+ mController = manager.getAllThreadNetworkControllers().get(0);
+ }
// TODO: we will also need it in tearDown(), it's better to have a Rule to skip
// tests if a feature is not available.
- assumeNotNull(mManager);
+ assumeNotNull(mController);
+
+ setEnabledAndWait(mController, true);
+
+ mNsdManager = mContext.getSystemService(NsdManager.class);
}
@After
public void tearDown() throws Exception {
- if (mManager != null) {
- leaveAndWait();
+ if (mController != null) {
+ grantPermissions(THREAD_NETWORK_PRIVILEGED);
+ CompletableFuture<Void> future = new CompletableFuture<>();
+ mController.leave(mExecutor, future::complete);
+ future.get(LEAVE_TIMEOUT_MILLIS, MILLISECONDS);
+ }
+ dropAllPermissions();
+ }
+
+ @Test
+ public void getThreadVersion_returnsAtLeastThreadVersion1P3() {
+ assertThat(mController.getThreadVersion()).isAtLeast(THREAD_VERSION_1_3);
+ }
+
+ @Test
+ public void registerStateCallback_permissionsGranted_returnsCurrentStates() throws Exception {
+ CompletableFuture<Integer> deviceRole = new CompletableFuture<>();
+ StateCallback callback = deviceRole::complete;
+
+ try {
+ runAsShell(
+ ACCESS_NETWORK_STATE,
+ () -> mController.registerStateCallback(mExecutor, callback));
+
+ assertThat(deviceRole.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS))
+ .isEqualTo(DEVICE_ROLE_STOPPED);
+ } finally {
+ runAsShell(ACCESS_NETWORK_STATE, () -> mController.unregisterStateCallback(callback));
+ }
+ }
+
+ @Test
+ public void registerStateCallback_returnsUpdatedEnabledStates() throws Exception {
+ CompletableFuture<Void> setFuture1 = new CompletableFuture<>();
+ CompletableFuture<Void> setFuture2 = new CompletableFuture<>();
+ EnabledStateListener listener = new EnabledStateListener(mController);
+
+ try {
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () -> {
+ mController.setEnabled(false, mExecutor, newOutcomeReceiver(setFuture1));
+ });
+ setFuture1.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
+
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () -> {
+ mController.setEnabled(true, mExecutor, newOutcomeReceiver(setFuture2));
+ });
+ setFuture2.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
+
+ listener.expectThreadEnabledState(STATE_ENABLED);
+ listener.expectThreadEnabledState(STATE_DISABLING);
+ listener.expectThreadEnabledState(STATE_DISABLED);
+ listener.expectThreadEnabledState(STATE_ENABLED);
+ } finally {
+ listener.unregisterStateCallback();
+ }
+ }
+
+ @Test
+ public void registerStateCallback_noPermissions_throwsSecurityException() throws Exception {
+ dropAllPermissions();
+
+ assertThrows(
+ SecurityException.class,
+ () -> mController.registerStateCallback(mExecutor, role -> {}));
+ }
+
+ @Test
+ public void registerStateCallback_alreadyRegistered_throwsIllegalArgumentException()
+ throws Exception {
+ grantPermissions(ACCESS_NETWORK_STATE);
+ CompletableFuture<Integer> deviceRole = new CompletableFuture<>();
+ StateCallback callback = role -> deviceRole.complete(role);
+
+ mController.registerStateCallback(mExecutor, callback);
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mController.registerStateCallback(mExecutor, callback));
+ }
+
+ @Test
+ public void unregisterStateCallback_noPermissions_throwsSecurityException() throws Exception {
+ CompletableFuture<Integer> deviceRole = new CompletableFuture<>();
+ StateCallback callback = role -> deviceRole.complete(role);
+ runAsShell(
+ ACCESS_NETWORK_STATE, () -> mController.registerStateCallback(mExecutor, callback));
+
+ try {
dropAllPermissions();
+ assertThrows(
+ SecurityException.class, () -> mController.unregisterStateCallback(callback));
+ } finally {
+ runAsShell(ACCESS_NETWORK_STATE, () -> mController.unregisterStateCallback(callback));
}
}
- private List<ThreadNetworkController> getAllControllers() {
- return mManager.getAllThreadNetworkControllers();
+ @Test
+ public void unregisterStateCallback_callbackRegistered_success() throws Exception {
+ grantPermissions(ACCESS_NETWORK_STATE);
+ CompletableFuture<Integer> deviceRole = new CompletableFuture<>();
+ StateCallback callback = role -> deviceRole.complete(role);
+
+ assertDoesNotThrow(() -> mController.registerStateCallback(mExecutor, callback));
+ mController.unregisterStateCallback(callback);
}
- private void leaveAndWait() throws Exception {
- grantPermissions(PERMISSION_THREAD_NETWORK_PRIVILEGED);
+ @Test
+ public void unregisterStateCallback_callbackNotRegistered_throwsIllegalArgumentException()
+ throws Exception {
+ CompletableFuture<Integer> deviceRole = new CompletableFuture<>();
+ StateCallback callback = role -> deviceRole.complete(role);
- for (ThreadNetworkController controller : getAllControllers()) {
- SettableFuture<Void> future = SettableFuture.create();
- controller.leave(mExecutor, future::set);
- future.get();
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mController.unregisterStateCallback(callback));
+ }
+
+ @Test
+ public void unregisterStateCallback_alreadyUnregistered_throwsIllegalArgumentException()
+ throws Exception {
+ grantPermissions(ACCESS_NETWORK_STATE);
+ CompletableFuture<Integer> deviceRole = new CompletableFuture<>();
+ StateCallback callback = deviceRole::complete;
+ mController.registerStateCallback(mExecutor, callback);
+ mController.unregisterStateCallback(callback);
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mController.unregisterStateCallback(callback));
+ }
+
+ @Test
+ public void registerOperationalDatasetCallback_permissionsGranted_returnsCurrentStates()
+ throws Exception {
+ grantPermissions(ACCESS_NETWORK_STATE, THREAD_NETWORK_PRIVILEGED);
+ CompletableFuture<ActiveOperationalDataset> activeFuture = new CompletableFuture<>();
+ CompletableFuture<PendingOperationalDataset> pendingFuture = new CompletableFuture<>();
+ var callback = newDatasetCallback(activeFuture, pendingFuture);
+
+ try {
+ mController.registerOperationalDatasetCallback(mExecutor, callback);
+
+ assertThat(activeFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isNull();
+ assertThat(pendingFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isNull();
+ } finally {
+ mController.unregisterOperationalDatasetCallback(callback);
}
}
+ @Test
+ public void registerOperationalDatasetCallback_noPermissions_throwsSecurityException()
+ throws Exception {
+ dropAllPermissions();
+ CompletableFuture<ActiveOperationalDataset> activeFuture = new CompletableFuture<>();
+ CompletableFuture<PendingOperationalDataset> pendingFuture = new CompletableFuture<>();
+ var callback = newDatasetCallback(activeFuture, pendingFuture);
+
+ assertThrows(
+ SecurityException.class,
+ () -> mController.registerOperationalDatasetCallback(mExecutor, callback));
+ }
+
+ @Test
+ public void unregisterOperationalDatasetCallback_callbackRegistered_success() throws Exception {
+ grantPermissions(ACCESS_NETWORK_STATE, THREAD_NETWORK_PRIVILEGED);
+ CompletableFuture<ActiveOperationalDataset> activeFuture = new CompletableFuture<>();
+ CompletableFuture<PendingOperationalDataset> pendingFuture = new CompletableFuture<>();
+ var callback = newDatasetCallback(activeFuture, pendingFuture);
+ mController.registerOperationalDatasetCallback(mExecutor, callback);
+
+ assertDoesNotThrow(() -> mController.unregisterOperationalDatasetCallback(callback));
+ }
+
+ @Test
+ public void unregisterOperationalDatasetCallback_noPermissions_throwsSecurityException()
+ throws Exception {
+ CompletableFuture<ActiveOperationalDataset> activeFuture = new CompletableFuture<>();
+ CompletableFuture<PendingOperationalDataset> pendingFuture = new CompletableFuture<>();
+ var callback = newDatasetCallback(activeFuture, pendingFuture);
+ runAsShell(
+ ACCESS_NETWORK_STATE,
+ THREAD_NETWORK_PRIVILEGED,
+ () -> mController.registerOperationalDatasetCallback(mExecutor, callback));
+
+ try {
+ dropAllPermissions();
+ assertThrows(
+ SecurityException.class,
+ () -> mController.unregisterOperationalDatasetCallback(callback));
+ } finally {
+ runAsShell(
+ ACCESS_NETWORK_STATE,
+ THREAD_NETWORK_PRIVILEGED,
+ () -> mController.unregisterOperationalDatasetCallback(callback));
+ }
+ }
+
+ private static <V> OutcomeReceiver<V, ThreadNetworkException> newOutcomeReceiver(
+ CompletableFuture<V> future) {
+ return new OutcomeReceiver<V, ThreadNetworkException>() {
+ @Override
+ public void onResult(V result) {
+ future.complete(result);
+ }
+
+ @Override
+ public void onError(ThreadNetworkException e) {
+ future.completeExceptionally(e);
+ }
+ };
+ }
+
+ @Test
+ public void join_withPrivilegedPermission_success() throws Exception {
+ ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", mController);
+ CompletableFuture<Void> joinFuture = new CompletableFuture<>();
+
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () -> mController.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture)));
+ joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
+
+ assertThat(isAttached(mController)).isTrue();
+ assertThat(getActiveOperationalDataset(mController)).isEqualTo(activeDataset);
+ }
+
+ @Test
+ public void join_withoutPrivilegedPermission_throwsSecurityException() throws Exception {
+ dropAllPermissions();
+ ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", mController);
+
+ assertThrows(
+ SecurityException.class, () -> mController.join(activeDataset, mExecutor, v -> {}));
+ }
+
+ @Test
+ public void join_threadDisabled_failsWithErrorThreadDisabled() throws Exception {
+ setEnabledAndWait(mController, false);
+ ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", mController);
+ CompletableFuture<Void> joinFuture = new CompletableFuture<>();
+
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () -> mController.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture)));
+
+ var thrown =
+ assertThrows(
+ ExecutionException.class,
+ () -> joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS));
+ var threadException = (ThreadNetworkException) thrown.getCause();
+ assertThat(threadException.getErrorCode()).isEqualTo(ERROR_THREAD_DISABLED);
+ }
+
+ @Test
+ public void join_concurrentRequests_firstOneIsAborted() throws Exception {
+ final byte[] KEY_1 = new byte[] {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
+ final byte[] KEY_2 = new byte[] {2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
+ ActiveOperationalDataset activeDataset1 =
+ new ActiveOperationalDataset.Builder(newRandomizedDataset("TestNet", mController))
+ .setNetworkKey(KEY_1)
+ .build();
+ ActiveOperationalDataset activeDataset2 =
+ new ActiveOperationalDataset.Builder(activeDataset1).setNetworkKey(KEY_2).build();
+ CompletableFuture<Void> joinFuture1 = new CompletableFuture<>();
+ CompletableFuture<Void> joinFuture2 = new CompletableFuture<>();
+
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () -> {
+ mController.join(activeDataset1, mExecutor, newOutcomeReceiver(joinFuture1));
+ mController.join(activeDataset2, mExecutor, newOutcomeReceiver(joinFuture2));
+ });
+
+ var thrown =
+ assertThrows(
+ ExecutionException.class,
+ () -> joinFuture1.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS));
+ var threadException = (ThreadNetworkException) thrown.getCause();
+ assertThat(threadException.getErrorCode()).isEqualTo(ERROR_ABORTED);
+ joinFuture2.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
+ assertThat(isAttached(mController)).isTrue();
+ assertThat(getActiveOperationalDataset(mController)).isEqualTo(activeDataset2);
+ }
+
+ @Test
+ public void leave_withPrivilegedPermission_success() throws Exception {
+ CompletableFuture<Void> leaveFuture = new CompletableFuture<>();
+ joinRandomizedDatasetAndWait(mController);
+
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () -> mController.leave(mExecutor, newOutcomeReceiver(leaveFuture)));
+ leaveFuture.get(LEAVE_TIMEOUT_MILLIS, MILLISECONDS);
+
+ assertThat(getDeviceRole(mController)).isEqualTo(DEVICE_ROLE_STOPPED);
+ }
+
+ @Test
+ public void leave_withoutPrivilegedPermission_throwsSecurityException() {
+ dropAllPermissions();
+
+ assertThrows(SecurityException.class, () -> mController.leave(mExecutor, v -> {}));
+ }
+
+ @Test
+ public void leave_threadDisabled_success() throws Exception {
+ setEnabledAndWait(mController, false);
+ CompletableFuture<Void> leaveFuture = new CompletableFuture<>();
+
+ leave(mController, newOutcomeReceiver(leaveFuture));
+ leaveFuture.get(LEAVE_TIMEOUT_MILLIS, MILLISECONDS);
+
+ assertThat(getDeviceRole(mController)).isEqualTo(DEVICE_ROLE_STOPPED);
+ }
+
+ @Test
+ public void leave_concurrentRequests_bothSuccess() throws Exception {
+ CompletableFuture<Void> leaveFuture1 = new CompletableFuture<>();
+ CompletableFuture<Void> leaveFuture2 = new CompletableFuture<>();
+ joinRandomizedDatasetAndWait(mController);
+
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () -> {
+ mController.leave(mExecutor, newOutcomeReceiver(leaveFuture1));
+ mController.leave(mExecutor, newOutcomeReceiver(leaveFuture2));
+ });
+
+ leaveFuture1.get(LEAVE_TIMEOUT_MILLIS, MILLISECONDS);
+ leaveFuture2.get(LEAVE_TIMEOUT_MILLIS, MILLISECONDS);
+ assertThat(getDeviceRole(mController)).isEqualTo(DEVICE_ROLE_STOPPED);
+ }
+
+ @Test
+ public void scheduleMigration_withPrivilegedPermission_newDatasetApplied() throws Exception {
+ grantPermissions(ACCESS_NETWORK_STATE, THREAD_NETWORK_PRIVILEGED);
+ ActiveOperationalDataset activeDataset1 =
+ new ActiveOperationalDataset.Builder(newRandomizedDataset("TestNet", mController))
+ .setActiveTimestamp(new OperationalDatasetTimestamp(1L, 0, false))
+ .setExtendedPanId(new byte[] {1, 1, 1, 1, 1, 1, 1, 1})
+ .build();
+ ActiveOperationalDataset activeDataset2 =
+ new ActiveOperationalDataset.Builder(activeDataset1)
+ .setActiveTimestamp(new OperationalDatasetTimestamp(2L, 0, false))
+ .setNetworkName("ThreadNet2")
+ .build();
+ PendingOperationalDataset pendingDataset2 =
+ new PendingOperationalDataset(
+ activeDataset2,
+ OperationalDatasetTimestamp.fromInstant(Instant.now()),
+ Duration.ofSeconds(30));
+ CompletableFuture<Void> joinFuture = new CompletableFuture<>();
+ CompletableFuture<Void> migrateFuture = new CompletableFuture<>();
+ mController.join(activeDataset1, mExecutor, newOutcomeReceiver(joinFuture));
+ joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
+
+ mController.scheduleMigration(
+ pendingDataset2, mExecutor, newOutcomeReceiver(migrateFuture));
+ migrateFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
+
+ CompletableFuture<Boolean> dataset2IsApplied = new CompletableFuture<>();
+ CompletableFuture<Boolean> pendingDatasetIsRemoved = new CompletableFuture<>();
+ OperationalDatasetCallback datasetCallback =
+ new OperationalDatasetCallback() {
+ @Override
+ public void onActiveOperationalDatasetChanged(
+ ActiveOperationalDataset activeDataset) {
+ if (activeDataset.equals(activeDataset2)) {
+ dataset2IsApplied.complete(true);
+ }
+ }
+
+ @Override
+ public void onPendingOperationalDatasetChanged(
+ PendingOperationalDataset pendingDataset) {
+ if (pendingDataset == null) {
+ pendingDatasetIsRemoved.complete(true);
+ }
+ }
+ };
+ mController.registerOperationalDatasetCallback(directExecutor(), datasetCallback);
+ try {
+ assertThat(dataset2IsApplied.get(MIGRATION_TIMEOUT_MILLIS, MILLISECONDS)).isTrue();
+ assertThat(pendingDatasetIsRemoved.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isTrue();
+ } finally {
+ mController.unregisterOperationalDatasetCallback(datasetCallback);
+ }
+ }
+
+ @Test
+ public void scheduleMigration_whenNotAttached_failWithPreconditionError() throws Exception {
+ grantPermissions(ACCESS_NETWORK_STATE, THREAD_NETWORK_PRIVILEGED);
+ PendingOperationalDataset pendingDataset =
+ new PendingOperationalDataset(
+ newRandomizedDataset("TestNet", mController),
+ OperationalDatasetTimestamp.fromInstant(Instant.now()),
+ Duration.ofSeconds(30));
+ CompletableFuture<Void> migrateFuture = new CompletableFuture<>();
+
+ mController.scheduleMigration(pendingDataset, mExecutor, newOutcomeReceiver(migrateFuture));
+
+ ThreadNetworkException thrown =
+ (ThreadNetworkException)
+ assertThrows(ExecutionException.class, migrateFuture::get).getCause();
+ assertThat(thrown.getErrorCode()).isEqualTo(ERROR_FAILED_PRECONDITION);
+ }
+
+ @Test
+ public void scheduleMigration_secondRequestHasSmallerTimestamp_rejectedByLeader()
+ throws Exception {
+ grantPermissions(ACCESS_NETWORK_STATE, THREAD_NETWORK_PRIVILEGED);
+ final ActiveOperationalDataset activeDataset =
+ new ActiveOperationalDataset.Builder(newRandomizedDataset("testNet", mController))
+ .setActiveTimestamp(new OperationalDatasetTimestamp(1L, 0, false))
+ .build();
+ ActiveOperationalDataset activeDataset1 =
+ new ActiveOperationalDataset.Builder(activeDataset)
+ .setActiveTimestamp(new OperationalDatasetTimestamp(2L, 0, false))
+ .setNetworkName("testNet1")
+ .build();
+ PendingOperationalDataset pendingDataset1 =
+ new PendingOperationalDataset(
+ activeDataset1,
+ new OperationalDatasetTimestamp(100, 0, false),
+ Duration.ofSeconds(30));
+ ActiveOperationalDataset activeDataset2 =
+ new ActiveOperationalDataset.Builder(activeDataset)
+ .setActiveTimestamp(new OperationalDatasetTimestamp(3L, 0, false))
+ .setNetworkName("testNet2")
+ .build();
+ PendingOperationalDataset pendingDataset2 =
+ new PendingOperationalDataset(
+ activeDataset2,
+ new OperationalDatasetTimestamp(20, 0, false),
+ Duration.ofSeconds(30));
+ CompletableFuture<Void> joinFuture = new CompletableFuture<>();
+ CompletableFuture<Void> migrateFuture1 = new CompletableFuture<>();
+ CompletableFuture<Void> migrateFuture2 = new CompletableFuture<>();
+ mController.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture));
+ joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
+
+ mController.scheduleMigration(
+ pendingDataset1, mExecutor, newOutcomeReceiver(migrateFuture1));
+ migrateFuture1.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
+ mController.scheduleMigration(
+ pendingDataset2, mExecutor, newOutcomeReceiver(migrateFuture2));
+
+ ThreadNetworkException thrown =
+ (ThreadNetworkException)
+ assertThrows(ExecutionException.class, migrateFuture2::get).getCause();
+ assertThat(thrown.getErrorCode()).isEqualTo(ERROR_REJECTED_BY_PEER);
+ }
+
+ @Test
+ public void scheduleMigration_secondRequestHasLargerTimestamp_newDatasetApplied()
+ throws Exception {
+ grantPermissions(ACCESS_NETWORK_STATE, THREAD_NETWORK_PRIVILEGED);
+ final ActiveOperationalDataset activeDataset =
+ new ActiveOperationalDataset.Builder(newRandomizedDataset("validName", mController))
+ .setActiveTimestamp(new OperationalDatasetTimestamp(1L, 0, false))
+ .build();
+ ActiveOperationalDataset activeDataset1 =
+ new ActiveOperationalDataset.Builder(activeDataset)
+ .setActiveTimestamp(new OperationalDatasetTimestamp(2L, 0, false))
+ .setNetworkName("testNet1")
+ .build();
+ PendingOperationalDataset pendingDataset1 =
+ new PendingOperationalDataset(
+ activeDataset1,
+ new OperationalDatasetTimestamp(100, 0, false),
+ Duration.ofSeconds(30));
+ ActiveOperationalDataset activeDataset2 =
+ new ActiveOperationalDataset.Builder(activeDataset)
+ .setActiveTimestamp(new OperationalDatasetTimestamp(3L, 0, false))
+ .setNetworkName("testNet2")
+ .build();
+ PendingOperationalDataset pendingDataset2 =
+ new PendingOperationalDataset(
+ activeDataset2,
+ new OperationalDatasetTimestamp(200, 0, false),
+ Duration.ofSeconds(30));
+ CompletableFuture<Void> joinFuture = new CompletableFuture<>();
+ CompletableFuture<Void> migrateFuture1 = new CompletableFuture<>();
+ CompletableFuture<Void> migrateFuture2 = new CompletableFuture<>();
+ mController.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture));
+ joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
+
+ mController.scheduleMigration(
+ pendingDataset1, mExecutor, newOutcomeReceiver(migrateFuture1));
+ migrateFuture1.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
+ mController.scheduleMigration(
+ pendingDataset2, mExecutor, newOutcomeReceiver(migrateFuture2));
+ migrateFuture2.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
+
+ CompletableFuture<Boolean> dataset2IsApplied = new CompletableFuture<>();
+ CompletableFuture<Boolean> pendingDatasetIsRemoved = new CompletableFuture<>();
+ OperationalDatasetCallback datasetCallback =
+ new OperationalDatasetCallback() {
+ @Override
+ public void onActiveOperationalDatasetChanged(
+ ActiveOperationalDataset activeDataset) {
+ if (activeDataset.equals(activeDataset2)) {
+ dataset2IsApplied.complete(true);
+ }
+ }
+
+ @Override
+ public void onPendingOperationalDatasetChanged(
+ PendingOperationalDataset pendingDataset) {
+ if (pendingDataset == null) {
+ pendingDatasetIsRemoved.complete(true);
+ }
+ }
+ };
+ mController.registerOperationalDatasetCallback(directExecutor(), datasetCallback);
+ try {
+ assertThat(dataset2IsApplied.get(MIGRATION_TIMEOUT_MILLIS, MILLISECONDS)).isTrue();
+ assertThat(pendingDatasetIsRemoved.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isTrue();
+ } finally {
+ mController.unregisterOperationalDatasetCallback(datasetCallback);
+ }
+ }
+
+ @Test
+ public void scheduleMigration_threadDisabled_failsWithErrorThreadDisabled() throws Exception {
+ ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", mController);
+ PendingOperationalDataset pendingDataset =
+ new PendingOperationalDataset(
+ activeDataset,
+ OperationalDatasetTimestamp.fromInstant(Instant.now()),
+ Duration.ofSeconds(30));
+ joinRandomizedDatasetAndWait(mController);
+ CompletableFuture<Void> migrationFuture = new CompletableFuture<>();
+
+ setEnabledAndWait(mController, false);
+
+ scheduleMigration(mController, pendingDataset, newOutcomeReceiver(migrationFuture));
+
+ ThreadNetworkException thrown =
+ (ThreadNetworkException)
+ assertThrows(ExecutionException.class, migrationFuture::get).getCause();
+ assertThat(thrown.getErrorCode()).isEqualTo(ERROR_THREAD_DISABLED);
+ }
+
+ @Test
+ public void createRandomizedDataset_wrongNetworkNameLength_throwsIllegalArgumentException() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mController.createRandomizedDataset("", mExecutor, dataset -> {}));
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ mController.createRandomizedDataset(
+ "ANetNameIs17Bytes", mExecutor, dataset -> {}));
+ }
+
+ @Test
+ public void createRandomizedDataset_validNetworkName_success() throws Exception {
+ ActiveOperationalDataset dataset = newRandomizedDataset("validName", mController);
+
+ assertThat(dataset.getNetworkName()).isEqualTo("validName");
+ assertThat(dataset.getPanId()).isLessThan(0xffff);
+ assertThat(dataset.getChannelMask().size()).isAtLeast(1);
+ assertThat(dataset.getExtendedPanId()).hasLength(8);
+ assertThat(dataset.getNetworkKey()).hasLength(16);
+ assertThat(dataset.getPskc()).hasLength(16);
+ assertThat(dataset.getMeshLocalPrefix().getPrefixLength()).isEqualTo(64);
+ assertThat(dataset.getMeshLocalPrefix().getRawAddress()[0]).isEqualTo((byte) 0xfd);
+ }
+
+ @Test
+ public void setEnabled_permissionsGranted_succeeds() throws Exception {
+ CompletableFuture<Void> setFuture1 = new CompletableFuture<>();
+ CompletableFuture<Void> setFuture2 = new CompletableFuture<>();
+
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () -> mController.setEnabled(false, mExecutor, newOutcomeReceiver(setFuture1)));
+ setFuture1.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
+ waitForEnabledState(mController, booleanToEnabledState(false));
+
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () -> mController.setEnabled(true, mExecutor, newOutcomeReceiver(setFuture2)));
+ setFuture2.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
+ waitForEnabledState(mController, booleanToEnabledState(true));
+ }
+
+ @Test
+ public void setEnabled_noPermissions_throwsSecurityException() throws Exception {
+ CompletableFuture<Void> setFuture = new CompletableFuture<>();
+ assertThrows(
+ SecurityException.class,
+ () -> mController.setEnabled(false, mExecutor, newOutcomeReceiver(setFuture)));
+ }
+
+ @Test
+ public void setEnabled_disable_leavesThreadNetwork() throws Exception {
+ joinRandomizedDatasetAndWait(mController);
+ setEnabledAndWait(mController, false);
+ assertThat(getDeviceRole(mController)).isEqualTo(DEVICE_ROLE_STOPPED);
+ }
+
+ @Test
+ public void setEnabled_toggleAfterJoin_joinsThreadNetworkAgain() throws Exception {
+ joinRandomizedDatasetAndWait(mController);
+
+ setEnabledAndWait(mController, false);
+ assertThat(getDeviceRole(mController)).isEqualTo(DEVICE_ROLE_STOPPED);
+ setEnabledAndWait(mController, true);
+
+ runAsShell(ACCESS_NETWORK_STATE, () -> waitForAttachedState(mController));
+ }
+
+ @Test
+ public void setEnabled_enableFollowedByDisable_allSucceed() throws Exception {
+ joinRandomizedDatasetAndWait(mController);
+ CompletableFuture<Void> setFuture1 = new CompletableFuture<>();
+ CompletableFuture<Void> setFuture2 = new CompletableFuture<>();
+ EnabledStateListener listener = new EnabledStateListener(mController);
+ listener.expectThreadEnabledState(STATE_ENABLED);
+
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () -> {
+ mController.setEnabled(true, mExecutor, newOutcomeReceiver(setFuture1));
+ mController.setEnabled(false, mExecutor, newOutcomeReceiver(setFuture2));
+ });
+ setFuture1.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
+ setFuture2.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
+
+ listener.expectThreadEnabledState(STATE_DISABLING);
+ listener.expectThreadEnabledState(STATE_DISABLED);
+ assertThat(getDeviceRole(mController)).isEqualTo(DEVICE_ROLE_STOPPED);
+ // FIXME: this is not called when a exception is thrown after the creation of `listener`
+ listener.unregisterStateCallback();
+ }
+
+ // TODO (b/322437869): add test case to verify when Thread is in DISABLING state, any commands
+ // (join/leave/scheduleMigration/setEnabled) fail with ERROR_BUSY. This is not currently tested
+ // because DISABLING has very short lifecycle, it's not possible to guarantee the command can be
+ // sent before state changes to DISABLED.
+
+ @Test
+ public void threadNetworkCallback_deviceAttached_threadNetworkIsAvailable() throws Exception {
+ CompletableFuture<Network> networkFuture = new CompletableFuture<>();
+ ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
+ NetworkRequest networkRequest =
+ new NetworkRequest.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_THREAD)
+ .build();
+ ConnectivityManager.NetworkCallback networkCallback =
+ new ConnectivityManager.NetworkCallback() {
+ @Override
+ public void onAvailable(Network network) {
+ networkFuture.complete(network);
+ }
+ };
+
+ joinRandomizedDatasetAndWait(mController);
+ runAsShell(
+ ACCESS_NETWORK_STATE,
+ () -> cm.registerNetworkCallback(networkRequest, networkCallback));
+
+ assertThat(isAttached(mController)).isTrue();
+ assertThat(networkFuture.get(NETWORK_CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isNotNull();
+ }
+
private void grantPermissions(String... permissions) {
for (String permission : permissions) {
mGrantedPermissions.add(permission);
@@ -136,14 +827,82 @@
getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(allPermissions);
}
+ @Test
+ public void meshcopService_threadEnabledButNotJoined_discoveredButNoNetwork() throws Exception {
+ TestNetworkTracker testNetwork = setUpTestNetwork();
+
+ setEnabledAndWait(mController, true);
+ leaveAndWait(mController);
+
+ NsdServiceInfo serviceInfo =
+ expectServiceResolved(
+ MESHCOP_SERVICE_TYPE,
+ SERVICE_DISCOVERY_TIMEOUT_MILLIS,
+ s -> s.getAttributes().get("at") == null);
+
+ Map<String, byte[]> txtMap = serviceInfo.getAttributes();
+
+ assertThat(txtMap.get("rv")).isNotNull();
+ assertThat(txtMap.get("tv")).isNotNull();
+ assertThat(txtMap.get("sb")).isNotNull();
+
+ tearDownTestNetwork(testNetwork);
+ }
+
+ @Test
+ public void meshcopService_joinedNetwork_discoveredHasNetwork() throws Exception {
+ TestNetworkTracker testNetwork = setUpTestNetwork();
+
+ String networkName = "TestNet" + new Random().nextInt(10_000);
+ joinRandomizedDatasetAndWait(mController, networkName);
+
+ Predicate<NsdServiceInfo> predicate =
+ serviceInfo ->
+ serviceInfo.getAttributes().get("at") != null
+ && Arrays.equals(
+ serviceInfo.getAttributes().get("nn"),
+ networkName.getBytes(StandardCharsets.UTF_8));
+
+ NsdServiceInfo resolvedService =
+ expectServiceResolved(
+ MESHCOP_SERVICE_TYPE, SERVICE_DISCOVERY_TIMEOUT_MILLIS, predicate);
+
+ Map<String, byte[]> txtMap = resolvedService.getAttributes();
+ assertThat(txtMap.get("rv")).isNotNull();
+ assertThat(txtMap.get("tv")).isNotNull();
+ assertThat(txtMap.get("sb")).isNotNull();
+ assertThat(txtMap.get("id").length).isEqualTo(16);
+
+ tearDownTestNetwork(testNetwork);
+ }
+
+ @Test
+ public void meshcopService_threadDisabled_notDiscovered() throws Exception {
+ TestNetworkTracker testNetwork = setUpTestNetwork();
+
+ CompletableFuture<NsdServiceInfo> serviceLostFuture = new CompletableFuture<>();
+ NsdManager.DiscoveryListener listener =
+ discoverForServiceLost(MESHCOP_SERVICE_TYPE, serviceLostFuture);
+ setEnabledAndWait(mController, false);
+
+ try {
+ serviceLostFuture.get(10_000, MILLISECONDS);
+ } finally {
+ mNsdManager.stopServiceDiscovery(listener);
+ }
+ assertThrows(TimeoutException.class, () -> discoverService(MESHCOP_SERVICE_TYPE));
+
+ tearDownTestNetwork(testNetwork);
+ }
+
private static void dropAllPermissions() {
getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
}
private static ActiveOperationalDataset newRandomizedDataset(
String networkName, ThreadNetworkController controller) throws Exception {
- SettableFuture<ActiveOperationalDataset> future = SettableFuture.create();
- controller.createRandomizedDataset(networkName, directExecutor(), future::set);
+ CompletableFuture<ActiveOperationalDataset> future = new CompletableFuture<>();
+ controller.createRandomizedDataset(networkName, directExecutor(), future::complete);
return future.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
}
@@ -152,642 +911,299 @@
}
private static int getDeviceRole(ThreadNetworkController controller) throws Exception {
- SettableFuture<Integer> future = SettableFuture.create();
- StateCallback callback = future::set;
- controller.registerStateCallback(directExecutor(), callback);
- int role = future.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
- controller.unregisterStateCallback(callback);
- return role;
+ CompletableFuture<Integer> future = new CompletableFuture<>();
+ StateCallback callback = future::complete;
+ runAsShell(
+ ACCESS_NETWORK_STATE,
+ () -> controller.registerStateCallback(directExecutor(), callback));
+ try {
+ return future.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
+ } finally {
+ runAsShell(ACCESS_NETWORK_STATE, () -> controller.unregisterStateCallback(callback));
+ }
+ }
+
+ private static int waitForAttachedState(ThreadNetworkController controller) throws Exception {
+ List<Integer> attachedRoles = new ArrayList<>();
+ attachedRoles.add(DEVICE_ROLE_CHILD);
+ attachedRoles.add(DEVICE_ROLE_ROUTER);
+ attachedRoles.add(DEVICE_ROLE_LEADER);
+ return waitForStateAnyOf(controller, attachedRoles);
}
private static int waitForStateAnyOf(
ThreadNetworkController controller, List<Integer> deviceRoles) throws Exception {
- SettableFuture<Integer> future = SettableFuture.create();
+ CompletableFuture<Integer> future = new CompletableFuture<>();
StateCallback callback =
newRole -> {
if (deviceRoles.contains(newRole)) {
- future.set(newRole);
+ future.complete(newRole);
}
};
controller.registerStateCallback(directExecutor(), callback);
- int role = future.get();
+ int role = future.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
controller.unregisterStateCallback(callback);
return role;
}
+ private static void waitForEnabledState(ThreadNetworkController controller, int state)
+ throws Exception {
+ CompletableFuture<Integer> future = new CompletableFuture<>();
+ StateCallback callback =
+ new ThreadNetworkController.StateCallback() {
+ @Override
+ public void onDeviceRoleChanged(int r) {}
+
+ @Override
+ public void onThreadEnableStateChanged(int enabled) {
+ if (enabled == state) {
+ future.complete(enabled);
+ }
+ }
+ };
+ runAsShell(
+ ACCESS_NETWORK_STATE,
+ () -> controller.registerStateCallback(directExecutor(), callback));
+ future.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
+ runAsShell(ACCESS_NETWORK_STATE, () -> controller.unregisterStateCallback(callback));
+ }
+
+ private void leave(
+ ThreadNetworkController controller,
+ OutcomeReceiver<Void, ThreadNetworkException> receiver) {
+ runAsShell(THREAD_NETWORK_PRIVILEGED, () -> controller.leave(mExecutor, receiver));
+ }
+
+ private void leaveAndWait(ThreadNetworkController controller) throws Exception {
+ CompletableFuture<Void> future = new CompletableFuture<>();
+ leave(controller, future::complete);
+ future.get(LEAVE_TIMEOUT_MILLIS, MILLISECONDS);
+ }
+
+ private void scheduleMigration(
+ ThreadNetworkController controller,
+ PendingOperationalDataset pendingDataset,
+ OutcomeReceiver<Void, ThreadNetworkException> receiver) {
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () -> controller.scheduleMigration(pendingDataset, mExecutor, receiver));
+ }
+
+ private class EnabledStateListener {
+ private ArrayTrackRecord<Integer> mEnabledStates = new ArrayTrackRecord<>();
+ private final ArrayTrackRecord<Integer>.ReadHead mReadHead = mEnabledStates.newReadHead();
+ ThreadNetworkController mController;
+ StateCallback mCallback =
+ new ThreadNetworkController.StateCallback() {
+ @Override
+ public void onDeviceRoleChanged(int r) {}
+
+ @Override
+ public void onThreadEnableStateChanged(int enabled) {
+ mEnabledStates.add(enabled);
+ }
+ };
+
+ EnabledStateListener(ThreadNetworkController controller) {
+ this.mController = controller;
+ runAsShell(
+ ACCESS_NETWORK_STATE,
+ () -> controller.registerStateCallback(mExecutor, mCallback));
+ }
+
+ public void expectThreadEnabledState(int enabled) {
+ assertNotNull(mReadHead.poll(ENABLED_TIMEOUT_MILLIS, e -> (e == enabled)));
+ }
+
+ public void unregisterStateCallback() {
+ runAsShell(ACCESS_NETWORK_STATE, () -> mController.unregisterStateCallback(mCallback));
+ }
+ }
+
+ private int booleanToEnabledState(boolean enabled) {
+ return enabled ? STATE_ENABLED : STATE_DISABLED;
+ }
+
+ private void setEnabledAndWait(ThreadNetworkController controller, boolean enabled)
+ throws Exception {
+ CompletableFuture<Void> setFuture = new CompletableFuture<>();
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () -> controller.setEnabled(enabled, mExecutor, newOutcomeReceiver(setFuture)));
+ setFuture.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
+ waitForEnabledState(controller, booleanToEnabledState(enabled));
+ }
+
+ private CompletableFuture joinRandomizedDataset(
+ ThreadNetworkController controller, String networkName) throws Exception {
+ ActiveOperationalDataset activeDataset = newRandomizedDataset(networkName, controller);
+ CompletableFuture<Void> joinFuture = new CompletableFuture<>();
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () -> controller.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture)));
+ return joinFuture;
+ }
+
+ private void joinRandomizedDatasetAndWait(ThreadNetworkController controller) throws Exception {
+ joinRandomizedDatasetAndWait(controller, "TestNet");
+ }
+
+ private void joinRandomizedDatasetAndWait(
+ ThreadNetworkController controller, String networkName) throws Exception {
+ CompletableFuture<Void> joinFuture = joinRandomizedDataset(controller, networkName);
+ joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
+ assertThat(isAttached(controller)).isTrue();
+ }
+
private static ActiveOperationalDataset getActiveOperationalDataset(
ThreadNetworkController controller) throws Exception {
- SettableFuture<ActiveOperationalDataset> future = SettableFuture.create();
- OperationalDatasetCallback callback = future::set;
- controller.registerOperationalDatasetCallback(directExecutor(), callback);
- ActiveOperationalDataset dataset = future.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
- controller.unregisterOperationalDatasetCallback(callback);
- return dataset;
+ CompletableFuture<ActiveOperationalDataset> future = new CompletableFuture<>();
+ OperationalDatasetCallback callback = future::complete;
+ runAsShell(
+ ACCESS_NETWORK_STATE,
+ THREAD_NETWORK_PRIVILEGED,
+ () -> controller.registerOperationalDatasetCallback(directExecutor(), callback));
+ try {
+ return future.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
+ } finally {
+ runAsShell(
+ ACCESS_NETWORK_STATE,
+ THREAD_NETWORK_PRIVILEGED,
+ () -> controller.unregisterOperationalDatasetCallback(callback));
+ }
}
private static PendingOperationalDataset getPendingOperationalDataset(
ThreadNetworkController controller) throws Exception {
- SettableFuture<ActiveOperationalDataset> activeFuture = SettableFuture.create();
- SettableFuture<PendingOperationalDataset> pendingFuture = SettableFuture.create();
+ CompletableFuture<ActiveOperationalDataset> activeFuture = new CompletableFuture<>();
+ CompletableFuture<PendingOperationalDataset> pendingFuture = new CompletableFuture<>();
controller.registerOperationalDatasetCallback(
directExecutor(), newDatasetCallback(activeFuture, pendingFuture));
- return pendingFuture.get();
+ return pendingFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
}
private static OperationalDatasetCallback newDatasetCallback(
- SettableFuture<ActiveOperationalDataset> activeFuture,
- SettableFuture<PendingOperationalDataset> pendingFuture) {
+ CompletableFuture<ActiveOperationalDataset> activeFuture,
+ CompletableFuture<PendingOperationalDataset> pendingFuture) {
return new OperationalDatasetCallback() {
@Override
public void onActiveOperationalDatasetChanged(
ActiveOperationalDataset activeOpDataset) {
- activeFuture.set(activeOpDataset);
+ activeFuture.complete(activeOpDataset);
}
@Override
public void onPendingOperationalDatasetChanged(
PendingOperationalDataset pendingOpDataset) {
- pendingFuture.set(pendingOpDataset);
+ pendingFuture.complete(pendingOpDataset);
}
};
}
- @Test
- public void getThreadVersion_returnsAtLeastThreadVersion1P3() {
- for (ThreadNetworkController controller : getAllControllers()) {
- assertThat(controller.getThreadVersion()).isAtLeast(THREAD_VERSION_1_3);
+ private static void assertDoesNotThrow(ThrowingRunnable runnable) {
+ try {
+ runnable.run();
+ } catch (Throwable e) {
+ fail("Should not have thrown " + e);
}
}
- @Test
- public void registerStateCallback_permissionsGranted_returnsCurrentStates() throws Exception {
- grantPermissions(permission.ACCESS_NETWORK_STATE);
-
- for (ThreadNetworkController controller : getAllControllers()) {
- SettableFuture<Integer> deviceRole = SettableFuture.create();
- StateCallback callback = deviceRole::set;
-
- try {
- controller.registerStateCallback(mExecutor, callback);
-
- assertThat(deviceRole.get()).isEqualTo(DEVICE_ROLE_STOPPED);
- } finally {
- controller.unregisterStateCallback(callback);
- }
- }
- }
-
- @Test
- public void registerStateCallback_noPermissions_throwsSecurityException() throws Exception {
- dropAllPermissions();
-
- for (ThreadNetworkController controller : getAllControllers()) {
- assertThrows(
- SecurityException.class,
- () -> controller.registerStateCallback(mExecutor, role -> {}));
- }
- }
-
- @Test
- public void registerStateCallback_alreadyRegistered_throwsIllegalArgumentException()
- throws Exception {
- grantPermissions(permission.ACCESS_NETWORK_STATE);
-
- for (ThreadNetworkController controller : getAllControllers()) {
- SettableFuture<Integer> deviceRole = SettableFuture.create();
- StateCallback callback = role -> deviceRole.set(role);
- controller.registerStateCallback(mExecutor, callback);
-
- assertThrows(
- IllegalArgumentException.class,
- () -> controller.registerStateCallback(mExecutor, callback));
- }
- }
-
- @Test
- public void unregisterStateCallback_noPermissions_throwsSecurityException() throws Exception {
- for (ThreadNetworkController controller : getAllControllers()) {
- SettableFuture<Integer> deviceRole = SettableFuture.create();
- StateCallback callback = role -> deviceRole.set(role);
- grantPermissions(permission.ACCESS_NETWORK_STATE);
- controller.registerStateCallback(mExecutor, callback);
-
- try {
- dropAllPermissions();
- assertThrows(
- SecurityException.class,
- () -> controller.unregisterStateCallback(callback));
- } finally {
- grantPermissions(permission.ACCESS_NETWORK_STATE);
- controller.unregisterStateCallback(callback);
- }
- }
- }
-
- @Test
- public void unregisterStateCallback_callbackRegistered_success() throws Exception {
- grantPermissions(permission.ACCESS_NETWORK_STATE);
- for (ThreadNetworkController controller : getAllControllers()) {
- SettableFuture<Integer> deviceRole = SettableFuture.create();
- StateCallback callback = role -> deviceRole.set(role);
- controller.registerStateCallback(mExecutor, callback);
-
- controller.unregisterStateCallback(callback);
- }
- }
-
- @Test
- public void unregisterStateCallback_callbackNotRegistered_throwsIllegalArgumentException()
- throws Exception {
- for (ThreadNetworkController controller : getAllControllers()) {
- SettableFuture<Integer> deviceRole = SettableFuture.create();
- StateCallback callback = role -> deviceRole.set(role);
-
- assertThrows(
- IllegalArgumentException.class,
- () -> controller.unregisterStateCallback(callback));
- }
- }
-
- @Test
- public void unregisterStateCallback_alreadyUnregistered_throwsIllegalArgumentException()
- throws Exception {
- grantPermissions(permission.ACCESS_NETWORK_STATE);
- for (ThreadNetworkController controller : getAllControllers()) {
- SettableFuture<Integer> deviceRole = SettableFuture.create();
- StateCallback callback = deviceRole::set;
- controller.registerStateCallback(mExecutor, callback);
- controller.unregisterStateCallback(callback);
-
- assertThrows(
- IllegalArgumentException.class,
- () -> controller.unregisterStateCallback(callback));
- }
- }
-
- @Test
- public void registerOperationalDatasetCallback_permissionsGranted_returnsCurrentStates()
- throws Exception {
- grantPermissions(permission.ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
-
- for (ThreadNetworkController controller : getAllControllers()) {
- SettableFuture<ActiveOperationalDataset> activeFuture = SettableFuture.create();
- SettableFuture<PendingOperationalDataset> pendingFuture = SettableFuture.create();
- var callback = newDatasetCallback(activeFuture, pendingFuture);
-
- try {
- controller.registerOperationalDatasetCallback(mExecutor, callback);
-
- assertThat(activeFuture.get()).isNull();
- assertThat(pendingFuture.get()).isNull();
- } finally {
- controller.unregisterOperationalDatasetCallback(callback);
- }
- }
- }
-
- @Test
- public void registerOperationalDatasetCallback_noPermissions_throwsSecurityException()
- throws Exception {
- dropAllPermissions();
-
- for (ThreadNetworkController controller : getAllControllers()) {
- SettableFuture<ActiveOperationalDataset> activeFuture = SettableFuture.create();
- SettableFuture<PendingOperationalDataset> pendingFuture = SettableFuture.create();
- var callback = newDatasetCallback(activeFuture, pendingFuture);
-
- assertThrows(
- SecurityException.class,
- () -> controller.registerOperationalDatasetCallback(mExecutor, callback));
- }
- }
-
- @Test
- public void unregisterOperationalDatasetCallback_callbackRegistered_success() throws Exception {
- grantPermissions(permission.ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
- for (ThreadNetworkController controller : getAllControllers()) {
- SettableFuture<ActiveOperationalDataset> activeFuture = SettableFuture.create();
- SettableFuture<PendingOperationalDataset> pendingFuture = SettableFuture.create();
- var callback = newDatasetCallback(activeFuture, pendingFuture);
- controller.registerOperationalDatasetCallback(mExecutor, callback);
-
- controller.unregisterOperationalDatasetCallback(callback);
- }
- }
-
- @Test
- public void unregisterOperationalDatasetCallback_noPermissions_throwsSecurityException()
- throws Exception {
- dropAllPermissions();
-
- for (ThreadNetworkController controller : getAllControllers()) {
- SettableFuture<ActiveOperationalDataset> activeFuture = SettableFuture.create();
- SettableFuture<PendingOperationalDataset> pendingFuture = SettableFuture.create();
- var callback = newDatasetCallback(activeFuture, pendingFuture);
- grantPermissions(permission.ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
- controller.registerOperationalDatasetCallback(mExecutor, callback);
-
- try {
- dropAllPermissions();
- assertThrows(
- SecurityException.class,
- () -> controller.unregisterOperationalDatasetCallback(callback));
- } finally {
- grantPermissions(
- permission.ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
- controller.unregisterOperationalDatasetCallback(callback);
- }
- }
- }
-
- private static <V> OutcomeReceiver<V, ThreadNetworkException> newOutcomeReceiver(
- SettableFuture<V> future) {
- return new OutcomeReceiver<V, ThreadNetworkException>() {
- @Override
- public void onResult(V result) {
- future.set(result);
- }
-
- @Override
- public void onError(ThreadNetworkException e) {
- future.setException(e);
- }
- };
- }
-
- @Test
- public void join_withPrivilegedPermission_success() throws Exception {
- grantPermissions(PERMISSION_THREAD_NETWORK_PRIVILEGED);
-
- for (ThreadNetworkController controller : getAllControllers()) {
- ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", controller);
- SettableFuture<Void> joinFuture = SettableFuture.create();
-
- controller.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture));
- joinFuture.get();
-
- grantPermissions(permission.ACCESS_NETWORK_STATE);
- assertThat(isAttached(controller)).isTrue();
- assertThat(getActiveOperationalDataset(controller)).isEqualTo(activeDataset);
- }
- }
-
- @Test
- public void join_withoutPrivilegedPermission_throwsSecurityException() throws Exception {
- dropAllPermissions();
-
- for (ThreadNetworkController controller : getAllControllers()) {
- ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", controller);
-
- assertThrows(
- SecurityException.class,
- () -> controller.join(activeDataset, mExecutor, v -> {}));
- }
- }
-
- @Test
- public void join_concurrentRequests_firstOneIsAborted() throws Exception {
- grantPermissions(PERMISSION_THREAD_NETWORK_PRIVILEGED);
-
- final byte[] KEY_1 = new byte[] {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
- final byte[] KEY_2 = new byte[] {2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
- for (ThreadNetworkController controller : getAllControllers()) {
- ActiveOperationalDataset activeDataset1 =
- new ActiveOperationalDataset.Builder(
- newRandomizedDataset("TestNet", controller))
- .setNetworkKey(KEY_1)
- .build();
- ActiveOperationalDataset activeDataset2 =
- new ActiveOperationalDataset.Builder(activeDataset1)
- .setNetworkKey(KEY_2)
- .build();
- SettableFuture<Void> joinFuture1 = SettableFuture.create();
- SettableFuture<Void> joinFuture2 = SettableFuture.create();
-
- controller.join(activeDataset1, mExecutor, newOutcomeReceiver(joinFuture1));
- controller.join(activeDataset2, mExecutor, newOutcomeReceiver(joinFuture2));
-
- ThreadNetworkException thrown =
- (ThreadNetworkException)
- assertThrows(ExecutionException.class, joinFuture1::get).getCause();
- assertThat(thrown.getErrorCode()).isEqualTo(ERROR_ABORTED);
- joinFuture2.get();
- grantPermissions(permission.ACCESS_NETWORK_STATE);
- assertThat(isAttached(controller)).isTrue();
- assertThat(getActiveOperationalDataset(controller)).isEqualTo(activeDataset2);
- }
- }
-
- @Test
- public void leave_withPrivilegedPermission_success() throws Exception {
- grantPermissions(PERMISSION_THREAD_NETWORK_PRIVILEGED);
-
- for (ThreadNetworkController controller : getAllControllers()) {
- ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", controller);
- SettableFuture<Void> joinFuture = SettableFuture.create();
- SettableFuture<Void> leaveFuture = SettableFuture.create();
- controller.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture));
- joinFuture.get();
-
- controller.leave(mExecutor, newOutcomeReceiver(leaveFuture));
- leaveFuture.get();
-
- grantPermissions(permission.ACCESS_NETWORK_STATE);
- assertThat(getDeviceRole(controller)).isEqualTo(DEVICE_ROLE_STOPPED);
- }
- }
-
- @Test
- public void leave_withoutPrivilegedPermission_throwsSecurityException() {
- dropAllPermissions();
-
- for (ThreadNetworkController controller : getAllControllers()) {
- assertThrows(SecurityException.class, () -> controller.leave(mExecutor, v -> {}));
- }
- }
-
- @Test
- public void leave_concurrentRequests_bothSuccess() throws Exception {
- grantPermissions(PERMISSION_THREAD_NETWORK_PRIVILEGED);
-
- for (ThreadNetworkController controller : getAllControllers()) {
- ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", controller);
- SettableFuture<Void> joinFuture = SettableFuture.create();
- SettableFuture<Void> leaveFuture1 = SettableFuture.create();
- SettableFuture<Void> leaveFuture2 = SettableFuture.create();
- controller.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture));
- joinFuture.get();
-
- controller.leave(mExecutor, newOutcomeReceiver(leaveFuture1));
- controller.leave(mExecutor, newOutcomeReceiver(leaveFuture2));
-
- leaveFuture1.get();
- leaveFuture2.get();
- grantPermissions(permission.ACCESS_NETWORK_STATE);
- assertThat(getDeviceRole(controller)).isEqualTo(DEVICE_ROLE_STOPPED);
- }
- }
-
- @Test
- public void scheduleMigration_withPrivilegedPermission_newDatasetApplied() throws Exception {
- grantPermissions(permission.ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
-
- for (ThreadNetworkController controller : getAllControllers()) {
- ActiveOperationalDataset activeDataset1 =
- new ActiveOperationalDataset.Builder(
- newRandomizedDataset("TestNet", controller))
- .setActiveTimestamp(new OperationalDatasetTimestamp(1L, 0, false))
- .setExtendedPanId(new byte[] {1, 1, 1, 1, 1, 1, 1, 1})
- .build();
- ActiveOperationalDataset activeDataset2 =
- new ActiveOperationalDataset.Builder(activeDataset1)
- .setActiveTimestamp(new OperationalDatasetTimestamp(2L, 0, false))
- .setNetworkName("ThreadNet2")
- .build();
- PendingOperationalDataset pendingDataset2 =
- new PendingOperationalDataset(
- activeDataset2,
- OperationalDatasetTimestamp.fromInstant(Instant.now()),
- Duration.ofSeconds(30));
- SettableFuture<Void> joinFuture = SettableFuture.create();
- SettableFuture<Void> migrateFuture = SettableFuture.create();
- controller.join(activeDataset1, mExecutor, newOutcomeReceiver(joinFuture));
- joinFuture.get();
-
- controller.scheduleMigration(
- pendingDataset2, mExecutor, newOutcomeReceiver(migrateFuture));
- migrateFuture.get();
-
- SettableFuture<Boolean> dataset2IsApplied = SettableFuture.create();
- SettableFuture<Boolean> pendingDatasetIsRemoved = SettableFuture.create();
- OperationalDatasetCallback datasetCallback =
- new OperationalDatasetCallback() {
- @Override
- public void onActiveOperationalDatasetChanged(
- ActiveOperationalDataset activeDataset) {
- if (activeDataset.equals(activeDataset2)) {
- dataset2IsApplied.set(true);
- }
- }
-
- @Override
- public void onPendingOperationalDatasetChanged(
- PendingOperationalDataset pendingDataset) {
- if (pendingDataset == null) {
- pendingDatasetIsRemoved.set(true);
- }
- }
- };
- controller.registerOperationalDatasetCallback(directExecutor(), datasetCallback);
- assertThat(dataset2IsApplied.get()).isTrue();
- assertThat(pendingDatasetIsRemoved.get()).isTrue();
- controller.unregisterOperationalDatasetCallback(datasetCallback);
- }
- }
-
- @Test
- public void scheduleMigration_whenNotAttached_failWithPreconditionError() throws Exception {
- grantPermissions(permission.ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
-
- for (ThreadNetworkController controller : getAllControllers()) {
- PendingOperationalDataset pendingDataset =
- new PendingOperationalDataset(
- newRandomizedDataset("TestNet", controller),
- OperationalDatasetTimestamp.fromInstant(Instant.now()),
- Duration.ofSeconds(30));
- SettableFuture<Void> migrateFuture = SettableFuture.create();
-
- controller.scheduleMigration(
- pendingDataset, mExecutor, newOutcomeReceiver(migrateFuture));
-
- ThreadNetworkException thrown =
- (ThreadNetworkException)
- assertThrows(ExecutionException.class, migrateFuture::get).getCause();
- assertThat(thrown.getErrorCode()).isEqualTo(ERROR_FAILED_PRECONDITION);
- }
- }
-
- @Test
- public void scheduleMigration_secondRequestHasSmallerTimestamp_rejectedByLeader()
- throws Exception {
- grantPermissions(permission.ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
-
- for (ThreadNetworkController controller : getAllControllers()) {
- final ActiveOperationalDataset activeDataset =
- new ActiveOperationalDataset.Builder(
- newRandomizedDataset("testNet", controller))
- .setActiveTimestamp(new OperationalDatasetTimestamp(1L, 0, false))
- .build();
- ActiveOperationalDataset activeDataset1 =
- new ActiveOperationalDataset.Builder(activeDataset)
- .setActiveTimestamp(new OperationalDatasetTimestamp(2L, 0, false))
- .setNetworkName("testNet1")
- .build();
- PendingOperationalDataset pendingDataset1 =
- new PendingOperationalDataset(
- activeDataset1,
- new OperationalDatasetTimestamp(100, 0, false),
- Duration.ofSeconds(30));
- ActiveOperationalDataset activeDataset2 =
- new ActiveOperationalDataset.Builder(activeDataset)
- .setActiveTimestamp(new OperationalDatasetTimestamp(3L, 0, false))
- .setNetworkName("testNet2")
- .build();
- PendingOperationalDataset pendingDataset2 =
- new PendingOperationalDataset(
- activeDataset2,
- new OperationalDatasetTimestamp(20, 0, false),
- Duration.ofSeconds(30));
- SettableFuture<Void> joinFuture = SettableFuture.create();
- SettableFuture<Void> migrateFuture1 = SettableFuture.create();
- SettableFuture<Void> migrateFuture2 = SettableFuture.create();
- controller.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture));
- joinFuture.get();
-
- controller.scheduleMigration(
- pendingDataset1, mExecutor, newOutcomeReceiver(migrateFuture1));
- migrateFuture1.get();
- controller.scheduleMigration(
- pendingDataset2, mExecutor, newOutcomeReceiver(migrateFuture2));
-
- ThreadNetworkException thrown =
- (ThreadNetworkException)
- assertThrows(ExecutionException.class, migrateFuture2::get).getCause();
- assertThat(thrown.getErrorCode()).isEqualTo(ERROR_REJECTED_BY_PEER);
- }
- }
-
- @Test
- public void scheduleMigration_secondRequestHasLargerTimestamp_newDatasetApplied()
- throws Exception {
- grantPermissions(permission.ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
-
- for (ThreadNetworkController controller : getAllControllers()) {
- final ActiveOperationalDataset activeDataset =
- new ActiveOperationalDataset.Builder(
- newRandomizedDataset("validName", controller))
- .setActiveTimestamp(new OperationalDatasetTimestamp(1L, 0, false))
- .build();
- ActiveOperationalDataset activeDataset1 =
- new ActiveOperationalDataset.Builder(activeDataset)
- .setActiveTimestamp(new OperationalDatasetTimestamp(2L, 0, false))
- .setNetworkName("testNet1")
- .build();
- PendingOperationalDataset pendingDataset1 =
- new PendingOperationalDataset(
- activeDataset1,
- new OperationalDatasetTimestamp(100, 0, false),
- Duration.ofSeconds(30));
- ActiveOperationalDataset activeDataset2 =
- new ActiveOperationalDataset.Builder(activeDataset)
- .setActiveTimestamp(new OperationalDatasetTimestamp(3L, 0, false))
- .setNetworkName("testNet2")
- .build();
- PendingOperationalDataset pendingDataset2 =
- new PendingOperationalDataset(
- activeDataset2,
- new OperationalDatasetTimestamp(200, 0, false),
- Duration.ofSeconds(30));
- SettableFuture<Void> joinFuture = SettableFuture.create();
- SettableFuture<Void> migrateFuture1 = SettableFuture.create();
- SettableFuture<Void> migrateFuture2 = SettableFuture.create();
- controller.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture));
- joinFuture.get();
-
- controller.scheduleMigration(
- pendingDataset1, mExecutor, newOutcomeReceiver(migrateFuture1));
- migrateFuture1.get();
- controller.scheduleMigration(
- pendingDataset2, mExecutor, newOutcomeReceiver(migrateFuture2));
- migrateFuture2.get();
-
- SettableFuture<Boolean> dataset2IsApplied = SettableFuture.create();
- SettableFuture<Boolean> pendingDatasetIsRemoved = SettableFuture.create();
- OperationalDatasetCallback datasetCallback =
- new OperationalDatasetCallback() {
- @Override
- public void onActiveOperationalDatasetChanged(
- ActiveOperationalDataset activeDataset) {
- if (activeDataset.equals(activeDataset2)) {
- dataset2IsApplied.set(true);
- }
- }
-
- @Override
- public void onPendingOperationalDatasetChanged(
- PendingOperationalDataset pendingDataset) {
- if (pendingDataset == null) {
- pendingDatasetIsRemoved.set(true);
- }
- }
- };
- controller.registerOperationalDatasetCallback(directExecutor(), datasetCallback);
- assertThat(dataset2IsApplied.get()).isTrue();
- assertThat(pendingDatasetIsRemoved.get()).isTrue();
- controller.unregisterOperationalDatasetCallback(datasetCallback);
- }
- }
-
- @Test
- public void createRandomizedDataset_wrongNetworkNameLength_throwsIllegalArgumentException() {
- for (ThreadNetworkController controller : getAllControllers()) {
- assertThrows(
- IllegalArgumentException.class,
- () -> controller.createRandomizedDataset("", mExecutor, dataset -> {}));
-
- assertThrows(
- IllegalArgumentException.class,
- () ->
- controller.createRandomizedDataset(
- "ANetNameIs17Bytes", mExecutor, dataset -> {}));
- }
- }
-
- @Test
- public void createRandomizedDataset_validNetworkName_success() throws Exception {
- for (ThreadNetworkController controller : getAllControllers()) {
- ActiveOperationalDataset dataset = newRandomizedDataset("validName", controller);
-
- assertThat(dataset.getNetworkName()).isEqualTo("validName");
- assertThat(dataset.getPanId()).isLessThan(0xffff);
- assertThat(dataset.getChannelMask().size()).isAtLeast(1);
- assertThat(dataset.getExtendedPanId()).hasLength(8);
- assertThat(dataset.getNetworkKey()).hasLength(16);
- assertThat(dataset.getPskc()).hasLength(16);
- assertThat(dataset.getMeshLocalPrefix().getPrefixLength()).isEqualTo(64);
- assertThat(dataset.getMeshLocalPrefix().getRawAddress()[0]).isEqualTo((byte) 0xfd);
- }
- }
-
- @Test
- public void threadNetworkCallback_deviceAttached_threadNetworkIsAvailable() throws Exception {
- ThreadNetworkController controller = mManager.getAllThreadNetworkControllers().get(0);
- ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", controller);
- SettableFuture<Void> joinFuture = SettableFuture.create();
- SettableFuture<Network> networkFuture = SettableFuture.create();
- ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
- NetworkRequest networkRequest =
- new NetworkRequest.Builder()
- .addTransportType(NetworkCapabilities.TRANSPORT_THREAD)
- .build();
- ConnectivityManager.NetworkCallback networkCallback =
- new ConnectivityManager.NetworkCallback() {
+ // Return the first discovered service instance.
+ private NsdServiceInfo discoverService(String serviceType) throws Exception {
+ CompletableFuture<NsdServiceInfo> serviceInfoFuture = new CompletableFuture<>();
+ NsdManager.DiscoveryListener listener =
+ new DefaultDiscoveryListener() {
@Override
- public void onAvailable(Network network) {
- networkFuture.set(network);
+ public void onServiceFound(NsdServiceInfo serviceInfo) {
+ serviceInfoFuture.complete(serviceInfo);
}
};
+ mNsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, listener);
+ try {
+ serviceInfoFuture.get(SERVICE_DISCOVERY_TIMEOUT_MILLIS, MILLISECONDS);
+ } finally {
+ mNsdManager.stopServiceDiscovery(listener);
+ }
- runAsShell(
- PERMISSION_THREAD_NETWORK_PRIVILEGED,
- () -> controller.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture)));
- runAsShell(
- permission.ACCESS_NETWORK_STATE,
- () -> cm.registerNetworkCallback(networkRequest, networkCallback));
+ return serviceInfoFuture.get();
+ }
- joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
- runAsShell(
- permission.ACCESS_NETWORK_STATE, () -> assertThat(isAttached(controller)).isTrue());
- assertThat(networkFuture.get(NETWORK_CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isNotNull();
+ private NsdManager.DiscoveryListener discoverForServiceLost(
+ String serviceType, CompletableFuture<NsdServiceInfo> serviceInfoFuture) {
+ NsdManager.DiscoveryListener listener =
+ new DefaultDiscoveryListener() {
+ @Override
+ public void onServiceLost(NsdServiceInfo serviceInfo) {
+ serviceInfoFuture.complete(serviceInfo);
+ }
+ };
+ mNsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, listener);
+ return listener;
+ }
+
+ private NsdServiceInfo expectServiceResolved(
+ String serviceType, int timeoutMilliseconds, Predicate<NsdServiceInfo> predicate)
+ throws Exception {
+ NsdServiceInfo discoveredServiceInfo = discoverService(serviceType);
+ CompletableFuture<NsdServiceInfo> future = new CompletableFuture<>();
+ NsdManager.ServiceInfoCallback callback =
+ new DefaultServiceInfoCallback() {
+ @Override
+ public void onServiceUpdated(@NonNull NsdServiceInfo serviceInfo) {
+ if (predicate.test(serviceInfo)) {
+ future.complete(serviceInfo);
+ }
+ }
+ };
+ mNsdManager.registerServiceInfoCallback(discoveredServiceInfo, mExecutor, callback);
+ try {
+ return future.get(timeoutMilliseconds, MILLISECONDS);
+ } finally {
+ mNsdManager.unregisterServiceInfoCallback(callback);
+ }
+ }
+
+ TestNetworkTracker setUpTestNetwork() {
+ return runAsShell(
+ MANAGE_TEST_NETWORKS,
+ () -> initTestNetwork(mContext, new LinkAddress("2001:db8:123::/64"), 10_000));
+ }
+
+ void tearDownTestNetwork(TestNetworkTracker testNetwork) {
+ runAsShell(MANAGE_TEST_NETWORKS, () -> testNetwork.teardown());
+ }
+
+ private static class DefaultDiscoveryListener implements NsdManager.DiscoveryListener {
+ @Override
+ public void onStartDiscoveryFailed(String serviceType, int errorCode) {}
+
+ @Override
+ public void onStopDiscoveryFailed(String serviceType, int errorCode) {}
+
+ @Override
+ public void onDiscoveryStarted(String serviceType) {}
+
+ @Override
+ public void onDiscoveryStopped(String serviceType) {}
+
+ @Override
+ public void onServiceFound(NsdServiceInfo serviceInfo) {}
+
+ @Override
+ public void onServiceLost(NsdServiceInfo serviceInfo) {}
+ }
+
+ private static class DefaultServiceInfoCallback implements NsdManager.ServiceInfoCallback {
+ @Override
+ public void onServiceInfoCallbackRegistrationFailed(int errorCode) {}
+
+ @Override
+ public void onServiceUpdated(@NonNull NsdServiceInfo serviceInfo) {}
+
+ @Override
+ public void onServiceLost() {}
+
+ @Override
+ public void onServiceInfoCallbackUnregistered() {}
}
}
diff --git a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkExceptionTest.java b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkExceptionTest.java
new file mode 100644
index 0000000..7d9ae81
--- /dev/null
+++ b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkExceptionTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.thread.cts;
+
+import static android.net.thread.ThreadNetworkException.ERROR_INTERNAL_ERROR;
+import static android.net.thread.ThreadNetworkException.ERROR_THREAD_DISABLED;
+import static android.net.thread.ThreadNetworkException.ERROR_UNKNOWN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.net.thread.ThreadNetworkException;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** CTS tests for {@link ThreadNetworkException}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class ThreadNetworkExceptionTest {
+ @Test
+ public void constructor_validValues_valuesAreConnectlySet() throws Exception {
+ ThreadNetworkException errorThreadDisabled =
+ new ThreadNetworkException(ERROR_THREAD_DISABLED, "Thread disabled error!");
+ ThreadNetworkException errorInternalError =
+ new ThreadNetworkException(ERROR_INTERNAL_ERROR, "internal error!");
+
+ assertThat(errorThreadDisabled.getErrorCode()).isEqualTo(ERROR_THREAD_DISABLED);
+ assertThat(errorThreadDisabled.getMessage()).isEqualTo("Thread disabled error!");
+ assertThat(errorInternalError.getErrorCode()).isEqualTo(ERROR_INTERNAL_ERROR);
+ assertThat(errorInternalError.getMessage()).isEqualTo("internal error!");
+ }
+
+ @Test
+ public void constructor_nullMessage_throwsNullPointerException() throws Exception {
+ assertThrows(
+ NullPointerException.class,
+ () -> new ThreadNetworkException(ERROR_UNKNOWN, null /* message */));
+ }
+
+ @Test
+ public void constructor_tooSmallErrorCode_throwsIllegalArgumentException() throws Exception {
+ assertThrows(IllegalArgumentException.class, () -> new ThreadNetworkException(0, "0"));
+ // TODO: add argument check for too large error code when mainline CTS is ready. This was
+ // not added here for CTS forward copatibility.
+ }
+}
diff --git a/thread/tests/integration/Android.bp b/thread/tests/integration/Android.bp
index 405fb76..6ba192d 100644
--- a/thread/tests/integration/Android.bp
+++ b/thread/tests/integration/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_fwk_thread_network",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -23,12 +24,14 @@
min_sdk_version: "30",
static_libs: [
"androidx.test.rules",
+ "compatibility-device-util-axt",
"guava",
"mockito-target-minus-junit4",
"net-tests-utils",
"net-utils-device-common",
"net-utils-device-common-bpf",
"testables",
+ "truth",
],
libs: [
"android.test.runner",
@@ -41,11 +44,13 @@
name: "ThreadNetworkIntegrationTests",
platform_apis: true,
manifest: "AndroidManifest.xml",
+ test_config: "AndroidTest.xml",
defaults: [
"framework-connectivity-test-defaults",
- "ThreadNetworkIntegrationTestsDefaults"
+ "ThreadNetworkIntegrationTestsDefaults",
],
test_suites: [
+ "mts-tethering",
"general-tests",
],
srcs: [
diff --git a/thread/tests/integration/AndroidTest.xml b/thread/tests/integration/AndroidTest.xml
new file mode 100644
index 0000000..152c1c3
--- /dev/null
+++ b/thread/tests/integration/AndroidTest.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<configuration description="Config for Thread integration tests">
+ <option name="test-tag" value="ThreadNetworkIntegrationTests" />
+ <option name="test-suite-tag" value="apct" />
+
+ <!--
+ Only run tests if the device under test is SDK version 34 (Android 14) or above.
+ -->
+ <object type="module_controller"
+ class="com.android.tradefed.testtype.suite.module.Sdk34ModuleController" />
+
+ <!-- Run tests in MTS only if the Tethering Mainline module is installed. -->
+ <object type="module_controller"
+ class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+ <option name="mainline-module-package-name" value="com.google.android.tethering" />
+ </object>
+
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
+
+ <!-- Install test -->
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="test-file-name" value="ThreadNetworkIntegrationTests.apk" />
+ <option name="check-min-sdk" value="true" />
+ <option name="cleanup-apks" value="true" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.thread.tests.integration" />
+ </test>
+</configuration>
diff --git a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
index 25f5bd3..29ada1b 100644
--- a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
+++ b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
@@ -18,28 +18,32 @@
import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
import static android.Manifest.permission.NETWORK_SETTINGS;
-import static android.net.thread.IntegrationTestUtils.isExpectedIcmpv6Packet;
-import static android.net.thread.IntegrationTestUtils.isSimulatedThreadRadioSupported;
-import static android.net.thread.IntegrationTestUtils.newPacketReader;
-import static android.net.thread.IntegrationTestUtils.readPacketFrom;
-import static android.net.thread.IntegrationTestUtils.waitFor;
-import static android.net.thread.IntegrationTestUtils.waitForStateAnyOf;
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_LEADER;
import static android.net.thread.ThreadNetworkManager.PERMISSION_THREAD_NETWORK_PRIVILEGED;
+import static android.net.thread.utils.IntegrationTestUtils.JOIN_TIMEOUT;
+import static android.net.thread.utils.IntegrationTestUtils.isExpectedIcmpv6Packet;
+import static android.net.thread.utils.IntegrationTestUtils.isSimulatedThreadRadioSupported;
+import static android.net.thread.utils.IntegrationTestUtils.newPacketReader;
+import static android.net.thread.utils.IntegrationTestUtils.readPacketFrom;
+import static android.net.thread.utils.IntegrationTestUtils.waitFor;
+import static android.net.thread.utils.IntegrationTestUtils.waitForStateAnyOf;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REPLY_TYPE;
import static com.android.testutils.TestNetworkTrackerKt.initTestNetwork;
import static com.android.testutils.TestPermissionUtil.runAsShell;
import static com.google.common.io.BaseEncoding.base16;
+import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
-import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assume.assumeNotNull;
import static org.junit.Assume.assumeTrue;
import android.content.Context;
import android.net.LinkProperties;
import android.net.MacAddress;
+import android.net.thread.utils.FullThreadDevice;
+import android.net.thread.utils.InfraNetworkDevice;
import android.os.Handler;
import android.os.HandlerThread;
@@ -50,14 +54,13 @@
import com.android.testutils.TapPacketReader;
import com.android.testutils.TestNetworkTracker;
-import com.google.common.util.concurrent.MoreExecutors;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.net.Inet6Address;
+import java.time.Duration;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -68,9 +71,7 @@
public class BorderRoutingTest {
private static final String TAG = BorderRoutingTest.class.getSimpleName();
private final Context mContext = ApplicationProvider.getApplicationContext();
- private final ThreadNetworkManager mThreadNetworkManager =
- mContext.getSystemService(ThreadNetworkManager.class);
- private ThreadNetworkController mThreadNetworkController;
+ private ThreadNetworkController mController;
private HandlerThread mHandlerThread;
private Handler mHandler;
private TestNetworkTracker mInfraNetworkTracker;
@@ -88,12 +89,18 @@
@Before
public void setUp() throws Exception {
+ final ThreadNetworkManager manager = mContext.getSystemService(ThreadNetworkManager.class);
+ if (manager != null) {
+ mController = manager.getAllThreadNetworkControllers().get(0);
+ }
+
+ // Run the tests on only devices where the Thread feature is available
+ assumeNotNull(mController);
+
mHandlerThread = new HandlerThread(getClass().getSimpleName());
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
- var threadControllers = mThreadNetworkManager.getAllThreadNetworkControllers();
- assertEquals(threadControllers.size(), 1);
- mThreadNetworkController = threadControllers.get(0);
+
mInfraNetworkTracker =
runAsShell(
MANAGE_TEST_NETWORKS,
@@ -105,27 +112,28 @@
NETWORK_SETTINGS,
() -> {
CountDownLatch latch = new CountDownLatch(1);
- mThreadNetworkController.setTestNetworkAsUpstream(
+ mController.setTestNetworkAsUpstream(
mInfraNetworkTracker.getTestIface().getInterfaceName(),
- MoreExecutors.directExecutor(),
- v -> {
- latch.countDown();
- });
+ directExecutor(),
+ v -> latch.countDown());
latch.await();
});
}
@After
public void tearDown() throws Exception {
+ if (mController == null) {
+ return;
+ }
+
runAsShell(
PERMISSION_THREAD_NETWORK_PRIVILEGED,
NETWORK_SETTINGS,
() -> {
CountDownLatch latch = new CountDownLatch(2);
- mThreadNetworkController.setTestNetworkAsUpstream(
- null, MoreExecutors.directExecutor(), v -> latch.countDown());
- mThreadNetworkController.leave(
- MoreExecutors.directExecutor(), v -> latch.countDown());
+ mController.setTestNetworkAsUpstream(
+ null, directExecutor(), v -> latch.countDown());
+ mController.leave(directExecutor(), v -> latch.countDown());
latch.await(10, TimeUnit.SECONDS);
});
runAsShell(MANAGE_TEST_NETWORKS, () -> mInfraNetworkTracker.teardown());
@@ -150,19 +158,15 @@
// BR forms a network.
runAsShell(
PERMISSION_THREAD_NETWORK_PRIVILEGED,
- () -> {
- mThreadNetworkController.join(
- DEFAULT_DATASET, MoreExecutors.directExecutor(), result -> {});
- });
- waitForStateAnyOf(
- mThreadNetworkController, List.of(DEVICE_ROLE_LEADER), 30 /* timeoutSeconds */);
+ () -> mController.join(DEFAULT_DATASET, directExecutor(), result -> {}));
+ waitForStateAnyOf(mController, List.of(DEVICE_ROLE_LEADER), JOIN_TIMEOUT);
// Creates a Full Thread Device (FTD) and lets it join the network.
FullThreadDevice ftd = new FullThreadDevice(5 /* node ID */);
ftd.factoryReset();
ftd.joinNetwork(DEFAULT_DATASET);
- ftd.waitForStateAnyOf(List.of("router", "child"), 10 /* timeoutSeconds */);
- waitFor(() -> ftd.getOmrAddress() != null, 60 /* timeoutSeconds */);
+ ftd.waitForStateAnyOf(List.of("router", "child"), JOIN_TIMEOUT);
+ waitFor(() -> ftd.getOmrAddress() != null, Duration.ofSeconds(60));
Inet6Address ftdOmr = ftd.getOmrAddress();
assertNotNull(ftdOmr);
@@ -171,7 +175,7 @@
newPacketReader(mInfraNetworkTracker.getTestIface(), mHandler);
InfraNetworkDevice infraDevice =
new InfraNetworkDevice(MacAddress.fromString("1:2:3:4:5:6"), infraNetworkReader);
- infraDevice.runSlaac(60 /* timeoutSeconds */);
+ infraDevice.runSlaac(Duration.ofSeconds(60));
assertNotNull(infraDevice.ipv6Addr);
// Infra device sends an echo request to FTD's OMR.
diff --git a/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java b/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
new file mode 100644
index 0000000..70897f0
--- /dev/null
+++ b/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.thread;
+
+import static android.Manifest.permission.NETWORK_SETTINGS;
+import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_DETACHED;
+import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_LEADER;
+import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_STOPPED;
+import static android.net.thread.ThreadNetworkManager.PERMISSION_THREAD_NETWORK_PRIVILEGED;
+import static android.net.thread.utils.IntegrationTestUtils.CALLBACK_TIMEOUT;
+import static android.net.thread.utils.IntegrationTestUtils.LEAVE_TIMEOUT;
+import static android.net.thread.utils.IntegrationTestUtils.RESTART_JOIN_TIMEOUT;
+import static android.net.thread.utils.IntegrationTestUtils.waitForStateAnyOf;
+
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import static com.android.testutils.TestPermissionUtil.runAsShell;
+
+import static com.google.common.io.BaseEncoding.base16;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
+
+import static org.junit.Assume.assumeNotNull;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.net.thread.ThreadNetworkController.StateCallback;
+import android.net.thread.utils.OtDaemonController;
+import android.os.SystemClock;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet6Address;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+/** Tests for E2E Android Thread integration with ot-daemon, ConnectivityService, etc.. */
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class ThreadIntegrationTest {
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ private ThreadNetworkController mController;
+ private OtDaemonController mOtCtl;
+
+ // A valid Thread Active Operational Dataset generated from OpenThread CLI "dataset init new".
+ private static final byte[] DEFAULT_DATASET_TLVS =
+ base16().decode(
+ "0E080000000000010000000300001335060004001FFFE002"
+ + "08ACC214689BC40BDF0708FD64DB1225F47E0B0510F26B31"
+ + "53760F519A63BAFDDFFC80D2AF030F4F70656E5468726561"
+ + "642D643961300102D9A00410A245479C836D551B9CA557F7"
+ + "B9D351B40C0402A0FFF8");
+ private static final ActiveOperationalDataset DEFAULT_DATASET =
+ ActiveOperationalDataset.fromThreadTlvs(DEFAULT_DATASET_TLVS);
+
+ @Before
+ public void setUp() throws Exception {
+ final ThreadNetworkManager manager = mContext.getSystemService(ThreadNetworkManager.class);
+ if (manager != null) {
+ mController = manager.getAllThreadNetworkControllers().get(0);
+ }
+
+ // Run the tests on only devices where the Thread feature is available
+ assumeNotNull(mController);
+
+ mOtCtl = new OtDaemonController();
+ leaveAndWait(mController);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mController == null) {
+ return;
+ }
+
+ setTestUpStreamNetworkAndWait(mController, null);
+ leaveAndWait(mController);
+ }
+
+ @Test
+ public void otDaemonRestart_notJoinedAndStopped_deviceRoleIsStopped() throws Exception {
+ leaveAndWait(mController);
+
+ runShellCommand("stop ot-daemon");
+ // TODO(b/323331973): the sleep is needed to workaround the race conditions
+ SystemClock.sleep(200);
+
+ waitForStateAnyOf(mController, List.of(DEVICE_ROLE_STOPPED), CALLBACK_TIMEOUT);
+ }
+
+ @Test
+ public void otDaemonRestart_JoinedNetworkAndStopped_autoRejoined() throws Exception {
+ joinAndWait(mController, DEFAULT_DATASET);
+
+ runShellCommand("stop ot-daemon");
+
+ waitForStateAnyOf(mController, List.of(DEVICE_ROLE_DETACHED), CALLBACK_TIMEOUT);
+ waitForStateAnyOf(mController, List.of(DEVICE_ROLE_LEADER), RESTART_JOIN_TIMEOUT);
+ }
+
+ @Test
+ public void otDaemonFactoryReset_deviceRoleIsStopped() throws Exception {
+ joinAndWait(mController, DEFAULT_DATASET);
+
+ mOtCtl.factoryReset();
+
+ assertThat(getDeviceRole(mController)).isEqualTo(DEVICE_ROLE_STOPPED);
+ }
+
+ @Test
+ public void otDaemonFactoryReset_addressesRemoved() throws Exception {
+ joinAndWait(mController, DEFAULT_DATASET);
+
+ mOtCtl.factoryReset();
+ String ifconfig = runShellCommand("ifconfig thread-wpan");
+
+ assertThat(ifconfig).doesNotContain("inet6 addr");
+ }
+
+ @Test
+ public void tunInterface_joinedNetwork_otAddressesAddedToTunInterface() throws Exception {
+ joinAndWait(mController, DEFAULT_DATASET);
+
+ String ifconfig = runShellCommand("ifconfig thread-wpan");
+ List<Inet6Address> otAddresses = mOtCtl.getAddresses();
+ assertThat(otAddresses).isNotEmpty();
+ for (Inet6Address otAddress : otAddresses) {
+ assertThat(ifconfig).contains(otAddress.getHostAddress());
+ }
+ }
+
+ // TODO (b/323300829): add more tests for integration with linux platform and
+ // ConnectivityService
+
+ private static int getDeviceRole(ThreadNetworkController controller) throws Exception {
+ CompletableFuture<Integer> future = new CompletableFuture<>();
+ StateCallback callback = future::complete;
+ controller.registerStateCallback(directExecutor(), callback);
+ try {
+ return future.get(CALLBACK_TIMEOUT.toMillis(), MILLISECONDS);
+ } finally {
+ controller.unregisterStateCallback(callback);
+ }
+ }
+
+ private static void joinAndWait(
+ ThreadNetworkController controller, ActiveOperationalDataset activeDataset)
+ throws Exception {
+ runAsShell(
+ PERMISSION_THREAD_NETWORK_PRIVILEGED,
+ () -> controller.join(activeDataset, directExecutor(), result -> {}));
+ waitForStateAnyOf(controller, List.of(DEVICE_ROLE_LEADER), RESTART_JOIN_TIMEOUT);
+ }
+
+ private static void leaveAndWait(ThreadNetworkController controller) throws Exception {
+ CompletableFuture<Void> future = new CompletableFuture<>();
+ runAsShell(
+ PERMISSION_THREAD_NETWORK_PRIVILEGED,
+ () -> controller.leave(directExecutor(), future::complete));
+ future.get(LEAVE_TIMEOUT.toMillis(), MILLISECONDS);
+ }
+
+ private static void setTestUpStreamNetworkAndWait(
+ ThreadNetworkController controller, @Nullable String networkInterfaceName)
+ throws Exception {
+ CompletableFuture<Void> future = new CompletableFuture<>();
+ runAsShell(
+ PERMISSION_THREAD_NETWORK_PRIVILEGED,
+ NETWORK_SETTINGS,
+ () -> {
+ controller.setTestNetworkAsUpstream(
+ networkInterfaceName, directExecutor(), future::complete);
+ });
+ future.get(CALLBACK_TIMEOUT.toMillis(), MILLISECONDS);
+ }
+}
diff --git a/thread/tests/integration/src/android/net/thread/FullThreadDevice.java b/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java
similarity index 93%
rename from thread/tests/integration/src/android/net/thread/FullThreadDevice.java
rename to thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java
index 01638f3..031d205 100644
--- a/thread/tests/integration/src/android/net/thread/FullThreadDevice.java
+++ b/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java
@@ -13,9 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.net.thread;
+package android.net.thread.utils;
-import static android.net.thread.IntegrationTestUtils.waitFor;
+import static android.net.thread.utils.IntegrationTestUtils.waitFor;
import static com.google.common.io.BaseEncoding.base16;
@@ -23,6 +23,7 @@
import android.net.InetAddresses;
import android.net.IpPrefix;
+import android.net.thread.ActiveOperationalDataset;
import java.io.BufferedReader;
import java.io.BufferedWriter;
@@ -30,6 +31,7 @@
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Inet6Address;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeoutException;
@@ -115,10 +117,10 @@
*
* @param states the list of states to wait for. Valid states are "disabled", "detached",
* "child", "router" and "leader".
- * @param timeoutSeconds the number of seconds to wait for.
+ * @param timeout the time to wait for the expected state before throwing
*/
- public void waitForStateAnyOf(List<String> states, int timeoutSeconds) throws TimeoutException {
- waitFor(() -> states.contains(getState()), timeoutSeconds);
+ public void waitForStateAnyOf(List<String> states, Duration timeout) throws TimeoutException {
+ waitFor(() -> states.contains(getState()), timeout);
}
/**
diff --git a/thread/tests/integration/src/android/net/thread/InfraNetworkDevice.java b/thread/tests/integration/src/android/net/thread/utils/InfraNetworkDevice.java
similarity index 91%
rename from thread/tests/integration/src/android/net/thread/InfraNetworkDevice.java
rename to thread/tests/integration/src/android/net/thread/utils/InfraNetworkDevice.java
index 43a800d..3081f9f 100644
--- a/thread/tests/integration/src/android/net/thread/InfraNetworkDevice.java
+++ b/thread/tests/integration/src/android/net/thread/utils/InfraNetworkDevice.java
@@ -13,11 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.net.thread;
+package android.net.thread.utils;
-import static android.net.thread.IntegrationTestUtils.getRaPios;
-import static android.net.thread.IntegrationTestUtils.readPacketFrom;
-import static android.net.thread.IntegrationTestUtils.waitFor;
+import static android.net.thread.utils.IntegrationTestUtils.getRaPios;
+import static android.net.thread.utils.IntegrationTestUtils.readPacketFrom;
+import static android.net.thread.utils.IntegrationTestUtils.waitFor;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_SLLA;
import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_ROUTERS_MULTICAST;
@@ -34,6 +34,7 @@
import java.net.Inet6Address;
import java.net.InetAddress;
import java.nio.ByteBuffer;
+import java.time.Duration;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeoutException;
@@ -100,8 +101,8 @@
* @param timeoutSeconds the number of seconds to wait for.
* @throws TimeoutException when the device fails to generate a SLAAC address in given timeout.
*/
- public void runSlaac(int timeoutSeconds) throws TimeoutException {
- waitFor(() -> (ipv6Addr = runSlaac()) != null, timeoutSeconds, 5 /* intervalSeconds */);
+ public void runSlaac(Duration timeout) throws TimeoutException {
+ waitFor(() -> (ipv6Addr = runSlaac()) != null, timeout);
}
private Inet6Address runSlaac() {
diff --git a/thread/tests/integration/src/android/net/thread/IntegrationTestUtils.java b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
similarity index 80%
rename from thread/tests/integration/src/android/net/thread/IntegrationTestUtils.java
rename to thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
index c465d57..f223367 100644
--- a/thread/tests/integration/src/android/net/thread/IntegrationTestUtils.java
+++ b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.net.thread;
+package android.net.thread.utils;
import static android.system.OsConstants.IPPROTO_ICMPV6;
@@ -23,6 +23,7 @@
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
import android.net.TestNetworkInterface;
+import android.net.thread.ThreadNetworkController;
import android.os.Handler;
import android.os.SystemClock;
import android.os.SystemProperties;
@@ -39,6 +40,7 @@
import java.io.FileDescriptor;
import java.nio.ByteBuffer;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
@@ -49,6 +51,14 @@
/** Static utility methods relating to Thread integration tests. */
public final class IntegrationTestUtils {
+ // The timeout of join() after restarting ot-daemon. The device needs to send 6 Link Request
+ // every 5 seconds, followed by 4 Parent Request every second. So this value needs to be 40
+ // seconds to be safe
+ public static final Duration RESTART_JOIN_TIMEOUT = Duration.ofSeconds(40);
+ public static final Duration JOIN_TIMEOUT = Duration.ofSeconds(30);
+ public static final Duration LEAVE_TIMEOUT = Duration.ofSeconds(2);
+ public static final Duration CALLBACK_TIMEOUT = Duration.ofSeconds(1);
+
private IntegrationTestUtils() {}
/** Returns whether the device supports simulated Thread radio. */
@@ -60,49 +70,33 @@
/**
* Waits for the given {@link Supplier} to be true until given timeout.
*
- * <p>It checks the condition once every second.
- *
- * @param condition the condition to check.
- * @param timeoutSeconds the number of seconds to wait for.
- * @throws TimeoutException if the condition is not met after the timeout.
+ * @param condition the condition to check
+ * @param timeout the time to wait for the condition before throwing
+ * @throws TimeoutException if the condition is still not met when the timeout expires
*/
- public static void waitFor(Supplier<Boolean> condition, int timeoutSeconds)
+ public static void waitFor(Supplier<Boolean> condition, Duration timeout)
throws TimeoutException {
- waitFor(condition, timeoutSeconds, 1);
- }
+ final long intervalMills = 1000;
+ final long timeoutMills = timeout.toMillis();
- /**
- * Waits for the given {@link Supplier} to be true until given timeout.
- *
- * <p>It checks the condition once every {@code intervalSeconds}.
- *
- * @param condition the condition to check.
- * @param timeoutSeconds the number of seconds to wait for.
- * @param intervalSeconds the period to check the {@code condition}.
- * @throws TimeoutException if the condition is still not met when the timeout expires.
- */
- public static void waitFor(Supplier<Boolean> condition, int timeoutSeconds, int intervalSeconds)
- throws TimeoutException {
- for (int i = 0; i < timeoutSeconds; i += intervalSeconds) {
+ for (long i = 0; i < timeoutMills; i += intervalMills) {
if (condition.get()) {
return;
}
- SystemClock.sleep(intervalSeconds * 1000L);
+ SystemClock.sleep(intervalMills);
}
if (condition.get()) {
return;
}
- throw new TimeoutException(
- String.format(
- "The condition failed to become true in %d seconds.", timeoutSeconds));
+ throw new TimeoutException("The condition failed to become true in " + timeout);
}
/**
* Creates a {@link TapPacketReader} given the {@link TestNetworkInterface} and {@link Handler}.
*
- * @param testNetworkInterface the TUN interface of the test network.
- * @param handler the handler to process the packets.
- * @return the {@link TapPacketReader}.
+ * @param testNetworkInterface the TUN interface of the test network
+ * @param handler the handler to process the packets
+ * @return the {@link TapPacketReader}
*/
public static TapPacketReader newPacketReader(
TestNetworkInterface testNetworkInterface, Handler handler) {
@@ -117,16 +111,16 @@
/**
* Waits for the Thread module to enter any state of the given {@code deviceRoles}.
*
- * @param controller the {@link ThreadNetworkController}.
+ * @param controller the {@link ThreadNetworkController}
* @param deviceRoles the desired device roles. See also {@link
- * ThreadNetworkController.DeviceRole}.
- * @param timeoutSeconds the number of seconds ot wait for.
- * @return the {@link ThreadNetworkController.DeviceRole} after waiting.
+ * ThreadNetworkController.DeviceRole}
+ * @param timeout the time to wait for the expected state before throwing
+ * @return the {@link ThreadNetworkController.DeviceRole} after waiting
* @throws TimeoutException if the device hasn't become any of expected roles until the timeout
- * expires.
+ * expires
*/
public static int waitForStateAnyOf(
- ThreadNetworkController controller, List<Integer> deviceRoles, int timeoutSeconds)
+ ThreadNetworkController controller, List<Integer> deviceRoles, Duration timeout)
throws TimeoutException {
SettableFuture<Integer> future = SettableFuture.create();
ThreadNetworkController.StateCallback callback =
@@ -137,24 +131,24 @@
};
controller.registerStateCallback(directExecutor(), callback);
try {
- int role = future.get(timeoutSeconds, TimeUnit.SECONDS);
- controller.unregisterStateCallback(callback);
- return role;
+ return future.get(timeout.toMillis(), TimeUnit.MILLISECONDS);
} catch (InterruptedException | ExecutionException e) {
throw new TimeoutException(
String.format(
- "The device didn't become an expected role in %d seconds.",
- timeoutSeconds));
+ "The device didn't become an expected role in %s: %s",
+ timeout, e.getMessage()));
+ } finally {
+ controller.unregisterStateCallback(callback);
}
}
/**
* Reads a packet from a given {@link TapPacketReader} that satisfies the {@code filter}.
*
- * @param packetReader a TUN packet reader.
- * @param filter the filter to be applied on the packet.
+ * @param packetReader a TUN packet reader
+ * @param filter the filter to be applied on the packet
* @return the first IPv6 packet that satisfies the {@code filter}. If it has waited for more
- * than 3000ms to read the next packet, the method will return null.
+ * than 3000ms to read the next packet, the method will return null
*/
public static byte[] readPacketFrom(TapPacketReader packetReader, Predicate<byte[]> filter) {
byte[] packet;
diff --git a/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java b/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java
new file mode 100644
index 0000000..4a06fe8
--- /dev/null
+++ b/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.thread.utils;
+
+import android.net.InetAddresses;
+import android.os.SystemClock;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import java.net.Inet6Address;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Wrapper of the "/system/bin/ot-ctl" which can be used to send CLI commands to ot-daemon to
+ * control its behavior.
+ *
+ * <p>Note that this class takes root privileged to run.
+ */
+public final class OtDaemonController {
+ private static final String OT_CTL = "/system/bin/ot-ctl";
+
+ /**
+ * Factory resets ot-daemon.
+ *
+ * <p>This will erase all persistent data written into apexdata/com.android.apex/ot-daemon and
+ * restart the ot-daemon service.
+ */
+ public void factoryReset() {
+ executeCommand("factoryreset");
+
+ // TODO(b/323164524): ot-ctl is a separate process so that the tests can't depend on the
+ // time sequence. Here needs to wait for system server to receive the ot-daemon death
+ // signal and take actions.
+ // A proper fix is to replace "ot-ctl" with "cmd thread_network ot-ctl" which is
+ // synchronized with the system server
+ SystemClock.sleep(500);
+ }
+
+ /** Returns the list of IPv6 addresses on ot-daemon. */
+ public List<Inet6Address> getAddresses() {
+ String output = executeCommand("ipaddr");
+ return Arrays.asList(output.split("\n")).stream()
+ .map(String::trim)
+ .filter(str -> !str.equals("Done"))
+ .map(addr -> InetAddresses.parseNumericAddress(addr))
+ .map(inetAddr -> (Inet6Address) inetAddr)
+ .toList();
+ }
+
+ public String executeCommand(String cmd) {
+ return SystemUtil.runShellCommand(OT_CTL + " " + cmd);
+ }
+}
diff --git a/thread/tests/unit/Android.bp b/thread/tests/unit/Android.bp
index 291475e..3365cd0 100644
--- a/thread/tests/unit/Android.bp
+++ b/thread/tests/unit/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_fwk_thread_network",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -29,6 +30,7 @@
],
test_suites: [
"general-tests",
+ "mts-tethering",
],
static_libs: [
"frameworks-base-testutils",
diff --git a/thread/tests/unit/src/android/net/thread/ThreadNetworkExceptionTest.java b/thread/tests/unit/src/android/net/thread/ThreadNetworkExceptionTest.java
new file mode 100644
index 0000000..f62b437
--- /dev/null
+++ b/thread/tests/unit/src/android/net/thread/ThreadNetworkExceptionTest.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.thread;
+
+import static org.junit.Assert.assertThrows;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit tests for {@link ThreadNetworkException} to cover what is not covered in CTS tests. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class ThreadNetworkExceptionTest {
+ @Test
+ public void constructor_tooLargeErrorCode_throwsIllegalArgumentException() throws Exception {
+ // TODO (b/323791003): move this test case to cts/ThreadNetworkExceptionTest when mainline
+ // CTS is ready.
+ assertThrows(IllegalArgumentException.class, () -> new ThreadNetworkException(13, "13"));
+ }
+}
diff --git a/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java b/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java
new file mode 100644
index 0000000..8aea0a3
--- /dev/null
+++ b/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java
@@ -0,0 +1,367 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.thread;
+
+import static android.net.nsd.NsdManager.FAILURE_INTERNAL_ERROR;
+import static android.net.nsd.NsdManager.PROTOCOL_DNS_SD;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.net.nsd.NsdManager;
+import android.net.nsd.NsdServiceInfo;
+import android.os.Handler;
+import android.os.test.TestLooper;
+
+import com.android.server.thread.openthread.DnsTxtAttribute;
+import com.android.server.thread.openthread.INsdStatusReceiver;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+/** Unit tests for {@link NsdPublisher}. */
+public final class NsdPublisherTest {
+ @Mock private NsdManager mMockNsdManager;
+
+ @Mock private INsdStatusReceiver mRegistrationReceiver;
+ @Mock private INsdStatusReceiver mUnregistrationReceiver;
+
+ private TestLooper mTestLooper;
+ private NsdPublisher mNsdPublisher;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void registerService_nsdManagerSucceeds_serviceRegistrationSucceeds() throws Exception {
+ prepareTest();
+
+ DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
+ DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
+
+ mNsdPublisher.registerService(
+ null,
+ "MyService",
+ "_test._tcp",
+ List.of("_subtype1", "_subtype2"),
+ 12345,
+ List.of(txt1, txt2),
+ mRegistrationReceiver,
+ 16 /* listenerId */);
+
+ mTestLooper.dispatchAll();
+
+ ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
+ ArgumentCaptor.forClass(NsdServiceInfo.class);
+ ArgumentCaptor<NsdManager.RegistrationListener> actualRegistrationListenerCaptor =
+ ArgumentCaptor.forClass(NsdManager.RegistrationListener.class);
+
+ verify(mMockNsdManager, times(1))
+ .registerService(
+ actualServiceInfoCaptor.capture(),
+ eq(PROTOCOL_DNS_SD),
+ any(),
+ actualRegistrationListenerCaptor.capture());
+
+ NsdServiceInfo actualServiceInfo = actualServiceInfoCaptor.getValue();
+ NsdManager.RegistrationListener actualRegistrationListener =
+ actualRegistrationListenerCaptor.getValue();
+
+ actualRegistrationListener.onServiceRegistered(actualServiceInfo);
+ mTestLooper.dispatchAll();
+
+ assertThat(actualServiceInfo.getServiceName()).isEqualTo("MyService");
+ assertThat(actualServiceInfo.getServiceType()).isEqualTo("_test._tcp");
+ assertThat(actualServiceInfo.getSubtypes()).isEqualTo(Set.of("_subtype1", "_subtype2"));
+ assertThat(actualServiceInfo.getPort()).isEqualTo(12345);
+ assertThat(actualServiceInfo.getAttributes().size()).isEqualTo(2);
+ assertThat(actualServiceInfo.getAttributes().get("key1"))
+ .isEqualTo(new byte[] {(byte) 0x01, (byte) 0x02});
+ assertThat(actualServiceInfo.getAttributes().get("key2"))
+ .isEqualTo(new byte[] {(byte) 0x03});
+
+ verify(mRegistrationReceiver, times(1)).onSuccess();
+ }
+
+ @Test
+ public void registerService_nsdManagerFails_serviceRegistrationFails() throws Exception {
+ prepareTest();
+
+ DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
+ DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
+
+ mNsdPublisher.registerService(
+ null,
+ "MyService",
+ "_test._tcp",
+ List.of("_subtype1", "_subtype2"),
+ 12345,
+ List.of(txt1, txt2),
+ mRegistrationReceiver,
+ 16 /* listenerId */);
+
+ mTestLooper.dispatchAll();
+
+ ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
+ ArgumentCaptor.forClass(NsdServiceInfo.class);
+ ArgumentCaptor<NsdManager.RegistrationListener> actualRegistrationListenerCaptor =
+ ArgumentCaptor.forClass(NsdManager.RegistrationListener.class);
+
+ verify(mMockNsdManager, times(1))
+ .registerService(
+ actualServiceInfoCaptor.capture(),
+ eq(PROTOCOL_DNS_SD),
+ any(Executor.class),
+ actualRegistrationListenerCaptor.capture());
+
+ NsdServiceInfo actualServiceInfo = actualServiceInfoCaptor.getValue();
+ NsdManager.RegistrationListener actualRegistrationListener =
+ actualRegistrationListenerCaptor.getValue();
+
+ actualRegistrationListener.onRegistrationFailed(actualServiceInfo, FAILURE_INTERNAL_ERROR);
+ mTestLooper.dispatchAll();
+
+ assertThat(actualServiceInfo.getServiceName()).isEqualTo("MyService");
+ assertThat(actualServiceInfo.getServiceType()).isEqualTo("_test._tcp");
+ assertThat(actualServiceInfo.getSubtypes()).isEqualTo(Set.of("_subtype1", "_subtype2"));
+ assertThat(actualServiceInfo.getPort()).isEqualTo(12345);
+ assertThat(actualServiceInfo.getAttributes().size()).isEqualTo(2);
+ assertThat(actualServiceInfo.getAttributes().get("key1"))
+ .isEqualTo(new byte[] {(byte) 0x01, (byte) 0x02});
+ assertThat(actualServiceInfo.getAttributes().get("key2"))
+ .isEqualTo(new byte[] {(byte) 0x03});
+
+ verify(mRegistrationReceiver, times(1)).onError(FAILURE_INTERNAL_ERROR);
+ }
+
+ @Test
+ public void registerService_nsdManagerThrows_serviceRegistrationFails() throws Exception {
+ prepareTest();
+
+ DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
+ DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
+
+ doThrow(new IllegalArgumentException("NsdManager fails"))
+ .when(mMockNsdManager)
+ .registerService(any(), anyInt(), any(Executor.class), any());
+
+ mNsdPublisher.registerService(
+ null,
+ "MyService",
+ "_test._tcp",
+ List.of("_subtype1", "_subtype2"),
+ 12345,
+ List.of(txt1, txt2),
+ mRegistrationReceiver,
+ 16 /* listenerId */);
+ mTestLooper.dispatchAll();
+
+ verify(mRegistrationReceiver, times(1)).onError(FAILURE_INTERNAL_ERROR);
+ }
+
+ @Test
+ public void unregisterService_nsdManagerSucceeds_serviceUnregistrationSucceeds()
+ throws Exception {
+ prepareTest();
+
+ DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
+ DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
+
+ mNsdPublisher.registerService(
+ null,
+ "MyService",
+ "_test._tcp",
+ List.of("_subtype1", "_subtype2"),
+ 12345,
+ List.of(txt1, txt2),
+ mRegistrationReceiver,
+ 16 /* listenerId */);
+
+ mTestLooper.dispatchAll();
+
+ ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
+ ArgumentCaptor.forClass(NsdServiceInfo.class);
+ ArgumentCaptor<NsdManager.RegistrationListener> actualRegistrationListenerCaptor =
+ ArgumentCaptor.forClass(NsdManager.RegistrationListener.class);
+
+ verify(mMockNsdManager, times(1))
+ .registerService(
+ actualServiceInfoCaptor.capture(),
+ eq(PROTOCOL_DNS_SD),
+ any(Executor.class),
+ actualRegistrationListenerCaptor.capture());
+
+ NsdServiceInfo actualServiceInfo = actualServiceInfoCaptor.getValue();
+ NsdManager.RegistrationListener actualRegistrationListener =
+ actualRegistrationListenerCaptor.getValue();
+
+ actualRegistrationListener.onServiceRegistered(actualServiceInfo);
+ mNsdPublisher.unregister(mUnregistrationReceiver, 16 /* listenerId */);
+ mTestLooper.dispatchAll();
+ verify(mMockNsdManager, times(1)).unregisterService(actualRegistrationListener);
+
+ actualRegistrationListener.onServiceUnregistered(actualServiceInfo);
+ mTestLooper.dispatchAll();
+ verify(mUnregistrationReceiver, times(1)).onSuccess();
+ }
+
+ @Test
+ public void unregisterService_nsdManagerFails_serviceUnregistrationFails() throws Exception {
+ prepareTest();
+
+ DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
+ DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
+
+ mNsdPublisher.registerService(
+ null,
+ "MyService",
+ "_test._tcp",
+ List.of("_subtype1", "_subtype2"),
+ 12345,
+ List.of(txt1, txt2),
+ mRegistrationReceiver,
+ 16 /* listenerId */);
+
+ mTestLooper.dispatchAll();
+
+ ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
+ ArgumentCaptor.forClass(NsdServiceInfo.class);
+ ArgumentCaptor<NsdManager.RegistrationListener> actualRegistrationListenerCaptor =
+ ArgumentCaptor.forClass(NsdManager.RegistrationListener.class);
+
+ verify(mMockNsdManager, times(1))
+ .registerService(
+ actualServiceInfoCaptor.capture(),
+ eq(PROTOCOL_DNS_SD),
+ any(Executor.class),
+ actualRegistrationListenerCaptor.capture());
+
+ NsdServiceInfo actualServiceInfo = actualServiceInfoCaptor.getValue();
+ NsdManager.RegistrationListener actualRegistrationListener =
+ actualRegistrationListenerCaptor.getValue();
+
+ actualRegistrationListener.onServiceRegistered(actualServiceInfo);
+ mNsdPublisher.unregister(mUnregistrationReceiver, 16 /* listenerId */);
+ mTestLooper.dispatchAll();
+ verify(mMockNsdManager, times(1)).unregisterService(actualRegistrationListener);
+
+ actualRegistrationListener.onUnregistrationFailed(
+ actualServiceInfo, FAILURE_INTERNAL_ERROR);
+ mTestLooper.dispatchAll();
+ verify(mUnregistrationReceiver, times(1)).onError(0);
+ }
+
+ @Test
+ public void onOtDaemonDied_unregisterAll() {
+ prepareTest();
+
+ DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
+ DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
+
+ ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
+ ArgumentCaptor.forClass(NsdServiceInfo.class);
+ ArgumentCaptor<NsdManager.RegistrationListener> actualRegistrationListenerCaptor =
+ ArgumentCaptor.forClass(NsdManager.RegistrationListener.class);
+
+ mNsdPublisher.registerService(
+ null,
+ "MyService",
+ "_test._tcp",
+ List.of("_subtype1", "_subtype2"),
+ 12345,
+ List.of(txt1, txt2),
+ mRegistrationReceiver,
+ 16 /* listenerId */);
+ mTestLooper.dispatchAll();
+
+ verify(mMockNsdManager, times(1))
+ .registerService(
+ actualServiceInfoCaptor.capture(),
+ eq(PROTOCOL_DNS_SD),
+ any(Executor.class),
+ actualRegistrationListenerCaptor.capture());
+ NsdManager.RegistrationListener actualListener1 =
+ actualRegistrationListenerCaptor.getValue();
+ actualListener1.onServiceRegistered(actualServiceInfoCaptor.getValue());
+
+ mNsdPublisher.registerService(
+ null,
+ "MyService2",
+ "_test._udp",
+ Collections.emptyList(),
+ 11111,
+ Collections.emptyList(),
+ mRegistrationReceiver,
+ 17 /* listenerId */);
+
+ mTestLooper.dispatchAll();
+
+ verify(mMockNsdManager, times(2))
+ .registerService(
+ actualServiceInfoCaptor.capture(),
+ eq(PROTOCOL_DNS_SD),
+ any(Executor.class),
+ actualRegistrationListenerCaptor.capture());
+ NsdManager.RegistrationListener actualListener2 =
+ actualRegistrationListenerCaptor.getAllValues().get(1);
+ actualListener2.onServiceRegistered(actualServiceInfoCaptor.getValue());
+
+ mNsdPublisher.onOtDaemonDied();
+ mTestLooper.dispatchAll();
+
+ verify(mMockNsdManager, times(1)).unregisterService(actualListener1);
+ verify(mMockNsdManager, times(1)).unregisterService(actualListener2);
+ }
+
+ private static DnsTxtAttribute makeTxtAttribute(String name, List<Integer> value) {
+ DnsTxtAttribute txtAttribute = new DnsTxtAttribute();
+
+ txtAttribute.name = name;
+ txtAttribute.value = new byte[value.size()];
+
+ for (int i = 0; i < value.size(); ++i) {
+ txtAttribute.value[i] = value.get(i).byteValue();
+ }
+
+ return txtAttribute;
+ }
+
+ // @Before and @Test run in different threads. NsdPublisher requires the jobs are run on the
+ // thread looper, so TestLooper needs to be created inside each test case to install the
+ // correct looper.
+ private void prepareTest() {
+ mTestLooper = new TestLooper();
+ Handler handler = new Handler(mTestLooper.getLooper());
+ mNsdPublisher = new NsdPublisher(mMockNsdManager, handler);
+ }
+}
diff --git a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
index 44a8ab7..f626edf 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
@@ -24,6 +24,7 @@
import static com.google.common.io.BaseEncoding.base16;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
@@ -85,6 +86,8 @@
@Mock private TunInterfaceController mMockTunIfController;
@Mock private ParcelFileDescriptor mMockTunFd;
@Mock private InfraInterfaceController mMockInfraIfController;
+ @Mock private ThreadPersistentSettings mMockPersistentSettings;
+ @Mock private NsdPublisher mMockNsdPublisher;
private Context mContext;
private TestLooper mTestLooper;
private FakeOtDaemon mFakeOtDaemon;
@@ -104,6 +107,8 @@
when(mMockTunIfController.getTunFd()).thenReturn(mMockTunFd);
+ when(mMockPersistentSettings.get(any())).thenReturn(true);
+
mService =
new ThreadNetworkControllerService(
ApplicationProvider.getApplicationContext(),
@@ -112,12 +117,14 @@
() -> mFakeOtDaemon,
mMockConnectivityManager,
mMockTunIfController,
- mMockInfraIfController);
+ mMockInfraIfController,
+ mMockPersistentSettings,
+ mMockNsdPublisher);
mService.setTestNetworkAgent(mMockNetworkAgent);
}
@Test
- public void initialize_tunInterfaceSetToOtDaemon() throws Exception {
+ public void initialize_tunInterfaceAndNsdPublisherSetToOtDaemon() throws Exception {
when(mMockTunIfController.getTunFd()).thenReturn(mMockTunFd);
mService.initialize();
@@ -125,6 +132,7 @@
verify(mMockTunIfController, times(1)).createTunInterface();
assertThat(mFakeOtDaemon.getTunFd()).isEqualTo(mMockTunFd);
+ assertThat(mFakeOtDaemon.getNsdPublisher()).isEqualTo(mMockNsdPublisher);
}
@Test
diff --git a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkCountryCodeTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkCountryCodeTest.java
index 17cdd01..5ca6511 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkCountryCodeTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkCountryCodeTest.java
@@ -22,6 +22,7 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyDouble;
@@ -55,6 +56,7 @@
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -70,6 +72,9 @@
import org.mockito.MockitoAnnotations;
import org.mockito.stubbing.Answer;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.io.StringWriter;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
@@ -80,6 +85,8 @@
public class ThreadNetworkCountryCodeTest {
private static final String TEST_COUNTRY_CODE_US = "US";
private static final String TEST_COUNTRY_CODE_CN = "CN";
+ private static final String TEST_COUNTRY_CODE_INVALID = "INVALID";
+ private static final String TEST_WIFI_DEFAULT_COUNTRY_CODE = "00";
private static final int TEST_SIM_SLOT_INDEX_0 = 0;
private static final int TEST_SIM_SLOT_INDEX_1 = 1;
@@ -144,16 +151,20 @@
.when(mThreadNetworkControllerService)
.setCountryCode(any(), any(IOperationReceiver.class));
- mThreadNetworkCountryCode =
- new ThreadNetworkCountryCode(
- mLocationManager,
- mThreadNetworkControllerService,
- mGeocoder,
- mConnectivityResources,
- mWifiManager,
- mContext,
- mTelephonyManager,
- mSubscriptionManager);
+ mThreadNetworkCountryCode = newCountryCodeWithOemSource(null);
+ }
+
+ private ThreadNetworkCountryCode newCountryCodeWithOemSource(@Nullable String oemCountryCode) {
+ return new ThreadNetworkCountryCode(
+ mLocationManager,
+ mThreadNetworkControllerService,
+ mGeocoder,
+ mConnectivityResources,
+ mWifiManager,
+ mContext,
+ mTelephonyManager,
+ mSubscriptionManager,
+ oemCountryCode);
}
private static Address newAddress(String countryCode) {
@@ -163,6 +174,13 @@
}
@Test
+ public void threadNetworkCountryCode_invalidOemCountryCode_illegalArgumentExceptionIsThrown() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> newCountryCodeWithOemSource(TEST_COUNTRY_CODE_INVALID));
+ }
+
+ @Test
public void initialize_defaultCountryCodeIsUsed() {
mThreadNetworkCountryCode.initialize();
@@ -170,6 +188,15 @@
}
@Test
+ public void initialize_oemCountryCodeAvailable_oemCountryCodeIsUsed() {
+ mThreadNetworkCountryCode = newCountryCodeWithOemSource(TEST_COUNTRY_CODE_US);
+
+ mThreadNetworkCountryCode.initialize();
+
+ assertThat(mThreadNetworkCountryCode.getCountryCode()).isEqualTo(TEST_COUNTRY_CODE_US);
+ }
+
+ @Test
public void initialize_locationUseIsDisabled_locationFunctionIsNotCalled() {
when(mResources.getBoolean(R.bool.config_thread_location_use_for_country_code_enabled))
.thenReturn(false);
@@ -233,6 +260,21 @@
}
@Test
+ public void wifiCountryCode_wifiDefaultCountryCodeIsActive_wifiCountryCodeIsNotUsed() {
+ mThreadNetworkCountryCode.initialize();
+
+ verify(mWifiManager)
+ .registerActiveCountryCodeChangedCallback(
+ any(), mWifiCountryCodeReceiverCaptor.capture());
+ mWifiCountryCodeReceiverCaptor
+ .getValue()
+ .onActiveCountryCodeChanged(TEST_WIFI_DEFAULT_COUNTRY_CODE);
+
+ assertThat(mThreadNetworkCountryCode.getCountryCode())
+ .isNotEqualTo(TEST_WIFI_DEFAULT_COUNTRY_CODE);
+ }
+
+ @Test
public void wifiCountryCode_wifiCountryCodeIsInactive_defaultCountryCodeIsUsed() {
mThreadNetworkCountryCode.initialize();
verify(mWifiManager)
@@ -406,4 +448,22 @@
.setCountryCode(eq(TEST_COUNTRY_CODE_CN), mOperationReceiverCaptor.capture());
assertThat(mThreadNetworkCountryCode.getCountryCode()).isEqualTo(DEFAULT_COUNTRY_CODE);
}
+
+ @Test
+ public void dump_allCountryCodeInfoAreDumped() {
+ StringWriter stringWriter = new StringWriter();
+ PrintWriter printWriter = new PrintWriter(stringWriter);
+
+ mThreadNetworkCountryCode.dump(new FileDescriptor(), printWriter, null);
+ String outputString = stringWriter.toString();
+
+ assertThat(outputString).contains("mOverrideCountryCodeInfo");
+ assertThat(outputString).contains("mTelephonyCountryCodeSlotInfoMap");
+ assertThat(outputString).contains("mTelephonyCountryCodeInfo");
+ assertThat(outputString).contains("mWifiCountryCodeInfo");
+ assertThat(outputString).contains("mTelephonyLastCountryCodeInfo");
+ assertThat(outputString).contains("mLocationCountryCodeInfo");
+ assertThat(outputString).contains("mOemCountryCodeInfo");
+ assertThat(outputString).contains("mCurrentCountryCodeInfo");
+ }
}
diff --git a/tools/Android.bp b/tools/Android.bp
index 3ce76f6..b7b2aaa 100644
--- a/tools/Android.bp
+++ b/tools/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_fwk_core_networking",
// See: http://go/android-license-faq
default_applicable_licenses: ["Android-Apache-2.0"],
}