Merge "Include structs lib in service-connectivity-pre-jarjar" into main
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index d79be20..30bdf37 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -54,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 {
diff --git a/Tethering/common/TetheringLib/Android.bp b/Tethering/common/TetheringLib/Android.bp
index 9c2a59d..47227e3 100644
--- a/Tethering/common/TetheringLib/Android.bp
+++ b/Tethering/common/TetheringLib/Android.bp
@@ -59,6 +59,9 @@
lint: {
strict_updatability_linting: true,
},
+ aconfig_declarations: [
+ "com.android.net.flags-aconfig",
+ ],
}
java_library {
diff --git a/Tethering/common/TetheringLib/api/system-current.txt b/Tethering/common/TetheringLib/api/system-current.txt
index 844ff64..a287b42 100644
--- a/Tethering/common/TetheringLib/api/system-current.txt
+++ b/Tethering/common/TetheringLib/api/system-current.txt
@@ -95,13 +95,16 @@
method public default void onUpstreamChanged(@Nullable android.net.Network);
}
- public static class TetheringManager.TetheringRequest {
+ public static final class TetheringManager.TetheringRequest implements android.os.Parcelable {
+ method @FlaggedApi("com.android.net.flags.tethering_request_with_soft_ap_config") public int describeContents();
method @Nullable public android.net.LinkAddress getClientStaticIpv4Address();
method public int getConnectivityScope();
method @Nullable public android.net.LinkAddress getLocalIpv4Address();
method public boolean getShouldShowEntitlementUi();
method public int getTetheringType();
method public boolean isExemptFromEntitlementCheck();
+ method @FlaggedApi("com.android.net.flags.tethering_request_with_soft_ap_config") public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @FlaggedApi("com.android.net.flags.tethering_request_with_soft_ap_config") @NonNull public static final android.os.Parcelable.Creator<android.net.TetheringManager.TetheringRequest> CREATOR;
}
public static class TetheringManager.TetheringRequest.Builder {
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index cd914d3..7b769d4 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -18,6 +18,7 @@
import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
import android.Manifest;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -28,6 +29,8 @@
import android.os.Bundle;
import android.os.ConditionVariable;
import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.util.ArrayMap;
@@ -59,6 +62,14 @@
*/
@SystemApi
public class TetheringManager {
+ // TODO : remove this class when udc-mainline-prod is abandoned and android.net.flags.Flags is
+ // available here
+ /** @hide */
+ public static class Flags {
+ static final String TETHERING_REQUEST_WITH_SOFT_AP_CONFIG =
+ "com.android.net.flags.tethering_request_with_soft_ap_config";
+ }
+
private static final String TAG = TetheringManager.class.getSimpleName();
private static final int DEFAULT_TIMEOUT_MS = 60_000;
private static final long CONNECTOR_POLL_INTERVAL_MILLIS = 200L;
@@ -673,14 +684,44 @@
/**
* Use with {@link #startTethering} to specify additional parameters when starting tethering.
*/
- public static class TetheringRequest {
+ public static final class TetheringRequest implements Parcelable {
/** A configuration set for TetheringRequest. */
private final TetheringRequestParcel mRequestParcel;
- private TetheringRequest(final TetheringRequestParcel request) {
+ private TetheringRequest(@NonNull final TetheringRequestParcel request) {
mRequestParcel = request;
}
+ private TetheringRequest(@NonNull Parcel in) {
+ mRequestParcel = in.readParcelable(TetheringRequestParcel.class.getClassLoader());
+ }
+
+ @FlaggedApi(Flags.TETHERING_REQUEST_WITH_SOFT_AP_CONFIG)
+ @NonNull
+ public static final Creator<TetheringRequest> CREATOR = new Creator<>() {
+ @Override
+ public TetheringRequest createFromParcel(@NonNull Parcel in) {
+ return new TetheringRequest(in);
+ }
+
+ @Override
+ public TetheringRequest[] newArray(int size) {
+ return new TetheringRequest[size];
+ }
+ };
+
+ @FlaggedApi(Flags.TETHERING_REQUEST_WITH_SOFT_AP_CONFIG)
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @FlaggedApi(Flags.TETHERING_REQUEST_WITH_SOFT_AP_CONFIG)
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeParcelable(mRequestParcel, flags);
+ }
+
/** Builder used to create TetheringRequest. */
public static class Builder {
private final TetheringRequestParcel mBuilderParcel;
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/bpf_progs/netd.h b/bpf_progs/netd.h
index 64ed633..098147f 100644
--- a/bpf_progs/netd.h
+++ b/bpf_progs/netd.h
@@ -178,7 +178,7 @@
#endif // __cplusplus
// LINT.IfChange(match_type)
-enum UidOwnerMatchType {
+enum UidOwnerMatchType : uint32_t {
NO_MATCH = 0,
HAPPY_BOX_MATCH = (1 << 0),
PENALTY_BOX_MATCH = (1 << 1),
@@ -196,14 +196,14 @@
};
// LINT.ThenChange(../framework/src/android/net/BpfNetMapsConstants.java)
-enum BpfPermissionMatch {
+enum BpfPermissionMatch : uint8_t {
BPF_PERMISSION_INTERNET = 1 << 2,
BPF_PERMISSION_UPDATE_DEVICE_STATS = 1 << 3,
};
// In production we use two identical stats maps to record per uid stats and
// do swap and clean based on the configuration specified here. The statsMapType
// value in configuration map specified which map is currently in use.
-enum StatsMapType {
+enum StatsMapType : uint32_t {
SELECT_MAP_A,
SELECT_MAP_B,
};
diff --git a/common/Android.bp b/common/Android.bp
index f4b4cae..0048a0a 100644
--- a/common/Android.bp
+++ b/common/Android.bp
@@ -20,6 +20,8 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
+build = ["FlaggedApi.bp"]
+
// This is a placeholder comment to avoid merge conflicts
// as the above target may not exist
// depending on the branch
diff --git a/common/FlaggedApi.bp b/common/FlaggedApi.bp
index c382e76..56625c5 100644
--- a/common/FlaggedApi.bp
+++ b/common/FlaggedApi.bp
@@ -21,3 +21,19 @@
srcs: ["flags.aconfig"],
visibility: ["//packages/modules/Connectivity:__subpackages__"],
}
+
+aconfig_declarations {
+ name: "com.android.net.thread.flags-aconfig",
+ package: "com.android.net.thread.flags",
+ container: "system",
+ srcs: ["thread_flags.aconfig"],
+ visibility: ["//packages/modules/Connectivity:__subpackages__"],
+}
+
+aconfig_declarations {
+ name: "nearby_flags",
+ package: "com.android.nearby.flags",
+ container: "system",
+ srcs: ["nearby_flags.aconfig"],
+ visibility: ["//packages/modules/Connectivity:__subpackages__"],
+}
diff --git a/common/OWNERS b/common/OWNERS
new file mode 100644
index 0000000..e7f5d11
--- /dev/null
+++ b/common/OWNERS
@@ -0,0 +1 @@
+per-file thread_flags.aconfig = file:platform/packages/modules/Connectivity:main:/thread/OWNERS
diff --git a/common/flags.aconfig b/common/flags.aconfig
index 30f5d9c..19b522c 100644
--- a/common/flags.aconfig
+++ b/common/flags.aconfig
@@ -5,13 +5,6 @@
# Flags used from platform code must be in under frameworks
flag {
- name: "forbidden_capability"
- namespace: "android_core_networking"
- description: "This flag controls the forbidden capability API"
- bug: "302997505"
-}
-
-flag {
name: "set_data_saver_via_cm"
namespace: "android_core_networking"
description: "Set data saver through ConnectivityManager API"
@@ -33,9 +26,50 @@
}
flag {
- name: "register_nsd_offload_engine"
- namespace: "android_core_networking"
- description: "The flag controls the access for registerOffloadEngine API in NsdManager"
- bug: "294777050"
+ name: "ipsec_transform_state"
+ namespace: "android_core_networking_ipsec"
+ description: "The flag controls the access for getIpSecTransformState and IpSecTransformState"
+ bug: "308011229"
}
+flag {
+ name: "tethering_request_with_soft_ap_config"
+ namespace: "android_core_networking"
+ description: "The flag controls the access for the parcelable TetheringRequest with getSoftApConfiguration/setSoftApConfiguration API"
+ bug: "216524590"
+}
+
+flag {
+ name: "request_restricted_wifi"
+ namespace: "android_core_networking"
+ description: "Flag for API to support requesting restricted wifi"
+ bug: "315835605"
+}
+
+flag {
+ name: "net_capability_local_network"
+ namespace: "android_core_networking"
+ description: "Flag for local network capability API"
+ bug: "313000440"
+}
+
+flag {
+ name: "support_transport_satellite"
+ namespace: "android_core_networking"
+ description: "Flag for satellite transport API"
+ bug: "320514105"
+}
+
+flag {
+ name: "nsd_subtypes_support_enabled"
+ namespace: "android_core_networking"
+ description: "Flag for API to support nsd subtypes"
+ bug: "265095929"
+}
+
+flag {
+ name: "register_nsd_offload_engine_api"
+ namespace: "android_core_networking"
+ description: "Flag for API to register nsd offload engine"
+ bug: "301713539"
+}
diff --git a/common/nearby_flags.aconfig b/common/nearby_flags.aconfig
new file mode 100644
index 0000000..b957d33
--- /dev/null
+++ b/common/nearby_flags.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.nearby.flags"
+container: "system"
+
+flag {
+ name: "powered_off_finding"
+ namespace: "nearby"
+ description: "Controls whether the Powered Off Finding feature is enabled"
+ bug: "307898240"
+}
diff --git a/thread/flags/thread_base.aconfig b/common/thread_flags.aconfig
similarity index 100%
rename from thread/flags/thread_base.aconfig
rename to common/thread_flags.aconfig
diff --git a/framework-t/Android.bp b/framework-t/Android.bp
index f485a44..468cee4 100644
--- a/framework-t/Android.bp
+++ b/framework-t/Android.bp
@@ -118,6 +118,7 @@
"framework-bluetooth",
"framework-wifi",
"framework-connectivity-pre-jarjar",
+ "framework-location.stubs.module_lib",
],
visibility: ["//packages/modules/Connectivity:__subpackages__"],
}
@@ -140,6 +141,7 @@
"sdk_module-lib_current_framework-connectivity",
],
libs: [
+ "framework-location.stubs.module_lib",
"sdk_module-lib_current_framework-connectivity",
],
permitted_packages: [
@@ -195,6 +197,8 @@
],
aconfig_declarations: [
"com.android.net.flags-aconfig",
+ "com.android.net.thread.flags-aconfig",
+ "nearby_flags",
],
}
diff --git a/framework-t/api/current.txt b/framework-t/api/current.txt
index 7cd3d4f..9ae0cf7 100644
--- a/framework-t/api/current.txt
+++ b/framework-t/api/current.txt
@@ -127,7 +127,7 @@
public final class IpSecTransform implements java.lang.AutoCloseable {
method public void close();
- method @FlaggedApi("com.android.net.flags.ipsec_transform_state") public void getIpSecTransformState(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.net.IpSecTransformState,java.lang.RuntimeException>);
+ method @FlaggedApi("com.android.net.flags.ipsec_transform_state") public void requestIpSecTransformState(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.net.IpSecTransformState,java.lang.RuntimeException>);
}
public static class IpSecTransform.Builder {
@@ -145,7 +145,7 @@
method public long getPacketCount();
method @NonNull public byte[] getReplayBitmap();
method public long getRxHighestSequenceNumber();
- method public long getTimestamp();
+ method public long getTimestampMillis();
method public long getTxHighestSequenceNumber();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.net.IpSecTransformState> CREATOR;
@@ -158,7 +158,7 @@
method @NonNull public android.net.IpSecTransformState.Builder setPacketCount(long);
method @NonNull public android.net.IpSecTransformState.Builder setReplayBitmap(@NonNull byte[]);
method @NonNull public android.net.IpSecTransformState.Builder setRxHighestSequenceNumber(long);
- method @NonNull public android.net.IpSecTransformState.Builder setTimestamp(long);
+ method @NonNull public android.net.IpSecTransformState.Builder setTimestampMillis(long);
method @NonNull public android.net.IpSecTransformState.Builder setTxHighestSequenceNumber(long);
}
diff --git a/framework-t/api/system-current.txt b/framework-t/api/system-current.txt
index 8251f85..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 {
diff --git a/framework-t/src/android/net/IpSecTransform.java b/framework-t/src/android/net/IpSecTransform.java
index 246a2dd..4e10a96 100644
--- a/framework-t/src/android/net/IpSecTransform.java
+++ b/framework-t/src/android/net/IpSecTransform.java
@@ -215,7 +215,7 @@
* @see IpSecTransformState
*/
@FlaggedApi(IPSEC_TRANSFORM_STATE)
- public void getIpSecTransformState(
+ public void requestIpSecTransformState(
@CallbackExecutor @NonNull Executor executor,
@NonNull OutcomeReceiver<IpSecTransformState, RuntimeException> callback) {
Objects.requireNonNull(executor);
diff --git a/framework-t/src/android/net/IpSecTransformState.java b/framework-t/src/android/net/IpSecTransformState.java
index b575dd5..5b80ae2 100644
--- a/framework-t/src/android/net/IpSecTransformState.java
+++ b/framework-t/src/android/net/IpSecTransformState.java
@@ -23,6 +23,7 @@
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.SystemClock;
import com.android.internal.annotations.VisibleForTesting;
import com.android.net.module.util.HexDump;
@@ -40,7 +41,7 @@
*/
@FlaggedApi(IPSEC_TRANSFORM_STATE)
public final class IpSecTransformState implements Parcelable {
- private final long mTimeStamp;
+ private final long mTimestamp;
private final long mTxHighestSequenceNumber;
private final long mRxHighestSequenceNumber;
private final long mPacketCount;
@@ -54,7 +55,7 @@
long packetCount,
long byteCount,
byte[] replayBitmap) {
- mTimeStamp = timestamp;
+ mTimestamp = timestamp;
mTxHighestSequenceNumber = txHighestSequenceNumber;
mRxHighestSequenceNumber = rxHighestSequenceNumber;
mPacketCount = packetCount;
@@ -78,7 +79,7 @@
@VisibleForTesting(visibility = Visibility.PRIVATE)
public IpSecTransformState(@NonNull Parcel in) {
Objects.requireNonNull(in, "The input PersistableBundle is null");
- mTimeStamp = in.readLong();
+ mTimestamp = in.readLong();
mTxHighestSequenceNumber = in.readLong();
mRxHighestSequenceNumber = in.readLong();
mPacketCount = in.readLong();
@@ -97,7 +98,7 @@
@Override
public void writeToParcel(@NonNull Parcel out, int flags) {
- out.writeLong(mTimeStamp);
+ out.writeLong(mTimestamp);
out.writeLong(mTxHighestSequenceNumber);
out.writeLong(mRxHighestSequenceNumber);
out.writeLong(mPacketCount);
@@ -120,16 +121,17 @@
};
/**
- * Retrieve the epoch timestamp (milliseconds) for when this state was created
+ * Retrieve the timestamp (milliseconds) when this state was created, as per {@link
+ * SystemClock#elapsedRealtime}
*
- * @see Builder#setTimestamp(long)
+ * @see Builder#setTimestampMillis(long)
*/
- public long getTimestamp() {
- return mTimeStamp;
+ public long getTimestampMillis() {
+ return mTimestamp;
}
/**
- * Retrieve the highest sequence number sent so far
+ * Retrieve the highest sequence number sent so far as an unsigned long
*
* @see Builder#setTxHighestSequenceNumber(long)
*/
@@ -138,7 +140,7 @@
}
/**
- * Retrieve the highest sequence number received so far
+ * Retrieve the highest sequence number received so far as an unsigned long
*
* @see Builder#setRxHighestSequenceNumber(long)
*/
@@ -147,7 +149,10 @@
}
/**
- * Retrieve the number of packets received AND sent so far
+ * Retrieve the number of packets processed so far as an unsigned long.
+ *
+ * <p>The packet count direction (inbound or outbound) aligns with the direction in which the
+ * IpSecTransform is applied to.
*
* @see Builder#setPacketCount(long)
*/
@@ -156,7 +161,10 @@
}
/**
- * Retrieve the number of bytes received AND sent so far
+ * Retrieve the number of bytes processed so far as an unsigned long
+ *
+ * <p>The byte count direction (inbound or outbound) aligns with the direction in which the
+ * IpSecTransform is applied to.
*
* @see Builder#setByteCount(long)
*/
@@ -183,10 +191,15 @@
return mReplayBitmap.clone();
}
- /** Builder class for testing purposes */
+ /**
+ * Builder class for testing purposes
+ *
+ * <p>Except for testing, IPsec callers normally do not instantiate {@link IpSecTransformState}
+ * themselves but instead get a reference via {@link IpSecTransformState}
+ */
@FlaggedApi(IPSEC_TRANSFORM_STATE)
public static final class Builder {
- private long mTimeStamp;
+ private long mTimestamp;
private long mTxHighestSequenceNumber;
private long mRxHighestSequenceNumber;
private long mPacketCount;
@@ -194,22 +207,22 @@
private byte[] mReplayBitmap;
public Builder() {
- mTimeStamp = System.currentTimeMillis();
+ mTimestamp = SystemClock.elapsedRealtime();
}
/**
- * Set the epoch timestamp (milliseconds) for when this state was created
+ * Set the timestamp (milliseconds) when this state was created
*
- * @see IpSecTransformState#getTimestamp()
+ * @see IpSecTransformState#getTimestampMillis()
*/
@NonNull
- public Builder setTimestamp(long timeStamp) {
- mTimeStamp = timeStamp;
+ public Builder setTimestampMillis(long timestamp) {
+ mTimestamp = timestamp;
return this;
}
/**
- * Set the highest sequence number sent so far
+ * Set the highest sequence number sent so far as an unsigned long
*
* @see IpSecTransformState#getTxHighestSequenceNumber()
*/
@@ -220,7 +233,7 @@
}
/**
- * Set the highest sequence number received so far
+ * Set the highest sequence number received so far as an unsigned long
*
* @see IpSecTransformState#getRxHighestSequenceNumber()
*/
@@ -231,7 +244,7 @@
}
/**
- * Set the number of packets received AND sent so far
+ * Set the number of packets processed so far as an unsigned long
*
* @see IpSecTransformState#getPacketCount()
*/
@@ -242,7 +255,7 @@
}
/**
- * Set the number of bytes received AND sent so far
+ * Set the number of bytes processed so far as an unsigned long
*
* @see IpSecTransformState#getByteCount()
*/
@@ -271,7 +284,7 @@
@NonNull
public IpSecTransformState build() {
return new IpSecTransformState(
- mTimeStamp,
+ mTimestamp,
mTxHighestSequenceNumber,
mRxHighestSequenceNumber,
mPacketCount,
diff --git a/framework-t/src/android/net/nsd/NsdManager.java b/framework-t/src/android/net/nsd/NsdManager.java
index 27b4955..f6e1324 100644
--- a/framework-t/src/android/net/nsd/NsdManager.java
+++ b/framework-t/src/android/net/nsd/NsdManager.java
@@ -57,7 +57,6 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
-import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.regex.Matcher;
@@ -167,7 +166,28 @@
* 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.
@@ -180,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 + ")*)"
+ "$";
/**
diff --git a/framework/Android.bp b/framework/Android.bp
index 4dbe2db..f76bbe1 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -193,6 +193,9 @@
lint: {
baseline_filename: "lint-baseline.xml",
},
+ aconfig_declarations: [
+ "com.android.net.flags-aconfig",
+ ],
}
platform_compat_config {
diff --git a/framework/aidl-export/android/net/TetheringManager.aidl b/framework/aidl-export/android/net/TetheringManager.aidl
new file mode 100644
index 0000000..1235722
--- /dev/null
+++ b/framework/aidl-export/android/net/TetheringManager.aidl
@@ -0,0 +1,20 @@
+/**
+ *
+ * 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;
+
+parcelable TetheringManager.TetheringRequest;
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/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index 1ea1815..915ec52 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -74,6 +74,7 @@
import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.modules.utils.build.SdkLevel;
import libcore.net.event.NetworkEventDispatcher;
@@ -6278,9 +6279,13 @@
// Only the system server process and the network stack have access.
@FlaggedApi(Flags.SUPPORT_IS_UID_NETWORKING_BLOCKED)
@SystemApi(client = MODULE_LIBRARIES)
- @RequiresApi(Build.VERSION_CODES.TIRAMISU) // BPF maps were only mainlined in T
+ // Note b/326143935 kernel bug can trigger crash on some T device.
+ @RequiresApi(VERSION_CODES.UPSIDE_DOWN_CAKE)
@RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK)
public boolean isUidNetworkingBlocked(int uid, boolean isNetworkMetered) {
+ if (!SdkLevel.isAtLeastU()) {
+ Log.wtf(TAG, "isUidNetworkingBlocked is not supported on pre-U devices");
+ }
final BpfNetMapsReader reader = BpfNetMapsReader.getInstance();
// Note that before V, the data saver status in bpf is written by ConnectivityService
// when receiving {@link #ACTION_RESTRICT_BACKGROUND_CHANGED}. Thus,
diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java
index efae754..84a0d29 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,24 @@
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.
*
- * <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
+ * Local networks are networks where the device is not obtaining IP addresses from the
+ * network, but advertising IP addresses itself. Examples of local networks are:
+ * <ul>
+ * <li>USB tethering or Wi-Fi hotspot networks to which the device is sharing its Internet
+ * connectivity.
+ * <li>Thread networks where the current device is the Thread Border Router.
+ * <li>Wi-Fi P2P networks where the current device is the Group Owner.
+ * </ul>
+ *
+ * Networks used to obtain Internet access are never local networks.
+ *
+ * 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 +1268,7 @@
TRANSPORT_TEST,
TRANSPORT_USB,
TRANSPORT_THREAD,
+ TRANSPORT_SATELLITE,
})
public @interface Transport { }
@@ -1313,10 +1325,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 +1361,7 @@
"TEST",
"USB",
"THREAD",
+ "SATELLITE",
};
/**
@@ -1751,9 +1770,13 @@
public @NonNull NetworkCapabilities setNetworkSpecifier(
@NonNull NetworkSpecifier networkSpecifier) {
if (networkSpecifier != null
- // Transport can be test, or test + a single other transport
+ // Transport can be test, or test + a single other transport or cellular + satellite
+ // transport. Note: cellular + satellite combination is allowed since both transport
+ // use the same specifier, TelephonyNetworkSpecifier.
&& mTransportTypes != (1L << TRANSPORT_TEST)
- && Long.bitCount(mTransportTypes & ~(1L << TRANSPORT_TEST)) != 1) {
+ && Long.bitCount(mTransportTypes & ~(1L << TRANSPORT_TEST)) != 1
+ && (mTransportTypes & ~(1L << TRANSPORT_TEST))
+ != (1 << TRANSPORT_CELLULAR | 1 << TRANSPORT_SATELLITE)) {
throw new IllegalStateException("Must have a single non-test transport specified to "
+ "use setNetworkSpecifier");
}
@@ -2794,10 +2817,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/framework/Android.bp b/nearby/framework/Android.bp
index 0fd9a89..4be102c 100644
--- a/nearby/framework/Android.bp
+++ b/nearby/framework/Android.bp
@@ -50,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/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/tests/cts/fastpair/Android.bp b/nearby/tests/cts/fastpair/Android.bp
index aa2806d..8009303 100644
--- a/nearby/tests/cts/fastpair/Android.bp
+++ b/nearby/tests/cts/fastpair/Android.bp
@@ -34,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/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/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/netbpfload/Android.bp b/netbpfload/Android.bp
index b5e4722..b71890e 100644
--- a/netbpfload/Android.bp
+++ b/netbpfload/Android.bp
@@ -44,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).
@@ -54,15 +54,14 @@
required: ["bpfloader"],
}
-// Versioned netbpfload init rc: init system will process it only on api V/35+ devices
-// (TODO: consider reducing to T/33+ - adjust the comment up above in line 43 as well)
-// Note: S[31] Sv2[32] T[33] U[34] V[35])
+// 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.35rc",
+ filename: "netbpfload.33rc",
installable: false,
}
diff --git a/netbpfload/NetBpfLoad.cpp b/netbpfload/NetBpfLoad.cpp
index cbd14ec..9dc7cdc 100644
--- a/netbpfload/NetBpfLoad.cpp
+++ b/netbpfload/NetBpfLoad.cpp
@@ -169,6 +169,63 @@
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);
@@ -176,25 +233,61 @@
ALOGI("NetBpfLoad '%s' starting...", argv[0]);
// true iff we are running from the module
- const bool is_mainline = !strcmp(argv[0], "/apex/com.android.tethering/bin/netbpfload");
+ const bool is_mainline = !strcmp(argv[0], apexNetBpfLoad);
// true iff we are running from the platform
- const bool is_platform = !strcmp(argv[0], "/system/bin/netbpfload");
+ 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__);
- ALOGI("NetBpfLoad api:%d/%d kver:%07x platform:%d mainline:%d",
+ // 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);
+ 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) {
+ ALOGI("Executing apex netbpfload...");
+ const char * args[] = { apexNetBpfLoad, NULL, };
+ execve(args[0], (char**)args, envp);
+ ALOGE("exec '%s' fail: %d[%s]", apexNetBpfLoad, errno, strerror(errno));
+ return 1;
+ }
+
+ 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;
@@ -210,6 +303,11 @@
return 1;
}
+ if (isAtLeastV && android::bpf::isX86() && !android::bpf::isKernel64Bit()) {
+ ALOGE("Android V requires X86 kernel to be 64-bit.");
+ return 1;
+ }
+
if (android::bpf::isUserspace32bit() && android::bpf::isAtLeastKernelVersion(6, 2, 0)) {
/* Android 14/U should only launch on 64-bit kernels
* T launches on 5.10/5.15
@@ -295,10 +393,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/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index 34927a6..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;
@@ -1760,7 +1760,7 @@
/** 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
@@ -1880,13 +1880,6 @@
}
/**
- * @see DeviceConfigUtils#isTrunkStableFeatureEnabled
- */
- public boolean isTrunkStableFeatureEnabled(String feature) {
- return DeviceConfigUtils.isTrunkStableFeatureEnabled(feature);
- }
-
- /**
* @see MdnsDiscoveryManager
*/
public MdnsDiscoveryManager makeMdnsDiscoveryManager(
@@ -2623,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;
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 fe9bbba..56202fd 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
@@ -118,6 +118,14 @@
}
/**
+ * Indicates whether {@link #NSD_KNOWN_ANSWER_SUPPRESSION} is enabled, including for testing.
+ */
+ public boolean isKnownAnswerSuppressionEnabled() {
+ return mIsKnownAnswerSuppressionEnabled
+ || isForceEnabledForTest(NSD_KNOWN_ANSWER_SUPPRESSION);
+ }
+
+ /**
* The constructor for {@link MdnsFeatureFlags}.
*/
public MdnsFeatureFlags(boolean isOffloadFeatureEnabled,
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 fb45454..ed0bde2 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
@@ -388,7 +388,8 @@
"Service ID must not be reused across registrations: " + serviceId);
}
- final int existing = getServiceByName(serviceInfo.getServiceName());
+ final int existing =
+ getServiceByNameAndType(serviceInfo.getServiceName(), serviceInfo.getServiceType());
// It's OK to re-add a service that is exiting
if (existing >= 0 && !mServices.get(existing).exiting) {
throw new NameConflictException(existing);
@@ -405,16 +406,17 @@
}
/**
- * @return The ID of the service identified by its name, or -1 if none.
+ * @return The ID of the service identified by its name and type, or -1 if none.
*/
- private int getServiceByName(@Nullable String serviceName) {
- if (TextUtils.isEmpty(serviceName)) {
+ private int getServiceByNameAndType(
+ @Nullable String serviceName, @Nullable String serviceType) {
+ if (TextUtils.isEmpty(serviceName) || TextUtils.isEmpty(serviceType)) {
return -1;
}
for (int i = 0; i < mServices.size(); i++) {
- final ServiceRegistration registration = mServices.valueAt(i);
- if (MdnsUtils.equalsIgnoreDnsCase(
- serviceName, registration.serviceInfo.getServiceName())) {
+ final NsdServiceInfo info = mServices.valueAt(i).serviceInfo;
+ if (MdnsUtils.equalsIgnoreDnsCase(serviceName, info.getServiceName())
+ && MdnsUtils.equalsIgnoreDnsCase(serviceType, info.getServiceType())) {
return mServices.keyAt(i);
}
}
@@ -536,7 +538,7 @@
}
private boolean isTruncatedKnownAnswerPacket(MdnsPacket packet) {
- if (!mMdnsFeatureFlags.mIsKnownAnswerSuppressionEnabled
+ if (!mMdnsFeatureFlags.isKnownAnswerSuppressionEnabled()
// Should ignore the response packet.
|| (packet.flags & MdnsConstants.FLAGS_RESPONSE) != 0) {
return false;
@@ -743,7 +745,7 @@
// RR TTL as known by the Multicast DNS responder, the responder MUST
// send an answer so as to update the querier's cache before the record
// becomes in danger of expiration.
- if (mMdnsFeatureFlags.mIsKnownAnswerSuppressionEnabled
+ if (mMdnsFeatureFlags.isKnownAnswerSuppressionEnabled()
&& isKnownAnswer(info.record, knownAnswerRecords)) {
continue;
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java b/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java
index a46be3b..db3845a 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java
@@ -145,7 +145,7 @@
public void queueReply(@NonNull MdnsReplyInfo reply) {
ensureRunningOnHandlerThread(mHandler);
- if (mMdnsFeatureFlags.mIsKnownAnswerSuppressionEnabled) {
+ if (mMdnsFeatureFlags.isKnownAnswerSuppressionEnabled()) {
mDependencies.removeMessages(mHandler, MSG_SEND, reply.source);
final MdnsReplyInfo queuingReply = mSrcReplies.remove(reply.source);
@@ -231,7 +231,7 @@
@Override
public void handleMessage(@NonNull Message msg) {
final MdnsReplyInfo replyInfo;
- if (mMdnsFeatureFlags.mIsKnownAnswerSuppressionEnabled) {
+ if (mMdnsFeatureFlags.isKnownAnswerSuppressionEnabled()) {
// Retrieve the MdnsReplyInfo from the map via a source address, as the reply info
// will be combined or updated.
final InetSocketAddress source = (InetSocketAddress) msg.obj;
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 4cb88b4..e222fcf 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
@@ -140,8 +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 */);
+ getAllDiscoverySubtypes(), needSendDiscoveryQueries(listeners));
executor.submit(queryTask);
break;
}
@@ -388,8 +387,7 @@
final QueryTask queryTask = new QueryTask(
mdnsQueryScheduler.scheduleFirstRun(taskConfig, now,
minRemainingTtl, currentSessionId), servicesToResolve,
- getAllDiscoverySubtypes(),
- servicesToResolve.size() < listeners.size() /* sendDiscoveryQueries */);
+ getAllDiscoverySubtypes(), needSendDiscoveryQueries(listeners));
executor.submit(queryTask);
}
@@ -627,6 +625,10 @@
if (resolveName == null) {
continue;
}
+ if (CollectionUtils.any(resolveResponses,
+ r -> MdnsUtils.equalsIgnoreDnsCase(resolveName, r.getServiceInstanceName()))) {
+ continue;
+ }
MdnsResponse knownResponse =
serviceCache.getCachedService(resolveName, cacheKey);
if (knownResponse == null) {
@@ -643,6 +645,17 @@
return resolveResponses;
}
+ private static boolean needSendDiscoveryQueries(
+ @NonNull ArrayMap<MdnsServiceBrowserListener, ListenerInfo> listeners) {
+ // Note iterators are discouraged on ArrayMap as per its documentation
+ for (int i = 0; i < listeners.size(); i++) {
+ if (listeners.valueAt(i).searchOptions.getResolveInstanceName() == null) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private void tryRemoveServiceAfterTtlExpires() {
if (!shouldRemoveServiceAfterTtlExpires()) return;
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index 80c4033..9684d18 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -2231,7 +2231,7 @@
.setDefaultNetwork(true)
.setOemManaged(ident.getOemManaged())
.setSubId(ident.getSubId()).build();
- final String ifaceVt = IFACE_VT + getSubIdForMobile(snapshot);
+ final String ifaceVt = IFACE_VT + getSubIdForCellularOrSatellite(snapshot);
findOrCreateNetworkIdentitySet(mActiveIfaces, ifaceVt).add(vtIdent);
findOrCreateNetworkIdentitySet(mActiveUidIfaces, ifaceVt).add(vtIdent);
}
@@ -2300,9 +2300,15 @@
mMobileIfaces = mobileIfaces.toArray(new String[0]);
}
- private static int getSubIdForMobile(@NonNull NetworkStateSnapshot state) {
- if (!state.getNetworkCapabilities().hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
- throw new IllegalArgumentException("Mobile state need capability TRANSPORT_CELLULAR");
+ private static int getSubIdForCellularOrSatellite(@NonNull NetworkStateSnapshot state) {
+ if (!state.getNetworkCapabilities().hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
+ // Both cellular and satellite are 2 different network transport at Mobile using
+ // same telephony network specifier. So adding satellite transport to consider
+ // for, when satellite network is active at mobile.
+ && !state.getNetworkCapabilities().hasTransport(
+ NetworkCapabilities.TRANSPORT_SATELLITE)) {
+ throw new IllegalArgumentException(
+ "Mobile state need capability TRANSPORT_CELLULAR or TRANSPORT_SATELLITE");
}
final NetworkSpecifier spec = state.getNetworkCapabilities().getNetworkSpecifier();
diff --git a/service-t/src/com/android/server/net/TrafficStatsRateLimitCache.java b/service-t/src/com/android/server/net/TrafficStatsRateLimitCache.java
new file mode 100644
index 0000000..8598ac4
--- /dev/null
+++ b/service-t/src/com/android/server/net/TrafficStatsRateLimitCache.java
@@ -0,0 +1,134 @@
+/*
+ * 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.net;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.NetworkStats;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.time.Clock;
+import java.util.HashMap;
+import java.util.Objects;
+
+/**
+ * A thread-safe cache for storing and retrieving {@link NetworkStats.Entry} objects,
+ * with an adjustable expiry duration to manage data freshness.
+ */
+class TrafficStatsRateLimitCache {
+ private final Clock mClock;
+ private final long mExpiryDurationMs;
+
+ /**
+ * Constructs a new {@link TrafficStatsRateLimitCache} with the specified expiry duration.
+ *
+ * @param clock The {@link Clock} to use for determining timestamps.
+ * @param expiryDurationMs The expiry duration in milliseconds.
+ */
+ TrafficStatsRateLimitCache(@NonNull Clock clock, long expiryDurationMs) {
+ mClock = clock;
+ mExpiryDurationMs = expiryDurationMs;
+ }
+
+ private static class TrafficStatsCacheKey {
+ @Nullable
+ public final String iface;
+ public final int uid;
+
+ TrafficStatsCacheKey(@Nullable String iface, int uid) {
+ this.iface = iface;
+ this.uid = uid;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof TrafficStatsCacheKey)) return false;
+ TrafficStatsCacheKey that = (TrafficStatsCacheKey) o;
+ return uid == that.uid && Objects.equals(iface, that.iface);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(iface, uid);
+ }
+ }
+
+ private static class TrafficStatsCacheValue {
+ public final long timestamp;
+ @NonNull
+ public final NetworkStats.Entry entry;
+
+ TrafficStatsCacheValue(long timestamp, NetworkStats.Entry entry) {
+ this.timestamp = timestamp;
+ this.entry = entry;
+ }
+ }
+
+ @GuardedBy("mMap")
+ private final HashMap<TrafficStatsCacheKey, TrafficStatsCacheValue> mMap = new HashMap<>();
+
+ /**
+ * Retrieves a {@link NetworkStats.Entry} from the cache, associated with the given key.
+ *
+ * @param iface The interface name to include in the cache key. Null if not applicable.
+ * @param uid The UID to include in the cache key. {@code UID_ALL} if not applicable.
+ * @return The cached {@link NetworkStats.Entry}, or null if not found or expired.
+ */
+ @Nullable
+ NetworkStats.Entry get(String iface, int uid) {
+ final TrafficStatsCacheKey key = new TrafficStatsCacheKey(iface, uid);
+ synchronized (mMap) { // Synchronize for thread-safety
+ final TrafficStatsCacheValue value = mMap.get(key);
+ if (value != null && !isExpired(value.timestamp)) {
+ return value.entry;
+ } else {
+ mMap.remove(key); // Remove expired entries
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Stores a {@link NetworkStats.Entry} in the cache, associated with the given key.
+ *
+ * @param iface The interface name to include in the cache key. Null if not applicable.
+ * @param uid The UID to include in the cache key. {@code UID_ALL} if not applicable.
+ * @param entry The {@link NetworkStats.Entry} to store in the cache.
+ */
+ void put(String iface, int uid, @NonNull final NetworkStats.Entry entry) {
+ Objects.requireNonNull(entry);
+ final TrafficStatsCacheKey key = new TrafficStatsCacheKey(iface, uid);
+ synchronized (mMap) { // Synchronize for thread-safety
+ mMap.put(key, new TrafficStatsCacheValue(mClock.millis(), entry));
+ }
+ }
+
+ /**
+ * Clear the cache.
+ */
+ void clear() {
+ synchronized (mMap) {
+ mMap.clear();
+ }
+ }
+
+ private boolean isExpired(long timestamp) {
+ return mClock.millis() > timestamp + mExpiryDurationMs;
+ }
+}
diff --git a/service/Android.bp b/service/Android.bp
index 89c83bd..c35c4f8 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -179,6 +179,8 @@
"unsupportedappusage",
"ServiceConnectivityResources",
"framework-statsd",
+ "framework-permission",
+ "framework-permission-s",
],
static_libs: [
// Do not add libs here if they are already included
@@ -186,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",
@@ -266,6 +268,8 @@
"framework-tethering.impl",
"framework-wifi",
"libprotobuf-java-nano",
+ "framework-permission",
+ "framework-permission-s",
],
jarjar_rules: ":connectivity-jarjar-rules",
apex_available: [
diff --git a/service/ServiceConnectivityResources/res/values/config_thread.xml b/service/ServiceConnectivityResources/res/values/config_thread.xml
index 14b5427..f7e47f5 100644
--- a/service/ServiceConnectivityResources/res/values/config_thread.xml
+++ b/service/ServiceConnectivityResources/res/values/config_thread.xml
@@ -20,10 +20,15 @@
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Sets to {@code true} to enable Thread on the device by default. Note this is the default
+ value, the actual Thread enabled state can be changed by the {@link
+ ThreadNetworkController#setEnabled} API.
+ -->
+ <bool name="config_thread_default_enabled">true</bool>
+
<!-- Whether to use location APIs in the algorithm to determine country code or not.
If disabled, will use other sources (telephony, wifi, etc) to determine device location for
Thread Network regulatory purposes.
-->
<bool name="config_thread_location_use_for_country_code_enabled">true</bool>
-
</resources>
diff --git a/service/ServiceConnectivityResources/res/values/overlayable.xml b/service/ServiceConnectivityResources/res/values/overlayable.xml
index f2c4d91..d9af5a3 100644
--- a/service/ServiceConnectivityResources/res/values/overlayable.xml
+++ b/service/ServiceConnectivityResources/res/values/overlayable.xml
@@ -46,6 +46,7 @@
<item type="integer" name="config_netstats_validate_import" />
<!-- Configuration values for ThreadNetworkService -->
+ <item type="bool" name="config_thread_default_enabled" />
<item type="bool" name="config_thread_location_use_for_country_code_enabled" />
</policy>
</overlayable>
diff --git a/service/lint-baseline.xml b/service/lint-baseline.xml
index b09589c..3e11d52 100644
--- a/service/lint-baseline.xml
+++ b/service/lint-baseline.xml
@@ -3,6 +3,17 @@
<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=" ~~~~~~~~~~~~~">
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 52f890d..6839c22 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;
@@ -113,6 +114,7 @@
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 com.android.server.connectivity.ConnectivityFlags.REQUEST_RESTRICTED_WIFI;
import android.Manifest;
import android.annotation.CheckResult;
@@ -254,6 +256,7 @@
import android.stats.connectivity.ValidatedState;
import android.sysprop.NetworkProperties;
import android.system.ErrnoException;
+import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -336,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;
@@ -373,6 +377,8 @@
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
/**
* @hide
@@ -467,6 +473,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.
@@ -562,6 +570,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
@@ -832,6 +844,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.
*/
@@ -920,6 +937,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;
@@ -1270,6 +1288,14 @@
}
private final LegacyTypeTracker mLegacyTypeTracker = new LegacyTypeTracker(this);
+ @VisibleForTesting
+ void onCarrierPrivilegesLost(Integer uid, Integer subId) {
+ if (mRequestRestrictedWifiEnabled) {
+ mHandler.sendMessage(mHandler.obtainMessage(
+ EVENT_UID_CARRIER_PRIVILEGES_LOST, uid, subId));
+ }
+ }
+
final LocalPriorityDump mPriorityDumper = new LocalPriorityDump();
/**
* Helper class which parses out priority arguments and dumps sections according to their
@@ -1488,15 +1514,31 @@
*/
@Nullable
public CarrierPrivilegeAuthenticator makeCarrierPrivilegeAuthenticator(
- @NonNull final Context context, @NonNull final TelephonyManager tm) {
+ @NonNull final Context context,
+ @NonNull final TelephonyManager tm,
+ boolean requestRestrictedWifiEnabled,
+ @NonNull BiConsumer<Integer, Integer> 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) {
@@ -1759,8 +1801,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,
+ this::onCarrierPrivilegesLost);
+
+ 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
@@ -1894,8 +1948,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(
@@ -2030,6 +2084,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(
@@ -3372,6 +3438,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,
@@ -3738,6 +3807,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);
@@ -5320,6 +5393,13 @@
return false;
}
+ private int getSubscriptionIdFromNetworkCaps(@NonNull final NetworkCapabilities caps) {
+ if (mCarrierPrivilegeAuthenticator != null) {
+ return mCarrierPrivilegeAuthenticator.getSubIdFromNetworkCapabilities(caps);
+ }
+ return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ }
+
private void handleRegisterNetworkRequestWithIntent(@NonNull final Message msg) {
final NetworkRequestInfo nri = (NetworkRequestInfo) (msg.obj);
// handleRegisterNetworkRequestWithIntent() doesn't apply to multilayer requests.
@@ -5929,7 +6009,7 @@
if (nm == null) return;
if (request == CaptivePortal.APP_REQUEST_REEVALUATION_REQUIRED) {
- hasNetworkStackPermission();
+ enforceNetworkStackPermission(mContext);
nm.forceReevaluation(mDeps.getCallingUid());
}
}
@@ -6410,6 +6490,9 @@
UidFrozenStateChangedArgs args = (UidFrozenStateChangedArgs) msg.obj;
handleFrozenUids(args.mUids, args.mFrozenStates);
break;
+ case EVENT_UID_CARRIER_PRIVILEGES_LOST:
+ handleUidCarrierPrivilegesLost(msg.arg1, msg.arg2);
+ break;
}
}
}
@@ -7490,9 +7573,14 @@
}
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) {
@@ -7772,6 +7860,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,
@@ -7779,13 +7883,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);
}
@@ -9052,6 +9154,40 @@
}
}
+ private void handleUidCarrierPrivilegesLost(int uid, int subId) {
+ 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
+ && getSubscriptionIdFromNetworkCaps(nr.networkCapabilities) == subId
+ && !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)
+ && getSubscriptionIdFromNetworkCaps(nai.networkCapabilities) == subId) {
+ 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:
*
@@ -9499,7 +9635,6 @@
final ArraySet<Integer> toAdd = new ArraySet<>(newUids);
toRemove.removeAll(newUids);
toAdd.removeAll(prevUids);
-
try {
if (!toAdd.isEmpty()) {
mNetd.networkAddUidRangesParcel(new NativeUidRangeConfig(
@@ -11145,17 +11280,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) {
@@ -11232,6 +11378,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
@@ -11292,6 +11470,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.");
}
}
@@ -12673,16 +12855,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.
@@ -12691,14 +12884,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() {
@@ -12710,6 +12917,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..04d0fc1 100644
--- a/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
+++ b/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
@@ -40,12 +40,13 @@
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.Log;
-import android.util.SparseIntArray;
+import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import com.android.modules.utils.HandlerExecutor;
+import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.DeviceConfigUtils;
import com.android.networkstack.apishim.TelephonyManagerShimImpl;
import com.android.networkstack.apishim.common.TelephonyManagerShim;
@@ -55,6 +56,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
+import java.util.function.BiConsumer;
/**
* Tracks the uid of the carrier privileged app that provides the carrier config.
@@ -71,7 +73,8 @@
private final TelephonyManagerShim mTelephonyManagerShim;
private final TelephonyManager mTelephonyManager;
@GuardedBy("mLock")
- private final SparseIntArray mCarrierServiceUid = new SparseIntArray(2 /* initialCapacity */);
+ private final SparseArray<CarrierServiceUidWithSubId> mCarrierServiceUidWithSubId =
+ new SparseArray<>(2 /* initialCapacity */);
@GuardedBy("mLock")
private int mModemCount = 0;
private final Object mLock = new Object();
@@ -79,11 +82,16 @@
@NonNull
private final List<PrivilegeListener> mCarrierPrivilegesChangedListeners = new ArrayList<>();
private final boolean mUseCallbacksForServiceChanged;
+ private final boolean mRequestRestrictedWifiEnabled;
+ @NonNull
+ private final BiConsumer<Integer, Integer> 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 BiConsumer<Integer, Integer> listener) {
mContext = c;
mTelephonyManager = t;
mTelephonyManagerShim = telephonyManagerShim;
@@ -92,6 +100,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 +123,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 BiConsumer<Integer, Integer> listener) {
+ this(c, new Dependencies(), t, TelephonyManagerShimImpl.newInstance(t),
+ requestRestrictedWifiEnabled, listener);
}
public static class Dependencies {
@@ -142,6 +154,29 @@
}
}
+ private static class CarrierServiceUidWithSubId {
+ final int mUid;
+ final int mSubId;
+
+ CarrierServiceUidWithSubId(int uid, int subId) {
+ mUid = uid;
+ mSubId = subId;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof CarrierServiceUidWithSubId)) {
+ return false;
+ }
+ CarrierServiceUidWithSubId compare = (CarrierServiceUidWithSubId) obj;
+ return (mUid == compare.mUid && mSubId == compare.mSubId);
+ }
+
+ @Override
+ public int hashCode() {
+ return mUid * 31 + mSubId;
+ }
+ }
private class PrivilegeListener implements CarrierPrivilegesListenerShim {
public final int mLogicalSlot;
@@ -171,7 +206,18 @@
return;
}
synchronized (mLock) {
- mCarrierServiceUid.put(mLogicalSlot, carrierServiceUid);
+ CarrierServiceUidWithSubId oldPair =
+ mCarrierServiceUidWithSubId.get(mLogicalSlot);
+ int subId = getSubId(mLogicalSlot);
+ mCarrierServiceUidWithSubId.put(
+ mLogicalSlot,
+ new CarrierServiceUidWithSubId(carrierServiceUid, subId));
+ if (oldPair != null
+ && oldPair.mUid != Process.INVALID_UID
+ && oldPair.mSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID
+ && !oldPair.equals(mCarrierServiceUidWithSubId.get(mLogicalSlot))) {
+ mListener.accept(oldPair.mUid, oldPair.mSubId);
+ }
}
}
}
@@ -193,7 +239,14 @@
private void unregisterCarrierPrivilegesListeners() {
for (PrivilegeListener carrierPrivilegesListener : mCarrierPrivilegesChangedListeners) {
removeCarrierPrivilegesListener(carrierPrivilegesListener);
- mCarrierServiceUid.delete(carrierPrivilegesListener.mLogicalSlot);
+ CarrierServiceUidWithSubId oldPair =
+ mCarrierServiceUidWithSubId.get(carrierPrivilegesListener.mLogicalSlot);
+ mCarrierServiceUidWithSubId.remove(carrierPrivilegesListener.mLogicalSlot);
+ if (oldPair != null
+ && oldPair.mUid != Process.INVALID_UID
+ && oldPair.mSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ mListener.accept(oldPair.mUid, oldPair.mSubId);
+ }
}
mCarrierPrivilegesChangedListeners.clear();
}
@@ -230,8 +283,24 @@
*/
public boolean isCarrierServiceUidForNetworkCapabilities(int callingUid,
@NonNull NetworkCapabilities networkCapabilities) {
- if (callingUid == Process.INVALID_UID) return false;
- final int subId;
+ if (callingUid == Process.INVALID_UID) {
+ return false;
+ }
+ int subId = getSubIdFromNetworkCapabilities(networkCapabilities);
+ if (SubscriptionManager.INVALID_SUBSCRIPTION_ID == subId) {
+ return false;
+ }
+ return callingUid == getCarrierServiceUidForSubId(subId);
+ }
+
+ /**
+ * Extract the SubscriptionId from the NetworkCapabilities.
+ *
+ * @param networkCapabilities the network capabilities which may contains the SubscriptionId.
+ * @return the SubscriptionId.
+ */
+ public int getSubIdFromNetworkCapabilities(@NonNull NetworkCapabilities networkCapabilities) {
+ int subId;
if (networkCapabilities.hasSingleTransportBesidesTest(TRANSPORT_CELLULAR)) {
subId = getSubIdFromTelephonySpecifier(networkCapabilities.getNetworkSpecifier());
} else if (networkCapabilities.hasSingleTransportBesidesTest(TRANSPORT_WIFI)) {
@@ -239,6 +308,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()
@@ -250,34 +325,60 @@
Log.wtf(TAG, "NetworkCapabilities subIds are inconsistent between "
+ "specifier/transportInfo and mSubIds : " + networkCapabilities);
}
- if (SubscriptionManager.INVALID_SUBSCRIPTION_ID == subId) return false;
- return callingUid == getCarrierServiceUidForSubId(subId);
+ return subId;
+ }
+
+ @VisibleForTesting
+ protected int getSubId(int slotIndex) {
+ if (SdkLevel.isAtLeastU()) {
+ return SubscriptionManager.getSubscriptionId(slotIndex);
+ } else {
+ SubscriptionManager sm = mContext.getSystemService(SubscriptionManager.class);
+ int[] subIds = sm.getSubscriptionIds(slotIndex);
+ if (subIds != null && subIds.length > 0) {
+ return subIds[0];
+ }
+ return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ }
}
@VisibleForTesting
void updateCarrierServiceUid() {
synchronized (mLock) {
- mCarrierServiceUid.clear();
+ SparseArray<CarrierServiceUidWithSubId> copy = mCarrierServiceUidWithSubId.clone();
+ mCarrierServiceUidWithSubId.clear();
for (int i = 0; i < mModemCount; i++) {
- mCarrierServiceUid.put(i, getCarrierServicePackageUidForSlot(i));
+ int subId = getSubId(i);
+ mCarrierServiceUidWithSubId.put(
+ i,
+ new CarrierServiceUidWithSubId(
+ getCarrierServicePackageUidForSlot(i), subId));
+ }
+ for (int i = 0; i < copy.size(); ++i) {
+ CarrierServiceUidWithSubId oldPair = copy.valueAt(i);
+ CarrierServiceUidWithSubId newPair = mCarrierServiceUidWithSubId.get(copy.keyAt(i));
+ if (oldPair.mUid != Process.INVALID_UID
+ && oldPair.mSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID
+ && !oldPair.equals(newPair)) {
+ mListener.accept(oldPair.mUid, oldPair.mSubId);
+ }
}
}
}
@VisibleForTesting
int getCarrierServiceUidForSubId(int subId) {
- final int slotId = getSlotIndex(subId);
synchronized (mLock) {
- return mCarrierServiceUid.get(slotId, Process.INVALID_UID);
+ for (int i = 0; i < mCarrierServiceUidWithSubId.size(); ++i) {
+ if (mCarrierServiceUidWithSubId.valueAt(i).mSubId == subId) {
+ return mCarrierServiceUidWithSubId.valueAt(i).mUid;
+ }
+ }
+ return Process.INVALID_UID;
}
}
@VisibleForTesting
- protected int getSlotIndex(int subId) {
- return SubscriptionManager.getSlotIndex(subId);
- }
-
- @VisibleForTesting
int getUidForPackage(String pkgName) {
if (pkgName == null) {
return Process.INVALID_UID;
@@ -340,12 +441,14 @@
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) {
- final int logicalSlot = mCarrierServiceUid.keyAt(i);
- final int serviceUid = mCarrierServiceUid.valueAt(i);
- pw.println("Logical slot = " + logicalSlot + " : uid = " + serviceUid);
+ for (int i = 0; i < mCarrierServiceUidWithSubId.size(); ++i) {
+ final int logicalSlot = mCarrierServiceUidWithSubId.keyAt(i);
+ final int serviceUid = mCarrierServiceUidWithSubId.valueAt(i).mUid;
+ final int subId = mCarrierServiceUidWithSubId.valueAt(i).mSubId;
+ pw.println("Logical slot = " + logicalSlot + " : uid = " + serviceUid
+ + " : subId = " + subId);
}
}
}
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/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..b53abce
--- /dev/null
+++ b/service/src/com/android/server/connectivity/SatelliteAccessController.java
@@ -0,0 +1,236 @@
+/*
+ * 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.os.UserManager;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.SparseArray;
+
+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 Context mContext;
+ private final Dependencies mDeps;
+ private final DefaultMessageRoleListener mDefaultMessageRoleListener;
+ private final UserManager mUserManager;
+ private final Consumer<Set<Integer>> mCallback;
+ private final Handler mConnectivityServiceHandler;
+
+ // At this sparseArray, Key is userId and values are uids of SMS apps that are allowed
+ // to use satellite network as fallback.
+ private final SparseArray<Set<Integer>> mAllUsersSatelliteNetworkFallbackUidCache =
+ new SparseArray<>();
+
+ /**
+ * Monitor {@link android.app.role.OnRoleHoldersChangedListener#onRoleHoldersChanged(String,
+ * UserHandle)},
+ *
+ */
+ private final class DefaultMessageRoleListener
+ implements OnRoleHoldersChangedListener {
+ @Override
+ public void onRoleHoldersChanged(String role, UserHandle userHandle) {
+ if (RoleManager.ROLE_SMS.equals(role)) {
+ Log.i(TAG, "ROLE_SMS Change detected ");
+ onRoleSmsChanged(userHandle);
+ }
+ }
+
+ public void register() {
+ try {
+ mDeps.addOnRoleHoldersChangedListenerAsUser(
+ mConnectivityServiceHandler::post, this, UserHandle.ALL);
+ } catch (RuntimeException e) {
+ Log.wtf(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#getRoleHoldersAsUser(String, UserHandle)} */
+ public List<String> getRoleHoldersAsUser(String roleName, UserHandle userHandle) {
+ return mRoleManager.getRoleHoldersAsUser(roleName, userHandle);
+ }
+
+ /** 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) {
+ mContext = c;
+ mDeps = deps;
+ mUserManager = mContext.getSystemService(UserManager.class);
+ mDefaultMessageRoleListener = new DefaultMessageRoleListener();
+ mCallback = callback;
+ mConnectivityServiceHandler = connectivityServiceInternalHandler;
+ }
+
+ private Set<Integer> updateSatelliteNetworkFallbackUidListCache(List<String> packageNames,
+ @NonNull UserHandle userHandle) {
+ Set<Integer> fallbackUids = new ArraySet<>();
+ PackageManager pm =
+ mContext.createContextAsUser(userHandle, 0).getPackageManager();
+ if (pm != null) {
+ for (String packageName : packageNames) {
+ // Check if SATELLITE_COMMUNICATION permission is enabled for default sms
+ // application package before adding it part of satellite network fallback uid
+ // cache list.
+ if (isSatellitePermissionEnabled(pm, packageName)) {
+ int uid = getUidForPackage(pm, packageName);
+ if (uid != Process.INVALID_UID) {
+ fallbackUids.add(uid);
+ }
+ }
+ }
+ } else {
+ Log.wtf(TAG, "package manager found null");
+ }
+ return fallbackUids;
+ }
+
+ //Check if satellite communication is enabled for the package
+ private boolean isSatellitePermissionEnabled(PackageManager packageManager,
+ String packageName) {
+ return packageManager.checkPermission(
+ Manifest.permission.SATELLITE_COMMUNICATION, packageName)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+ private int getUidForPackage(PackageManager packageManager, String pkgName) {
+ if (pkgName == null) {
+ return Process.INVALID_UID;
+ }
+ try {
+ ApplicationInfo applicationInfo = packageManager.getApplicationInfo(pkgName, 0);
+ 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()
+ // TODO(b/326373613): using UserLifecycleListener, callback to be received when user removed for
+ // user delete scenario. This to be used to update uid list and ML Layer request can also be
+ // updated.
+ private void onRoleSmsChanged(@NonNull UserHandle userHandle) {
+ int userId = userHandle.getIdentifier();
+ if (userId == Process.INVALID_UID) {
+ Log.wtf(TAG, "Invalid User Id");
+ return;
+ }
+
+ //Returns empty list if no package exists
+ final List<String> packageNames =
+ mDeps.getRoleHoldersAsUser(RoleManager.ROLE_SMS, userHandle);
+
+ // Store previous satellite fallback uid available
+ final Set<Integer> prevUidsForUser =
+ mAllUsersSatelliteNetworkFallbackUidCache.get(userId, new ArraySet<>());
+
+ Log.i(TAG, "currentUser : role_sms_packages: " + userId + " : " + packageNames);
+ final Set<Integer> newUidsForUser = !packageNames.isEmpty()
+ ? updateSatelliteNetworkFallbackUidListCache(packageNames, userHandle)
+ : new ArraySet<>();
+ Log.i(TAG, "satellite_fallback_uid: " + newUidsForUser);
+
+ // on Role change, update the multilayer request at ConnectivityService with updated
+ // satellite network fallback uid cache list of multiple users as applicable
+ if (newUidsForUser.equals(prevUidsForUser)) {
+ return;
+ }
+
+ mAllUsersSatelliteNetworkFallbackUidCache.put(userId, newUidsForUser);
+
+ // Merge all uids of multiple users available
+ Set<Integer> mergedSatelliteNetworkFallbackUidCache = new ArraySet<>();
+ for (int i = 0; i < mAllUsersSatelliteNetworkFallbackUidCache.size(); i++) {
+ mergedSatelliteNetworkFallbackUidCache.addAll(
+ mAllUsersSatelliteNetworkFallbackUidCache.valueAt(i));
+ }
+ Log.i(TAG, "merged uid list for multi layer request : "
+ + mergedSatelliteNetworkFallbackUidCache);
+
+ // trigger multiple layer request for satellite network fallback of multi user uids
+ mCallback.accept(mergedSatelliteNetworkFallbackUidCache);
+ }
+
+ private List<String> getRoleSmsChangedPackageName(UserHandle userHandle) {
+ try {
+ return mDeps.getRoleHoldersAsUser(RoleManager.ROLE_SMS, userHandle);
+ } 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::updateAllUserRoleSmsUids);
+ mDefaultMessageRoleListener.register();
+ }
+
+ private void updateAllUserRoleSmsUids() {
+ List<UserHandle> existingUsers = mUserManager.getUserHandles(true /* excludeDying */);
+ // Iterate through the user handles and obtain their uids with role sms and satellite
+ // communication permission
+ for (UserHandle userHandle : existingUsers) {
+ onRoleSmsChanged(userHandle);
+ }
+ }
+}
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index eaa7ea9..6790093 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -275,7 +275,7 @@
"//apex_available:platform",
],
lint: {
- strict_updatability_linting: true,
+ baseline_filename: "lint-baseline.xml",
error_checks: ["NewApi"],
},
}
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/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/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/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/tests/unit/Android.bp b/staticlibs/tests/unit/Android.bp
index d203bc0..4c226cc 100644
--- a/staticlibs/tests/unit/Android.bp
+++ b/staticlibs/tests/unit/Android.bp
@@ -38,7 +38,6 @@
"//packages/modules/NetworkStack/tests/integration",
],
lint: {
- strict_updatability_linting: true,
test: true,
},
}
@@ -56,7 +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/testutils/devicetests/com/android/testutils/ExternalPacketForwarder.kt b/staticlibs/testutils/devicetests/com/android/testutils/ExternalPacketForwarder.kt
new file mode 100644
index 0000000..36eb795
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/ExternalPacketForwarder.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.testutils
+
+import java.io.FileDescriptor
+
+class ExternalPacketForwarder(
+ srcFd: FileDescriptor,
+ mtu: Int,
+ dstFd: FileDescriptor,
+ forwardMap: Map<Int, Int>
+) : PacketForwarderBase(srcFd, mtu, dstFd, forwardMap) {
+
+ /**
+ * Prepares a packet for forwarding by potentially updating the
+ * source port based on the specified port remapping rules.
+ *
+ * @param buf The packet data as a byte array.
+ * @param version The IP version of the packet (e.g., 4 for IPv4).
+ */
+ override fun remapPort(buf: ByteArray, version: Int) {
+ val transportOffset = getTransportOffset(version)
+ val intPort = getRemappedPort(buf, transportOffset)
+
+ // Copy remapped source port.
+ if (intPort != 0) {
+ setPortAt(intPort, buf, transportOffset)
+ }
+ }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/InternalPacketForwarder.kt b/staticlibs/testutils/devicetests/com/android/testutils/InternalPacketForwarder.kt
new file mode 100644
index 0000000..58829dc
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/InternalPacketForwarder.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.testutils
+
+import java.io.FileDescriptor
+
+class InternalPacketForwarder(
+ srcFd: FileDescriptor,
+ mtu: Int,
+ dstFd: FileDescriptor,
+ forwardMap: Map<Int, Int>
+) : PacketForwarderBase(srcFd, mtu, dstFd, forwardMap) {
+ /**
+ * Prepares a packet for forwarding by potentially updating the
+ * destination port based on the specified port remapping rules.
+ *
+ * @param buf The packet data as a byte array.
+ * @param version The IP version of the packet (e.g., 4 for IPv4).
+ */
+ override fun remapPort(buf: ByteArray, version: Int) {
+ val transportOffset = getTransportOffset(version) + DESTINATION_PORT_OFFSET
+ val extPort = getRemappedPort(buf, transportOffset)
+
+ // Copy remapped destination port.
+ if (extPort != 0) {
+ setPortAt(extPort, buf, transportOffset)
+ }
+ }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/PacketBridge.kt b/staticlibs/testutils/devicetests/com/android/testutils/PacketBridge.kt
index 1a2cc88..0b736d1 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/PacketBridge.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/PacketBridge.kt
@@ -40,7 +40,8 @@
class PacketBridge(
context: Context,
addresses: List<LinkAddress>,
- dnsAddr: InetAddress
+ dnsAddr: InetAddress,
+ portMapping: List<Pair<Int, Int>>
) {
private val binder = Binder()
@@ -56,6 +57,10 @@
// Register test networks to ConnectivityService.
private val internalNetworkCallback: TestableNetworkCallback
private val externalNetworkCallback: TestableNetworkCallback
+
+ private val internalForwardMap = HashMap<Int, Int>()
+ private val externalForwardMap = HashMap<Int, Int>()
+
val internalNetwork: Network
val externalNetwork: Network
init {
@@ -65,14 +70,28 @@
externalNetworkCallback = exCb
internalNetwork = inNet
externalNetwork = exNet
+ for (mapping in portMapping) {
+ internalForwardMap[mapping.first] = mapping.second
+ externalForwardMap[mapping.second] = mapping.first
+ }
}
// Set up the packet bridge.
private val internalFd = internalIface.fileDescriptor.fileDescriptor
private val externalFd = externalIface.fileDescriptor.fileDescriptor
- private val pr1 = PacketForwarder(internalFd, 1500, externalFd)
- private val pr2 = PacketForwarder(externalFd, 1500, internalFd)
+ private val pr1 = InternalPacketForwarder(
+ internalFd,
+ 1500,
+ externalFd,
+ internalForwardMap
+ )
+ private val pr2 = ExternalPacketForwarder(
+ externalFd,
+ 1500,
+ internalFd,
+ externalForwardMap
+ )
fun start() {
IoUtils.setBlocking(internalFd, true /* blocking */)
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/PacketForwarder.java b/staticlibs/testutils/devicetests/com/android/testutils/PacketForwarderBase.java
similarity index 68%
rename from staticlibs/testutils/devicetests/com/android/testutils/PacketForwarder.java
rename to staticlibs/testutils/devicetests/com/android/testutils/PacketForwarderBase.java
index d8efb7d..5c79eb0 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/PacketForwarder.java
+++ b/staticlibs/testutils/devicetests/com/android/testutils/PacketForwarderBase.java
@@ -32,6 +32,7 @@
import java.io.FileDescriptor;
import java.io.IOException;
+import java.util.Map;
import java.util.Objects;
/**
@@ -57,8 +58,9 @@
* from the http server, the same mechanism is applied but in a different direction,
* where the source and destination will be swapped.
*/
-public class PacketForwarder extends Thread {
+public abstract class PacketForwarderBase extends Thread {
private static final String TAG = "PacketForwarder";
+ static final int DESTINATION_PORT_OFFSET = 2;
// The source fd to read packets from.
@NonNull
@@ -70,8 +72,10 @@
@NonNull
final FileDescriptor mDstFd;
+ @NonNull
+ final Map<Integer, Integer> mPortRemapRules;
/**
- * Construct a {@link PacketForwarder}.
+ * Construct a {@link PacketForwarderBase}.
*
* This class reads packets from {@code srcFd} of a {@link TestNetworkInterface}, and
* forwards them to the {@code dstFd} of another {@link TestNetworkInterface}.
@@ -82,13 +86,49 @@
* @param srcFd {@link FileDescriptor} to read packets from.
* @param mtu MTU of the test network.
* @param dstFd {@link FileDescriptor} to write packets to.
+ * @param portRemapRules port remap rules
*/
- public PacketForwarder(@NonNull FileDescriptor srcFd, int mtu,
- @NonNull FileDescriptor dstFd) {
+ public PacketForwarderBase(@NonNull FileDescriptor srcFd, int mtu,
+ @NonNull FileDescriptor dstFd,
+ @NonNull Map<Integer, Integer> portRemapRules) {
super(TAG);
mSrcFd = Objects.requireNonNull(srcFd);
mBuf = new byte[mtu];
mDstFd = Objects.requireNonNull(dstFd);
+ mPortRemapRules = Objects.requireNonNull(portRemapRules);
+ }
+
+ /**
+ * A method to prepare forwarding packets between two instances of {@link TestNetworkInterface},
+ * which includes ports mapping.
+ * Subclasses should override this method to implement the needed port remapping.
+ * For internal forwarder will remapped destination port,
+ * external forwarder will remapped source port.
+ * Example:
+ * An outgoing packet from the internal interface with
+ * source 1.2.3.4:1234 and destination 8.8.8.8:80
+ * might be translated to 8.8.8.8:1234 -> 1.2.3.4:8080 before forwarding.
+ * An outgoing packet from the external interface with
+ * source 1.2.3.4:8080 and destination 8.8.8.8:1234
+ * might be translated to 8.8.8.8:80 -> 1.2.3.4:1234 before forwarding.
+ */
+ abstract void remapPort(@NonNull byte[] buf, int version);
+
+ /**
+ * Retrieves a potentially remapped port number from a packet.
+ *
+ * @param buf The packet data as a byte array.
+ * @param transportOffset The offset within the packet where the transport layer port begins.
+ * @return The remapped port if a mapping exists in the internal forwarding map,
+ * otherwise returns 0 (indicating no remapping).
+ */
+ int getRemappedPort(@NonNull byte[] buf, int transportOffset) {
+ int port = PacketReflectorUtil.getPortAt(buf, transportOffset);
+ return mPortRemapRules.getOrDefault(port, 0);
+ }
+
+ int getTransportOffset(int version) {
+ return version == 4 ? IPV4_HEADER_LENGTH : IPV6_HEADER_LENGTH;
}
private void forwardPacket(@NonNull byte[] buf, int len) {
@@ -99,7 +139,13 @@
}
}
- // Reads one packet from mSrcFd, and writes the packet to the mDstFd for supported protocols.
+ /**
+ * Reads one packet from mSrcFd, and writes the packet to the mDestFd for supported protocols.
+ * This includes:
+ * 1.Address Swapping: Swaps source and destination IP addresses.
+ * 2.Port Remapping: Remap port if necessary.
+ * 3.Checksum Recalculation: Updates IP and transport layer checksums to reflect changes.
+ */
private void processPacket() {
final int len = PacketReflectorUtil.readPacket(mSrcFd, mBuf);
if (len < 1) {
@@ -142,13 +188,19 @@
if (len < ipHdrLen + transportHdrLen) {
throw new IllegalStateException("Unexpected buffer length: " + len);
}
- // Swap addresses.
+
+ // Swap source and destination address.
PacketReflectorUtil.swapAddresses(mBuf, version);
+ // Remapping the port.
+ remapPort(mBuf, version);
+
+ // Fix IP and Transport layer checksum.
+ PacketReflectorUtil.fixPacketChecksum(mBuf, len, version, proto);
+
// Send the packet to the destination fd.
forwardPacket(mBuf, len);
}
-
@Override
public void run() {
Log.i(TAG, "starting fd=" + mSrcFd + " valid=" + mSrcFd.valid());
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestHttpServer.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestHttpServer.kt
index 740bf63..f1f0c1c 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/TestHttpServer.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TestHttpServer.kt
@@ -25,8 +25,10 @@
* A minimal HTTP server running on a random available port.
*
* @param host The host to listen to, or null to listen on all hosts
+ * @param port The port to listen to, or 0 to auto select
*/
-class TestHttpServer(host: String? = null) : NanoHTTPD(host, 0 /* auto-select the port */) {
+class TestHttpServer
+ @JvmOverloads constructor(host: String? = null, port: Int = 0) : NanoHTTPD(host, port) {
// Map of URL path -> HTTP response code
private val responses = HashMap<Request, Response>()
diff --git a/tests/common/java/android/net/NetworkCapabilitiesTest.java b/tests/common/java/android/net/NetworkCapabilitiesTest.java
index 3a3459b..3124b1b 100644
--- a/tests/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/common/java/android/net/NetworkCapabilitiesTest.java
@@ -54,6 +54,7 @@
import static android.net.NetworkCapabilities.SIGNAL_STRENGTH_UNSPECIFIED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_SATELLITE;
import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static android.net.NetworkCapabilities.TRANSPORT_USB;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
@@ -761,6 +762,47 @@
}
@Test
+ public void testSetNetworkSpecifierWithCellularAndSatelliteMultiTransportNc() {
+ final TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier(1);
+ NetworkCapabilities nc = new NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .addTransportType(TRANSPORT_SATELLITE)
+ .setNetworkSpecifier(specifier)
+ .build();
+ // Adding a specifier did not crash with 2 transports if it is cellular + satellite
+ assertEquals(specifier, nc.getNetworkSpecifier());
+ }
+
+ @Test
+ public void testSetNetworkSpecifierWithWifiAndSatelliteMultiTransportNc() {
+ final TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier(1);
+ NetworkCapabilities.Builder nc1 = new NetworkCapabilities.Builder();
+ nc1.addTransportType(TRANSPORT_SATELLITE).addTransportType(TRANSPORT_WIFI);
+ // Adding multiple transports specifier to crash, apart from cellular + satellite
+ // combination
+ assertThrows("Cannot set NetworkSpecifier on a NetworkCapability with multiple transports!",
+ IllegalStateException.class,
+ () -> nc1.build().setNetworkSpecifier(specifier));
+ assertThrows("Cannot set NetworkSpecifier on a NetworkCapability with multiple transports!",
+ IllegalStateException.class,
+ () -> nc1.setNetworkSpecifier(specifier));
+ }
+
+ @Test
+ public void testSetNetworkSpecifierOnTestWithCellularAndSatelliteMultiTransportNc() {
+ final TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier(1);
+ NetworkCapabilities nc = new NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_TEST)
+ .addTransportType(TRANSPORT_CELLULAR)
+ .addTransportType(TRANSPORT_SATELLITE)
+ .setNetworkSpecifier(specifier)
+ .build();
+ // Adding a specifier did not crash with 3 transports , TEST + CELLULAR + SATELLITE and if
+ // one is test
+ assertEquals(specifier, nc.getNetworkSpecifier());
+ }
+
+ @Test
public void testSetNetworkSpecifierOnTestMultiTransportNc() {
final NetworkSpecifier specifier = CompatUtil.makeEthernetNetworkSpecifier("eth0");
NetworkCapabilities nc = new NetworkCapabilities.Builder()
diff --git a/tests/cts/hostside/Android.bp b/tests/cts/hostside/Android.bp
index 2688fb8..f6c0430 100644
--- a/tests/cts/hostside/Android.bp
+++ b/tests/cts/hostside/Android.bp
@@ -27,7 +27,10 @@
name: "CtsHostsideNetworkTests",
defaults: ["cts_defaults"],
// Only compile source java files in this apk.
- srcs: ["src/**/*.java"],
+ srcs: [
+ "src/**/*.java",
+ ":ArgumentConstants",
+ ],
libs: [
"net-tests-utils-host-device-common",
"cts-tradefed",
diff --git a/tests/cts/hostside/app/Android.bp b/tests/cts/hostside/app/Android.bp
index d555491..cf4afa9 100644
--- a/tests/cts/hostside/app/Android.bp
+++ b/tests/cts/hostside/app/Android.bp
@@ -36,7 +36,10 @@
"android.test.runner",
"android.test.base",
],
- srcs: ["src/**/*.java"],
+ srcs: [
+ "src/**/*.java",
+ ":ArgumentConstants",
+ ],
// Tag this module as a cts test artifact
test_suites: [
"general-tests",
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractDefaultRestrictionsTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractDefaultRestrictionsTest.java
new file mode 100644
index 0000000..8a3e790
--- /dev/null
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractDefaultRestrictionsTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.cts.net.hostside;
+
+import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.os.SystemClock;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Base class for default, always-on network restrictions.
+ */
+abstract class AbstractDefaultRestrictionsTest extends AbstractRestrictBackgroundNetworkTestCase {
+
+ @Before
+ public final void setUp() throws Exception {
+ super.setUp();
+
+ removePowerSaveModeWhitelist(TEST_APP2_PKG);
+ removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
+
+ registerBroadcastReceiver();
+ assumeTrue("Feature not enabled", isNetworkBlockedForTopSleepingAndAbove());
+ }
+
+ @After
+ public final void tearDown() throws Exception {
+ super.tearDown();
+
+ removePowerSaveModeWhitelist(TEST_APP2_PKG);
+ removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
+ }
+
+ @Test
+ public void testFgsNetworkAccess() throws Exception {
+ assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
+ SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+ assertNetworkAccess(false, null);
+
+ launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_FOREGROUND_SERVICE);
+ }
+
+ @Test
+ public void testActivityNetworkAccess() throws Exception {
+ assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
+ SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+ assertNetworkAccess(false, null);
+
+ launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
+ }
+
+ @Test
+ public void testBackgroundNetworkAccess_inFullAllowlist() throws Exception {
+ assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
+ SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+ assertNetworkAccess(false, null);
+
+ addPowerSaveModeWhitelist(TEST_APP2_PKG);
+ assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
+ assertNetworkAccess(true, null);
+ }
+
+ @Test
+ public void testBackgroundNetworkAccess_inExceptIdleAllowlist() throws Exception {
+ assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
+ SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+ assertNetworkAccess(false, null);
+
+ addPowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
+ assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
+ assertNetworkAccess(true, null);
+ }
+}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index 29aac3c..2ca8832 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -23,6 +23,7 @@
import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
import static android.os.BatteryManager.BATTERY_PLUGGED_ANY;
+import static com.android.cts.net.arguments.InstrumentationArguments.ARG_WAIVE_BIND_PRIORITY;
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.executeShellCommand;
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.forceRunJob;
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getConnectivityManager;
@@ -65,11 +66,13 @@
import android.util.Pair;
import androidx.annotation.Nullable;
+import androidx.test.platform.app.InstrumentationRegistry;
import com.android.compatibility.common.util.AmUtils;
import com.android.compatibility.common.util.BatteryUtils;
import com.android.compatibility.common.util.DeviceConfigStateHelper;
import com.android.compatibility.common.util.ThrowingRunnable;
+import com.android.modules.utils.build.SdkLevel;
import org.junit.Rule;
import org.junit.rules.RuleChain;
@@ -90,6 +93,8 @@
protected static final String TEST_PKG = "com.android.cts.net.hostside";
protected static final String TEST_APP2_PKG = "com.android.cts.net.hostside.app2";
+ // TODO(b/321797685): Configure it via device-config once it is available.
+ protected static final long PROCESS_STATE_TRANSITION_DELAY_MS = TimeUnit.SECONDS.toMillis(5);
private static final String TEST_APP2_ACTIVITY_CLASS = TEST_APP2_PKG + ".MyActivity";
private static final String TEST_APP2_SERVICE_CLASS = TEST_APP2_PKG + ".MyForegroundService";
@@ -97,7 +102,6 @@
private static final ComponentName TEST_JOB_COMPONENT = new ComponentName(
TEST_APP2_PKG, TEST_APP2_JOB_SERVICE_CLASS);
-
private static final int TEST_JOB_ID = 7357437;
private static final int SLEEP_TIME_SEC = 1;
@@ -152,8 +156,6 @@
private static final IntentFilter BATTERY_CHANGED_FILTER =
new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
- private static final String APP_NOT_FOREGROUND_ERROR = "app_not_fg";
-
protected static final long TEMP_POWERSAVE_WHITELIST_DURATION_MS = 20_000; // 20 sec
private static final long BROADCAST_TIMEOUT_MS = 5_000;
@@ -181,7 +183,16 @@
mUid = getUid(TEST_APP2_PKG);
mMyUid = getUid(mContext.getPackageName());
mServiceClient = new MyServiceClient(mContext);
- mServiceClient.bind();
+
+ final Bundle args = InstrumentationRegistry.getArguments();
+ final int bindPriorityFlags;
+ if (Boolean.valueOf(args.getString(ARG_WAIVE_BIND_PRIORITY, "false"))) {
+ bindPriorityFlags = Context.BIND_WAIVE_PRIORITY;
+ } else {
+ bindPriorityFlags = Context.BIND_NOT_FOREGROUND;
+ }
+ mServiceClient.bind(bindPriorityFlags);
+
mPowerManager = mContext.getSystemService(PowerManager.class);
executeShellCommand("cmd netpolicy start-watching " + mUid);
// Some of the test cases assume that Data saver mode is initially disabled, which might not
@@ -205,6 +216,22 @@
if (null != lock && lock.isHeld()) lock.release();
}
+ /**
+ * Check if the feature blocking network for top_sleeping and lower priority proc-states is
+ * enabled. This is a manual check because the feature flag infrastructure may not be available
+ * in all the branches that will get this code.
+ * TODO: b/322115994 - Use @RequiresFlagsEnabled with
+ * Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE once the tests are moved to cts.
+ */
+ protected boolean isNetworkBlockedForTopSleepingAndAbove() {
+ if (!SdkLevel.isAtLeastV()) {
+ return false;
+ }
+ final String output = executeShellCommand("device_config get backstage_power"
+ + " com.android.server.net.network_blocked_for_top_sleeping_and_above");
+ return Boolean.parseBoolean(output);
+ }
+
protected int getUid(String packageName) throws Exception {
return mContext.getPackageManager().getPackageUid(packageName, 0);
}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/ConnOnActivityStartTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/ConnOnActivityStartTest.java
index 4004789..c1d576d 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/ConnOnActivityStartTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/ConnOnActivityStartTest.java
@@ -18,6 +18,7 @@
import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING;
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getUiDevice;
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setRestrictBackground;
@@ -28,8 +29,13 @@
import static com.android.cts.net.hostside.Property.METERED_NETWORK;
import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
+import static org.junit.Assume.assumeTrue;
+
+import android.os.SystemClock;
import android.util.Log;
+import com.android.compatibility.common.util.ThrowingRunnable;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -63,14 +69,14 @@
@RequiredProperties({BATTERY_SAVER_MODE})
public void testStartActivity_batterySaver() throws Exception {
setBatterySaverMode(true);
- assertLaunchedActivityHasNetworkAccess("testStartActivity_batterySaver");
+ assertLaunchedActivityHasNetworkAccess("testStartActivity_batterySaver", null);
}
@Test
@RequiredProperties({DATA_SAVER_MODE, METERED_NETWORK})
public void testStartActivity_dataSaver() throws Exception {
setRestrictBackground(true);
- assertLaunchedActivityHasNetworkAccess("testStartActivity_dataSaver");
+ assertLaunchedActivityHasNetworkAccess("testStartActivity_dataSaver", null);
}
@Test
@@ -79,7 +85,7 @@
setDozeMode(true);
// TODO (235284115): We need to turn on Doze every time before starting
// the activity.
- assertLaunchedActivityHasNetworkAccess("testStartActivity_doze");
+ assertLaunchedActivityHasNetworkAccess("testStartActivity_doze", null);
}
@Test
@@ -89,11 +95,24 @@
setAppIdle(true);
// TODO (235284115): We need to put the app into app standby mode every
// time before starting the activity.
- assertLaunchedActivityHasNetworkAccess("testStartActivity_appStandby");
+ assertLaunchedActivityHasNetworkAccess("testStartActivity_appStandby", null);
}
- private void assertLaunchedActivityHasNetworkAccess(String testName) throws Exception {
+ @Test
+ public void testStartActivity_default() throws Exception {
+ assumeTrue("Feature not enabled", isNetworkBlockedForTopSleepingAndAbove());
+ assertLaunchedActivityHasNetworkAccess("testStartActivity_default", () -> {
+ assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
+ SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+ });
+ }
+
+ private void assertLaunchedActivityHasNetworkAccess(String testName,
+ ThrowingRunnable onBeginIteration) throws Exception {
for (int i = 0; i < TEST_ITERATION_COUNT; ++i) {
+ if (onBeginIteration != null) {
+ onBeginIteration.run();
+ }
Log.i(TAG, testName + " start #" + i);
launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
getUiDevice().pressHome();
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DefaultRestrictionsMeteredTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/DefaultRestrictionsMeteredTest.java
new file mode 100644
index 0000000..f3a1026
--- /dev/null
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/DefaultRestrictionsMeteredTest.java
@@ -0,0 +1,23 @@
+/*
+ * 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.cts.net.hostside;
+
+import static com.android.cts.net.hostside.Property.METERED_NETWORK;
+
+@RequiredProperties({METERED_NETWORK})
+public class DefaultRestrictionsMeteredTest extends AbstractDefaultRestrictionsTest {
+}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DefaultRestrictionsNonMeteredTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/DefaultRestrictionsNonMeteredTest.java
new file mode 100644
index 0000000..5651dd0
--- /dev/null
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/DefaultRestrictionsNonMeteredTest.java
@@ -0,0 +1,23 @@
+/*
+ * 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.cts.net.hostside;
+
+import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
+
+@RequiredProperties({NON_METERED_NETWORK})
+public class DefaultRestrictionsNonMeteredTest extends AbstractDefaultRestrictionsTest {
+}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java
index 93cc911..980ecd5 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java
@@ -34,26 +34,30 @@
private Context mContext;
private ServiceConnection mServiceConnection;
- private IMyService mService;
+ private volatile IMyService mService;
+ private final ConditionVariable mServiceCondition = new ConditionVariable();
public MyServiceClient(Context context) {
mContext = context;
}
- public void bind() {
+ /**
+ * Binds to a service in the test app to communicate state.
+ * @param bindPriorityFlags Flags to influence the process-state of the bound app.
+ */
+ public void bind(int bindPriorityFlags) {
if (mService != null) {
throw new IllegalStateException("Already bound");
}
-
- final ConditionVariable cv = new ConditionVariable();
mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = IMyService.Stub.asInterface(service);
- cv.open();
+ mServiceCondition.open();
}
@Override
public void onServiceDisconnected(ComponentName name) {
+ mServiceCondition.close();
mService = null;
}
};
@@ -63,12 +67,8 @@
// Needs to use BIND_NOT_FOREGROUND so app2 does not run in
// the same process state as app
mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE
- | Context.BIND_NOT_FOREGROUND);
- cv.block(TIMEOUT_MS);
- if (mService == null) {
- throw new IllegalStateException(
- "Could not bind to MyService service after " + TIMEOUT_MS + "ms");
- }
+ | bindPriorityFlags);
+ ensureServiceConnection();
}
public void unbind() {
@@ -77,37 +77,56 @@
}
}
+ private void ensureServiceConnection() {
+ if (mService != null) {
+ return;
+ }
+ mServiceCondition.block(TIMEOUT_MS);
+ if (mService == null) {
+ throw new IllegalStateException(
+ "Could not bind to MyService service after " + TIMEOUT_MS + "ms");
+ }
+ }
+
public void registerBroadcastReceiver() throws RemoteException {
+ ensureServiceConnection();
mService.registerBroadcastReceiver();
}
public int getCounters(String receiverName, String action) throws RemoteException {
+ ensureServiceConnection();
return mService.getCounters(receiverName, action);
}
public String checkNetworkStatus() throws RemoteException {
+ ensureServiceConnection();
return mService.checkNetworkStatus();
}
public String getRestrictBackgroundStatus() throws RemoteException {
+ ensureServiceConnection();
return mService.getRestrictBackgroundStatus();
}
public void sendNotification(int notificationId, String notificationType)
throws RemoteException {
+ ensureServiceConnection();
mService.sendNotification(notificationId, notificationType);
}
public void registerNetworkCallback(final NetworkRequest request, INetworkCallback cb)
throws RemoteException {
+ ensureServiceConnection();
mService.registerNetworkCallback(request, cb);
}
public void unregisterNetworkCallback() throws RemoteException {
+ ensureServiceConnection();
mService.unregisterNetworkCallback();
}
public int scheduleJob(JobInfo jobInfo) throws RemoteException {
+ ensureServiceConnection();
return mService.scheduleJob(jobInfo);
}
}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
index eb2347d..5552b8f 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
@@ -17,6 +17,7 @@
package com.android.cts.net.hostside;
import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.SIGNAL_STRENGTH_UNSPECIFIED;
@@ -34,6 +35,7 @@
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.cts.util.CtsNetUtils;
+import android.os.SystemClock;
import android.util.Log;
import com.android.modules.utils.build.SdkLevel;
@@ -43,6 +45,7 @@
import org.junit.Rule;
import org.junit.Test;
+import java.util.ArrayList;
import java.util.Objects;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
@@ -145,12 +148,22 @@
public Network expectAvailableCallbackAndGetNetwork() {
final CallbackInfo cb = nextCallback(TEST_CONNECT_TIMEOUT_MS);
if (cb.state != CallbackState.AVAILABLE) {
- fail("Network is not available. Instead obtained the following callback :"
- + cb);
+ fail("Network is not available. Instead obtained the following callback :" + cb);
}
return cb.network;
}
+ public void drainAndWaitForIdle() {
+ try {
+ do {
+ mCallbacks.drainTo(new ArrayList<>());
+ } while (mCallbacks.poll(TEST_CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS) != null);
+ } catch (InterruptedException ie) {
+ Log.e(TAG, "Interrupted while draining callback queue", ie);
+ Thread.currentThread().interrupt();
+ }
+ }
+
public void expectBlockedStatusCallback(Network expectedNetwork, boolean expectBlocked) {
expectCallback(CallbackState.BLOCKED_STATUS, expectedNetwork, expectBlocked);
}
@@ -225,7 +238,7 @@
// Check that the network is metered.
mTestNetworkCallback.expectCapabilitiesCallbackEventually(mNetwork,
false /* hasCapability */, NET_CAPABILITY_NOT_METERED);
- mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
+ mTestNetworkCallback.drainAndWaitForIdle();
// Before Android T, DNS queries over private DNS should be but are not restricted by Power
// Saver or Data Saver. The issue is fixed in mainline update and apps can no longer request
@@ -357,6 +370,58 @@
}
}
+ @Test
+ public void testOnBlockedStatusChanged_default() throws Exception {
+ assumeTrue("Feature not enabled", isNetworkBlockedForTopSleepingAndAbove());
+
+ try {
+ assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
+ assertNetworkAccess(false, null);
+ assertNetworkAccessBlockedByBpf(true, mUid, true /* metered */);
+
+ launchActivity();
+ assertTopState();
+ assertNetworkAccess(true, null);
+ mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, false);
+ assertNetworkAccessBlockedByBpf(false, mUid, true /* metered */);
+
+ finishActivity();
+ assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
+ SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+ assertNetworkAccess(false, null);
+ mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, true);
+ assertNetworkAccessBlockedByBpf(true, mUid, true /* metered */);
+
+ } finally {
+ mMeterednessConfiguration.resetNetworkMeteredness();
+ }
+
+ // Set to non-metered network
+ mMeterednessConfiguration.configureNetworkMeteredness(false);
+ mTestNetworkCallback.expectCapabilitiesCallbackEventually(mNetwork,
+ true /* hasCapability */, NET_CAPABILITY_NOT_METERED);
+ try {
+ assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
+ assertNetworkAccess(false, null);
+ assertNetworkAccessBlockedByBpf(true, mUid, false /* metered */);
+
+ launchActivity();
+ assertTopState();
+ assertNetworkAccess(true, null);
+ mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, false);
+ assertNetworkAccessBlockedByBpf(false, mUid, false /* metered */);
+
+ finishActivity();
+ assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
+ SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+ assertNetworkAccess(false, null);
+ mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, true);
+ assertNetworkAccessBlockedByBpf(true, mUid, false /* metered */);
+ } finally {
+ mMeterednessConfiguration.resetNetworkMeteredness();
+ }
+ }
+
// TODO: 1. test against VPN lockdown.
// 2. test against multiple networks.
}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyManagerTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyManagerTest.java
index 7aeca77..968e270 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyManagerTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyManagerTest.java
@@ -17,6 +17,7 @@
package com.android.cts.net.hostside;
import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING;
import static android.os.Process.SYSTEM_UID;
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.assertIsUidRestrictedOnMeteredNetworks;
@@ -28,6 +29,9 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.os.SystemClock;
import org.junit.After;
import org.junit.Before;
@@ -238,4 +242,33 @@
assertIsUidRestrictedOnMeteredNetworks(mUid, false /* expectedResult */);
}
}
+
+ @Test
+ public void testIsUidNetworkingBlocked_whenInBackground() throws Exception {
+ assumeTrue("Feature not enabled", isNetworkBlockedForTopSleepingAndAbove());
+
+ try {
+ assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
+ SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+ assertNetworkingBlockedStatusForUid(mUid, METERED, true /* expectedResult */);
+ assertTrue(isUidNetworkingBlocked(mUid, NON_METERED));
+
+ launchActivity();
+ assertTopState();
+ assertNetworkingBlockedStatusForUid(mUid, METERED, false /* expectedResult */);
+ assertFalse(isUidNetworkingBlocked(mUid, NON_METERED));
+
+ finishActivity();
+ assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
+ SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+ assertNetworkingBlockedStatusForUid(mUid, METERED, true /* expectedResult */);
+ assertTrue(isUidNetworkingBlocked(mUid, NON_METERED));
+
+ addPowerSaveModeWhitelist(TEST_APP2_PKG);
+ assertNetworkingBlockedStatusForUid(mUid, METERED, false /* expectedResult */);
+ assertFalse(isUidNetworkingBlocked(mUid, NON_METERED));
+ } finally {
+ removePowerSaveModeWhitelist(TEST_APP2_PKG);
+ }
+ }
}
diff --git a/tests/cts/hostside/instrumentation_arguments/Android.bp b/tests/cts/hostside/instrumentation_arguments/Android.bp
new file mode 100644
index 0000000..cdede36
--- /dev/null
+++ b/tests/cts/hostside/instrumentation_arguments/Android.bp
@@ -0,0 +1,22 @@
+// 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"],
+}
+
+filegroup {
+ name: "ArgumentConstants",
+ srcs: ["src/**/*.java"],
+}
diff --git a/tests/cts/hostside/instrumentation_arguments/src/com/android/cts/net/arguments/InstrumentationArguments.java b/tests/cts/hostside/instrumentation_arguments/src/com/android/cts/net/arguments/InstrumentationArguments.java
new file mode 100644
index 0000000..472e347
--- /dev/null
+++ b/tests/cts/hostside/instrumentation_arguments/src/com/android/cts/net/arguments/InstrumentationArguments.java
@@ -0,0 +1,21 @@
+/*
+ * 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.cts.net.arguments;
+
+public interface InstrumentationArguments {
+ String ARG_WAIVE_BIND_PRIORITY = "waive_bind_priority";
+}
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideConnOnActivityStartTest.java b/tests/cts/hostside/src/com/android/cts/net/HostsideConnOnActivityStartTest.java
index 849ac7c..880e826 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideConnOnActivityStartTest.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideConnOnActivityStartTest.java
@@ -16,6 +16,8 @@
package com.android.cts.net;
+import static com.android.cts.net.arguments.InstrumentationArguments.ARG_WAIVE_BIND_PRIORITY;
+
import android.platform.test.annotations.FlakyTest;
import com.android.testutils.SkipPresubmit;
@@ -26,9 +28,12 @@
import org.junit.Test;
+import java.util.Map;
+
@SkipPresubmit(reason = "Out of SLO flakiness")
public class HostsideConnOnActivityStartTest extends HostsideNetworkTestCase {
private static final String TEST_CLASS = TEST_PKG + ".ConnOnActivityStartTest";
+
@BeforeClassWithInfo
public static void setUpOnce(TestInformation testInfo) throws Exception {
uninstallPackage(testInfo, TEST_APP2_PKG, false);
@@ -60,4 +65,11 @@
public void testStartActivity_appStandby() throws Exception {
runDeviceTests(TEST_PKG, TEST_CLASS, "testStartActivity_appStandby");
}
+
+ // TODO(b/321848487): Annotate with @RequiresFlagsEnabled to mirror the device-side test.
+ @Test
+ public void testStartActivity_default() throws Exception {
+ runDeviceTestsWithArgs(TEST_PKG, TEST_CLASS, "testStartActivity_default",
+ Map.of(ARG_WAIVE_BIND_PRIORITY, "true"));
+ }
}
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideDefaultNetworkRestrictionsTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideDefaultNetworkRestrictionsTests.java
new file mode 100644
index 0000000..0d01fc1
--- /dev/null
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideDefaultNetworkRestrictionsTests.java
@@ -0,0 +1,103 @@
+/*
+ * 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.cts.net;
+
+import static com.android.cts.net.arguments.InstrumentationArguments.ARG_WAIVE_BIND_PRIORITY;
+
+import com.android.testutils.SkipPresubmit;
+import com.android.tradefed.device.DeviceNotAvailableException;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Map;
+
+// TODO(b/321848487): Annotate with @RequiresFlagsEnabled to mirror the device-side tests.
+@SkipPresubmit(reason = "Monitoring for flakiness")
+public class HostsideDefaultNetworkRestrictionsTests extends HostsideNetworkTestCase {
+ private static final String METERED_TEST_CLASS = TEST_PKG + ".DefaultRestrictionsMeteredTest";
+ private static final String NON_METERED_TEST_CLASS =
+ TEST_PKG + ".DefaultRestrictionsNonMeteredTest";
+
+ @Before
+ public void setUp() throws Exception {
+ uninstallPackage(TEST_APP2_PKG, false);
+ installPackage(TEST_APP2_APK);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ uninstallPackage(TEST_APP2_PKG, true);
+ }
+
+ private void runMeteredTest(String methodName) throws DeviceNotAvailableException {
+ runDeviceTestsWithArgs(TEST_PKG, METERED_TEST_CLASS, methodName,
+ Map.of(ARG_WAIVE_BIND_PRIORITY, "true"));
+ }
+
+ private void runNonMeteredTest(String methodName) throws DeviceNotAvailableException {
+ runDeviceTestsWithArgs(TEST_PKG, NON_METERED_TEST_CLASS, methodName,
+ Map.of(ARG_WAIVE_BIND_PRIORITY, "true"));
+ }
+
+ @Test
+ public void testMeteredNetworkAccess_defaultRestrictions_testActivityNetworkAccess()
+ throws Exception {
+ runMeteredTest("testActivityNetworkAccess");
+ }
+
+ @Test
+ public void testMeteredNetworkAccess_defaultRestrictions_testFgsNetworkAccess()
+ throws Exception {
+ runMeteredTest("testFgsNetworkAccess");
+ }
+
+ @Test
+ public void testMeteredNetworkAccess_defaultRestrictions_inFullAllowlist() throws Exception {
+ runMeteredTest("testBackgroundNetworkAccess_inFullAllowlist");
+ }
+
+ @Test
+ public void testMeteredNetworkAccess_defaultRestrictions_inExceptIdleAllowlist()
+ throws Exception {
+ runMeteredTest("testBackgroundNetworkAccess_inExceptIdleAllowlist");
+ }
+
+ @Test
+ public void testNonMeteredNetworkAccess_defaultRestrictions_testActivityNetworkAccess()
+ throws Exception {
+ runNonMeteredTest("testActivityNetworkAccess");
+ }
+
+ @Test
+ public void testNonMeteredNetworkAccess_defaultRestrictions_testFgsNetworkAccess()
+ throws Exception {
+ runNonMeteredTest("testFgsNetworkAccess");
+ }
+
+ @Test
+ public void testNonMeteredNetworkAccess_defaultRestrictions_inFullAllowlist() throws Exception {
+ runNonMeteredTest("testBackgroundNetworkAccess_inFullAllowlist");
+ }
+
+ @Test
+ public void testNonMeteredNetworkAccess_defaultRestrictions_inExceptIdleAllowlist()
+ throws Exception {
+ runNonMeteredTest("testBackgroundNetworkAccess_inExceptIdleAllowlist");
+ }
+}
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkCallbackTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkCallbackTests.java
index 04bd1ad..361f7c7 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkCallbackTests.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkCallbackTests.java
@@ -15,12 +15,16 @@
*/
package com.android.cts.net;
+import static com.android.cts.net.arguments.InstrumentationArguments.ARG_WAIVE_BIND_PRIORITY;
+
import com.android.testutils.SkipPresubmit;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import java.util.Map;
+
@SkipPresubmit(reason = "Out of SLO flakiness")
public class HostsideNetworkCallbackTests extends HostsideNetworkTestCase {
@@ -46,5 +50,12 @@
runDeviceTests(TEST_PKG,
TEST_PKG + ".NetworkCallbackTest", "testOnBlockedStatusChanged_powerSaver");
}
+
+ // TODO(b/321848487): Annotate with @RequiresFlagsEnabled to mirror the device-side test.
+ @Test
+ public void testOnBlockedStatusChanged_default() throws Exception {
+ runDeviceTestsWithArgs(TEST_PKG, TEST_PKG + ".NetworkCallbackTest",
+ "testOnBlockedStatusChanged_default", Map.of(ARG_WAIVE_BIND_PRIORITY, "true"));
+ }
}
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkPolicyManagerTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkPolicyManagerTests.java
index 3ddb88b..e97db58 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkPolicyManagerTests.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkPolicyManagerTests.java
@@ -16,10 +16,14 @@
package com.android.cts.net;
+import static com.android.cts.net.arguments.InstrumentationArguments.ARG_WAIVE_BIND_PRIORITY;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import java.util.Map;
+
public class HostsideNetworkPolicyManagerTests extends HostsideNetworkTestCase {
@Before
public void setUp() throws Exception {
@@ -71,4 +75,12 @@
runDeviceTests(TEST_PKG,
TEST_PKG + ".NetworkPolicyManagerTest", "testIsUidRestrictedOnMeteredNetworks");
}
+
+ // TODO(b/321848487): Annotate with @RequiresFlagsEnabled to mirror the device-side test.
+ @Test
+ public void testIsUidNetworkingBlocked_whenInBackground() throws Exception {
+ runDeviceTestsWithArgs(TEST_PKG, TEST_PKG + ".NetworkPolicyManagerTest",
+ "testIsUidNetworkingBlocked_whenInBackground",
+ Map.of(ARG_WAIVE_BIND_PRIORITY, "true"));
+ }
}
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
index 3358fd7..ca95ed6 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
@@ -31,10 +31,13 @@
import com.android.tradefed.testtype.junit4.AfterClassWithInfo;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
+import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
import com.android.tradefed.util.RunUtil;
import org.junit.runner.RunWith;
+import java.util.Map;
+
@RunWith(DeviceJUnit4ClassRunner.class)
abstract class HostsideNetworkTestCase extends BaseHostJUnit4Test {
protected static final boolean DEBUG = false;
@@ -146,6 +149,17 @@
+ packageName + ", u=" + currentUser);
}
+ protected boolean runDeviceTestsWithArgs(String packageName, String className,
+ String methodName, Map<String, String> args) throws DeviceNotAvailableException {
+ final DeviceTestRunOptions deviceTestRunOptions = new DeviceTestRunOptions(packageName)
+ .setTestClassName(className)
+ .setTestMethodName(methodName);
+ for (Map.Entry<String, String> arg : args.entrySet()) {
+ deviceTestRunOptions.addInstrumentationArg(arg.getKey(), arg.getValue());
+ }
+ return runDeviceTests(deviceTestRunOptions);
+ }
+
protected String runCommand(String command) throws DeviceNotAvailableException {
Log.d(TAG, "Command: '" + command + "'");
final String output = getDevice().executeShellCommand(command);
diff --git a/tests/cts/net/Android.bp b/tests/cts/net/Android.bp
index 98d5630..074c587 100644
--- a/tests/cts/net/Android.bp
+++ b/tests/cts/net/Android.bp
@@ -61,9 +61,7 @@
"TetheringIntegrationTestsBaseLib",
],
- // uncomment when b/13249961 is fixed
- // sdk_version: "current",
- platform_apis: true,
+ min_sdk_version: "30",
per_testcase_directory: true,
host_required: ["net-tests-utils-host-common"],
test_config_template: "AndroidTestTemplate.xml",
@@ -75,8 +73,8 @@
// Networking CTS tests for development and release. These tests always target the platform SDK
// version, and are subject to all the restrictions appropriate to that version. Before SDK
-// finalization, these tests have a min_sdk_version of 10000, and cannot be installed on release
-// devices.
+// finalization, these tests have a min_sdk_version of 10000, but they can still be installed on
+// release devices as their min_sdk_version is set to a production version.
android_test {
name: "CtsNetTestCases",
defaults: [
@@ -89,6 +87,14 @@
],
test_suites: [
"cts",
+ "mts-dnsresolver",
+ "mts-networking",
+ "mts-tethering",
+ "mts-wifi",
+ "mcts-dnsresolver",
+ "mcts-networking",
+ "mcts-tethering",
+ "mcts-wifi",
"general-tests",
],
}
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 2646b60..cdf8340 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -802,7 +802,9 @@
assertNull(redactedNormal.getUids());
assertNull(redactedNormal.getSsid());
assertNull(redactedNormal.getUnderlyingNetworks());
- assertEquals(0, redactedNormal.getSubscriptionIds().size());
+ // TODO: Make subIds public and update to verify the size is 2
+ final int subIdsSize = redactedNormal.getSubscriptionIds().size();
+ assertTrue(subIdsSize == 0 || subIdsSize == 2);
assertEquals(WifiInfo.DEFAULT_MAC_ADDRESS,
((WifiInfo) redactedNormal.getTransportInfo()).getBSSID());
assertEquals(rssi, ((WifiInfo) redactedNormal.getTransportInfo()).getRssi());
@@ -1552,6 +1554,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/DnsResolverTest.java b/tests/cts/net/src/android/net/cts/DnsResolverTest.java
index 9ff0f2f..752891f 100644
--- a/tests/cts/net/src/android/net/cts/DnsResolverTest.java
+++ b/tests/cts/net/src/android/net/cts/DnsResolverTest.java
@@ -23,6 +23,7 @@
import static android.net.DnsResolver.TYPE_AAAA;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.cts.util.CtsNetUtils.TestNetworkCallback;
+import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
import static android.system.OsConstants.ETIMEDOUT;
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
@@ -59,11 +60,14 @@
import com.android.net.module.util.DnsPacket;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.DeviceConfigRule;
import com.android.testutils.DnsResolverModuleTest;
import com.android.testutils.SkipPresubmit;
import org.junit.After;
import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -80,6 +84,8 @@
@AppModeFull(reason = "WRITE_SECURE_SETTINGS permission can't be granted to instant apps")
@RunWith(AndroidJUnit4.class)
public class DnsResolverTest {
+ @ClassRule
+ public static final DeviceConfigRule DEVICE_CONFIG_CLASS_RULE = new DeviceConfigRule();
@Rule
public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
@@ -123,6 +129,20 @@
private TestNetworkCallback mWifiRequestCallback = null;
+ /**
+ * @see BeforeClass
+ */
+ @BeforeClass
+ public static void beforeClass() throws Exception {
+ // Use async private DNS resolution to avoid flakes due to races applying the setting
+ DEVICE_CONFIG_CLASS_RULE.setConfig(NAMESPACE_CONNECTIVITY,
+ "networkmonitor_async_privdns_resolution", "1");
+ // Make sure NetworkMonitor is restarted before and after the test so the flag is applied
+ // and cleaned up.
+ maybeToggleWifiAndCell();
+ DEVICE_CONFIG_CLASS_RULE.runAfterNextCleanup(DnsResolverTest::maybeToggleWifiAndCell);
+ }
+
@Before
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getContext();
@@ -144,6 +164,12 @@
}
}
+ private static void maybeToggleWifiAndCell() throws Exception {
+ final CtsNetUtils utils = new CtsNetUtils(InstrumentationRegistry.getContext());
+ utils.reconnectWifiIfSupported();
+ utils.reconnectCellIfSupported();
+ }
+
private static String byteArrayToHexString(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for (int i = 0; i < bytes.length; ++i) {
diff --git a/tests/cts/net/src/android/net/cts/IpSecTransformStateTest.java b/tests/cts/net/src/android/net/cts/IpSecTransformStateTest.java
new file mode 100644
index 0000000..7b42306
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/IpSecTransformStateTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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 static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+
+import android.net.IpSecTransformState;
+import android.os.Build;
+import android.os.SystemClock;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.DevSdkIgnoreRunner;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+@RunWith(DevSdkIgnoreRunner.class)
+public class IpSecTransformStateTest {
+ @Rule public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
+
+ private static final long TIMESTAMP_MILLIS = 1000L;
+ private static final long HIGHEST_SEQ_NUMBER_TX = 10000L;
+ private static final long HIGHEST_SEQ_NUMBER_RX = 20000L;
+ private static final long PACKET_COUNT = 9000L;
+ private static final long BYTE_COUNT = 900000L;
+
+ private static final int REPLAY_BITMAP_LEN_BYTE = 512;
+ private static final byte[] REPLAY_BITMAP_NO_PACKETS = new byte[REPLAY_BITMAP_LEN_BYTE];
+ private static final byte[] REPLAY_BITMAP_ALL_RECEIVED = new byte[REPLAY_BITMAP_LEN_BYTE];
+
+ static {
+ for (int i = 0; i < REPLAY_BITMAP_ALL_RECEIVED.length; i++) {
+ REPLAY_BITMAP_ALL_RECEIVED[i] = (byte) 0xff;
+ }
+ }
+
+ @Test
+ public void testBuildAndGet() {
+ final IpSecTransformState state =
+ new IpSecTransformState.Builder()
+ .setTimestampMillis(TIMESTAMP_MILLIS)
+ .setTxHighestSequenceNumber(HIGHEST_SEQ_NUMBER_TX)
+ .setRxHighestSequenceNumber(HIGHEST_SEQ_NUMBER_RX)
+ .setPacketCount(PACKET_COUNT)
+ .setByteCount(BYTE_COUNT)
+ .setReplayBitmap(REPLAY_BITMAP_ALL_RECEIVED)
+ .build();
+
+ assertEquals(TIMESTAMP_MILLIS, state.getTimestampMillis());
+ assertEquals(HIGHEST_SEQ_NUMBER_TX, state.getTxHighestSequenceNumber());
+ assertEquals(HIGHEST_SEQ_NUMBER_RX, state.getRxHighestSequenceNumber());
+ assertEquals(PACKET_COUNT, state.getPacketCount());
+ assertEquals(BYTE_COUNT, state.getByteCount());
+ assertArrayEquals(REPLAY_BITMAP_ALL_RECEIVED, state.getReplayBitmap());
+ }
+
+ @Test
+ public void testSelfGeneratedTimestampMillis() {
+ final long elapsedRealtimeBefore = SystemClock.elapsedRealtime();
+
+ final IpSecTransformState state =
+ new IpSecTransformState.Builder().setReplayBitmap(REPLAY_BITMAP_NO_PACKETS).build();
+
+ final long elapsedRealtimeAfter = SystemClock.elapsedRealtime();
+
+ // Verify elapsedRealtimeBefore <= state.getTimestampMillis() <= elapsedRealtimeAfter
+ assertFalse(elapsedRealtimeBefore > state.getTimestampMillis());
+ assertFalse(elapsedRealtimeAfter < state.getTimestampMillis());
+ }
+
+ @Test
+ public void testBuildWithoutReplayBitmap() throws Exception {
+ try {
+ new IpSecTransformState.Builder().build();
+ fail("Expected expcetion if replay bitmap is not set");
+ } catch (NullPointerException expected) {
+ }
+ }
+}
diff --git a/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java b/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
index 17a9ca2..bca18f5 100644
--- a/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
+++ b/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
@@ -17,6 +17,12 @@
package android.net.cts;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
import android.content.ContentResolver;
import android.content.Context;
@@ -28,9 +34,21 @@
import android.platform.test.annotations.AppModeFull;
import android.system.ErrnoException;
import android.system.OsConstants;
-import android.test.AndroidTestCase;
-public class MultinetworkApiTest extends AndroidTestCase {
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.testutils.DeviceConfigRule;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class MultinetworkApiTest {
+ @Rule
+ public final DeviceConfigRule mDeviceConfigRule = new DeviceConfigRule();
static {
System.loadLibrary("nativemultinetwork_jni");
@@ -58,20 +76,17 @@
private CtsNetUtils mCtsNetUtils;
private String mOldMode;
private String mOldDnsSpecifier;
+ private Context mContext;
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mCM = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
- mCR = getContext().getContentResolver();
- mCtsNetUtils = new CtsNetUtils(getContext());
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ mCM = mContext.getSystemService(ConnectivityManager.class);
+ mCR = mContext.getContentResolver();
+ mCtsNetUtils = new CtsNetUtils(mContext);
}
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
- }
-
+ @Test
public void testGetaddrinfo() throws ErrnoException {
for (Network network : mCtsNetUtils.getTestableNetworks()) {
int errno = runGetaddrinfoCheck(network.getNetworkHandle());
@@ -82,6 +97,7 @@
}
}
+ @Test
@AppModeFull(reason = "CHANGE_NETWORK_STATE permission can't be granted to instant apps")
public void testSetprocnetwork() throws ErrnoException {
// Hopefully no prior test in this process space has set a default network.
@@ -125,6 +141,7 @@
}
}
+ @Test
@AppModeFull(reason = "CHANGE_NETWORK_STATE permission can't be granted to instant apps")
public void testSetsocknetwork() throws ErrnoException {
for (Network network : mCtsNetUtils.getTestableNetworks()) {
@@ -136,6 +153,7 @@
}
}
+ @Test
public void testNativeDatagramTransmission() throws ErrnoException {
for (Network network : mCtsNetUtils.getTestableNetworks()) {
int errno = runDatagramCheck(network.getNetworkHandle());
@@ -146,6 +164,7 @@
}
}
+ @Test
public void testNoSuchNetwork() {
final Network eNoNet = new Network(54321);
assertNull(mCM.getNetworkInfo(eNoNet));
@@ -158,6 +177,7 @@
// assertEquals(-OsConstants.ENONET, runGetaddrinfoCheck(eNoNetHandle));
}
+ @Test
public void testNetworkHandle() {
// Test Network -> NetworkHandle -> Network results in the same Network.
for (Network network : mCtsNetUtils.getTestableNetworks()) {
@@ -181,6 +201,7 @@
} catch (IllegalArgumentException e) {}
}
+ @Test
public void testResNApi() throws Exception {
final Network[] testNetworks = mCtsNetUtils.getTestableNetworks();
@@ -201,9 +222,21 @@
}
}
+ @Test
@AppModeFull(reason = "WRITE_SECURE_SETTINGS permission can't be granted to instant apps")
- public void testResNApiNXDomainPrivateDns() throws InterruptedException {
+ public void testResNApiNXDomainPrivateDns() throws Exception {
+ // Use async private DNS resolution to avoid flakes due to races applying the setting
+ mDeviceConfigRule.setConfig(NAMESPACE_CONNECTIVITY,
+ "networkmonitor_async_privdns_resolution", "1");
+ mCtsNetUtils.reconnectWifiIfSupported();
+ mCtsNetUtils.reconnectCellIfSupported();
+
mCtsNetUtils.storePrivateDnsSetting();
+
+ mDeviceConfigRule.runAfterNextCleanup(() -> {
+ mCtsNetUtils.reconnectWifiIfSupported();
+ mCtsNetUtils.reconnectCellIfSupported();
+ });
// Enable private DNS strict mode and set server to dns.google before doing NxDomain test.
// b/144521720
try {
diff --git a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
index 594f3fb..6ec4e62 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;
@@ -62,6 +64,7 @@
import com.android.networkstack.apishim.NetworkRequestShimImpl;
import com.android.networkstack.apishim.common.NetworkRequestShim;
import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
+import com.android.testutils.ConnectivityModuleTest;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
@@ -72,6 +75,7 @@
import java.util.Set;
@RunWith(AndroidJUnit4.class)
+@ConnectivityModuleTest
public class NetworkRequestTest {
@Rule
public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
@@ -173,6 +177,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 9aa3c84..ce2c2c1 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -114,7 +114,6 @@
import kotlin.math.min
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
-import kotlin.test.assertNotEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.fail
@@ -127,7 +126,6 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import kotlin.test.assertNotEquals
private const val TAG = "NsdManagerTest"
private const val TIMEOUT_MS = 2000L
@@ -686,6 +684,48 @@
}
}
+ @Test
+ fun testRegisterService_twoServicesWithSameNameButDifferentTypes_registeredAndDiscoverable() {
+ val si1 = NsdServiceInfo().also {
+ it.network = testNetwork1.network
+ it.serviceName = serviceName
+ it.serviceType = serviceType
+ it.port = TEST_PORT
+ }
+ val si2 = NsdServiceInfo().also {
+ it.network = testNetwork1.network
+ it.serviceName = serviceName
+ it.serviceType = serviceType2
+ it.port = TEST_PORT + 1
+ }
+ val registrationRecord1 = NsdRegistrationRecord()
+ val registrationRecord2 = NsdRegistrationRecord()
+ val discoveryRecord1 = NsdDiscoveryRecord()
+ val discoveryRecord2 = NsdDiscoveryRecord()
+ tryTest {
+ registerService(registrationRecord1, si1)
+ registerService(registrationRecord2, si2)
+
+ nsdManager.discoverServices(serviceType,
+ NsdManager.PROTOCOL_DNS_SD,
+ testNetwork1.network, Executor { it.run() }, discoveryRecord1)
+ nsdManager.discoverServices(serviceType2,
+ NsdManager.PROTOCOL_DNS_SD,
+ testNetwork1.network, Executor { it.run() }, discoveryRecord2)
+
+ discoveryRecord1.waitForServiceDiscovered(serviceName, serviceType,
+ testNetwork1.network)
+ discoveryRecord2.waitForServiceDiscovered(serviceName, serviceType2,
+ testNetwork1.network)
+ } cleanupStep {
+ nsdManager.stopServiceDiscovery(discoveryRecord1)
+ nsdManager.stopServiceDiscovery(discoveryRecord2)
+ } cleanup {
+ nsdManager.unregisterService(registrationRecord1)
+ nsdManager.unregisterService(registrationRecord2)
+ }
+ }
+
fun checkOffloadServiceInfo(serviceInfo: OffloadServiceInfo, si: NsdServiceInfo) {
val expectedServiceType = si.serviceType.split(",")[0]
assertEquals(si.serviceName, serviceInfo.key.serviceName)
@@ -1108,6 +1148,51 @@
}
@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"
@@ -1631,6 +1716,177 @@
}
}
+ @Test
+ fun testReplyWhenKnownAnswerSuppressionFlagSet() {
+ // The flag may be removed in the future but known-answer suppression should be enabled by
+ // default in that case. The rule will reset flags automatically on teardown.
+ deviceConfigRule.setConfig(NAMESPACE_TETHERING, "test_nsd_known_answer_suppression", "1")
+ deviceConfigRule.setConfig(NAMESPACE_TETHERING, "test_nsd_unicast_reply_enabled", "1")
+
+ val si = makeTestServiceInfo(testNetwork1.network)
+
+ // Register service on testNetwork1
+ val registrationRecord = NsdRegistrationRecord()
+ var nsResponder: NSResponder? = null
+ tryTest {
+ registerService(registrationRecord, si)
+ val packetReader = TapPacketReader(Handler(handlerThread.looper),
+ testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
+ packetReader.startAsyncForTest()
+
+ handlerThread.waitForIdle(TIMEOUT_MS)
+ /*
+ Send a query with a known answer. Expect to receive a response containing TXT record
+ only.
+ Generated with:
+ scapy.raw(scapy.DNS(rd=0, qr=0, aa=0, qd =
+ scapy.DNSQR(qname='_nmt123456789._tcp.local', qtype='PTR',
+ qclass=0x8001) /
+ scapy.DNSQR(qname='NsdTest123456789._nmt123456789._tcp.local', qtype='TXT',
+ qclass=0x8001),
+ an = scapy.DNSRR(rrname='_nmt123456789._tcp.local', type='PTR', ttl=4500,
+ rdata='NsdTest123456789._nmt123456789._tcp.local')
+ )).hex()
+ */
+ val query = HexDump.hexStringToByteArray("0000000000020001000000000d5f6e6d74313233343" +
+ "536373839045f746370056c6f63616c00000c8001104e7364546573743132333435363738390" +
+ "d5f6e6d74313233343536373839045f746370056c6f63616c00001080010d5f6e6d743132333" +
+ "43536373839045f746370056c6f63616c00000c000100001194002b104e73645465737431323" +
+ "33435363738390d5f6e6d74313233343536373839045f746370056c6f63616c00")
+ replaceServiceNameAndTypeWithTestSuffix(query)
+
+ val testSrcAddr = makeLinkLocalAddressOfOtherDeviceOnPrefix(testNetwork1.network)
+ nsResponder = NSResponder(packetReader, mapOf(
+ testSrcAddr to MacAddress.fromString("01:02:03:04:05:06")
+ )).apply { start() }
+
+ packetReader.sendResponse(buildMdnsPacket(query, testSrcAddr))
+ // The reply is sent unicast to the source address. There may be announcements sent
+ // multicast around this time, so filter by destination address.
+ val reply = packetReader.pollForMdnsPacket { pkt ->
+ pkt.isReplyFor("$serviceName.$serviceType.local", DnsResolver.TYPE_TXT) &&
+ !pkt.isReplyFor("$serviceType.local", DnsResolver.TYPE_PTR) &&
+ pkt.dstAddr == testSrcAddr
+ }
+ assertNotNull(reply)
+
+ /*
+ Send a query with a known answer (TTL is less than half). Expect to receive a response
+ containing both PTR and TXT records.
+ Generated with:
+ scapy.raw(scapy.DNS(rd=0, qr=0, aa=0, qd =
+ scapy.DNSQR(qname='_nmt123456789._tcp.local', qtype='PTR',
+ qclass=0x8001) /
+ scapy.DNSQR(qname='NsdTest123456789._nmt123456789._tcp.local', qtype='TXT',
+ qclass=0x8001),
+ an = scapy.DNSRR(rrname='_nmt123456789._tcp.local', type='PTR', ttl=2150,
+ rdata='NsdTest123456789._nmt123456789._tcp.local')
+ )).hex()
+ */
+ val query2 = HexDump.hexStringToByteArray("0000000000020001000000000d5f6e6d7431323334" +
+ "3536373839045f746370056c6f63616c00000c8001104e736454657374313233343536373839" +
+ "0d5f6e6d74313233343536373839045f746370056c6f63616c00001080010d5f6e6d74313233" +
+ "343536373839045f746370056c6f63616c00000c000100000866002b104e7364546573743132" +
+ "333435363738390d5f6e6d74313233343536373839045f746370056c6f63616c00")
+ replaceServiceNameAndTypeWithTestSuffix(query2)
+
+ packetReader.sendResponse(buildMdnsPacket(query2, testSrcAddr))
+ // The reply is sent unicast to the source address. There may be announcements sent
+ // multicast around this time, so filter by destination address.
+ val reply2 = packetReader.pollForMdnsPacket { pkt ->
+ pkt.isReplyFor("$serviceName.$serviceType.local", DnsResolver.TYPE_TXT) &&
+ pkt.isReplyFor("$serviceType.local", DnsResolver.TYPE_PTR) &&
+ pkt.dstAddr == testSrcAddr
+ }
+ assertNotNull(reply2)
+ } cleanup {
+ nsResponder?.stop()
+ nsdManager.unregisterService(registrationRecord)
+ registrationRecord.expectCallback<ServiceUnregistered>()
+ }
+ }
+
+ @Test
+ fun testReplyWithMultipacketWhenKnownAnswerSuppressionFlagSet() {
+ // The flag may be removed in the future but known-answer suppression should be enabled by
+ // default in that case. The rule will reset flags automatically on teardown.
+ deviceConfigRule.setConfig(NAMESPACE_TETHERING, "test_nsd_known_answer_suppression", "1")
+ deviceConfigRule.setConfig(NAMESPACE_TETHERING, "test_nsd_unicast_reply_enabled", "1")
+
+ val si = makeTestServiceInfo(testNetwork1.network)
+
+ // Register service on testNetwork1
+ val registrationRecord = NsdRegistrationRecord()
+ var nsResponder: NSResponder? = null
+ tryTest {
+ registerService(registrationRecord, si)
+ val packetReader = TapPacketReader(Handler(handlerThread.looper),
+ testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
+ packetReader.startAsyncForTest()
+
+ handlerThread.waitForIdle(TIMEOUT_MS)
+ /*
+ Send a query with truncated bit set.
+ Generated with:
+ scapy.raw(scapy.DNS(rd=0, qr=0, aa=0, tc=1, qd=
+ scapy.DNSQR(qname='_nmt123456789._tcp.local', qtype='PTR',
+ qclass=0x8001) /
+ scapy.DNSQR(qname='NsdTest123456789._nmt123456789._tcp.local', qtype='TXT',
+ qclass=0x8001)
+ )).hex()
+ */
+ val query = HexDump.hexStringToByteArray("0000020000020000000000000d5f6e6d74313233343" +
+ "536373839045f746370056c6f63616c00000c8001104e7364546573743132333435363738390" +
+ "d5f6e6d74313233343536373839045f746370056c6f63616c0000108001")
+ replaceServiceNameAndTypeWithTestSuffix(query)
+ /*
+ Send a known answer packet (other service) with truncated bit set.
+ Generated with:
+ scapy.raw(scapy.DNS(rd=0, qr=0, aa=0, tc=1, qd=None,
+ an = scapy.DNSRR(rrname='_test._tcp.local', type='PTR', ttl=4500,
+ rdata='NsdTest._test._tcp.local')
+ )).hex()
+ */
+ val knownAnswer1 = HexDump.hexStringToByteArray("000002000000000100000000055f74657374" +
+ "045f746370056c6f63616c00000c000100001194001a074e736454657374055f74657374045f" +
+ "746370056c6f63616c00")
+ replaceServiceNameAndTypeWithTestSuffix(knownAnswer1)
+ /*
+ Send a known answer packet.
+ Generated with:
+ scapy.raw(scapy.DNS(rd=0, qr=0, aa=0, qd=None,
+ an = scapy.DNSRR(rrname='_nmt123456789._tcp.local', type='PTR', ttl=4500,
+ rdata='NsdTest123456789._nmt123456789._tcp.local')
+ )).hex()
+ */
+ val knownAnswer2 = HexDump.hexStringToByteArray("0000000000000001000000000d5f6e6d7431" +
+ "3233343536373839045f746370056c6f63616c00000c000100001194002b104e736454657374" +
+ "3132333435363738390d5f6e6d74313233343536373839045f746370056c6f63616c00")
+ replaceServiceNameAndTypeWithTestSuffix(knownAnswer2)
+
+ val testSrcAddr = makeLinkLocalAddressOfOtherDeviceOnPrefix(testNetwork1.network)
+ nsResponder = NSResponder(packetReader, mapOf(
+ testSrcAddr to MacAddress.fromString("01:02:03:04:05:06")
+ )).apply { start() }
+
+ packetReader.sendResponse(buildMdnsPacket(query, testSrcAddr))
+ packetReader.sendResponse(buildMdnsPacket(knownAnswer1, testSrcAddr))
+ packetReader.sendResponse(buildMdnsPacket(knownAnswer2, testSrcAddr))
+ // The reply is sent unicast to the source address. There may be announcements sent
+ // multicast around this time, so filter by destination address.
+ val reply = packetReader.pollForMdnsPacket { pkt ->
+ pkt.isReplyFor("$serviceName.$serviceType.local", DnsResolver.TYPE_TXT) &&
+ !pkt.isReplyFor("$serviceType.local", DnsResolver.TYPE_PTR) &&
+ pkt.dstAddr == testSrcAddr
+ }
+ assertNotNull(reply)
+ } cleanup {
+ nsResponder?.stop()
+ nsdManager.unregisterService(registrationRecord)
+ registrationRecord.expectCallback<ServiceUnregistered>()
+ }
+ }
+
private fun makeLinkLocalAddressOfOtherDeviceOnPrefix(network: Network): Inet6Address {
val lp = cm.getLinkProperties(network) ?: fail("No LinkProperties for net $network")
// Expect to have a /64 link-local address
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/tethering/src/android/tethering/cts/TetheringManagerTest.java b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
index 274596f..81608f7 100644
--- a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
+++ b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
@@ -71,6 +71,8 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.android.testutils.ParcelUtils;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -236,6 +238,26 @@
}
@Test
+ public void testTetheringRequestParcelable() {
+ final LinkAddress localAddr = new LinkAddress("192.168.24.5/24");
+ final LinkAddress clientAddr = new LinkAddress("192.168.24.100/24");
+ final TetheringRequest unparceled = new TetheringRequest.Builder(TETHERING_USB)
+ .setStaticIpv4Addresses(localAddr, clientAddr)
+ .setExemptFromEntitlementCheck(true)
+ .setShouldShowEntitlementUi(false).build();
+ final TetheringRequest parceled = ParcelUtils.parcelingRoundTrip(unparceled);
+ assertEquals(unparceled.getTetheringType(), parceled.getTetheringType());
+ assertEquals(unparceled.getConnectivityScope(), parceled.getConnectivityScope());
+ assertEquals(unparceled.getLocalIpv4Address(), parceled.getLocalIpv4Address());
+ assertEquals(unparceled.getClientStaticIpv4Address(),
+ parceled.getClientStaticIpv4Address());
+ assertEquals(unparceled.isExemptFromEntitlementCheck(),
+ parceled.isExemptFromEntitlementCheck());
+ assertEquals(unparceled.getShouldShowEntitlementUi(),
+ parceled.getShouldShowEntitlementUi());
+ }
+
+ @Test
public void testRegisterTetheringEventCallback() throws Exception {
final TestTetheringEventCallback tetherEventCallback =
mCtsTetheringUtils.registerTetheringEventCallback();
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..361d68c 100644
--- a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
+++ b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
@@ -60,6 +60,7 @@
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 +87,8 @@
import org.mockito.Mockito.mock
import org.mockito.MockitoAnnotations
import org.mockito.Spy
+import java.util.function.Consumer
+import java.util.function.BiConsumer
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: BiConsumer<Int, Int>
): 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/native/utilities/Android.bp b/tests/native/utilities/Android.bp
index 2f761d7..48a5414 100644
--- a/tests/native/utilities/Android.bp
+++ b/tests/native/utilities/Android.bp
@@ -18,8 +18,10 @@
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",
diff --git a/tests/native/utilities/firewall.cpp b/tests/native/utilities/firewall.cpp
index 669b76a..34b4f07 100644
--- a/tests/native/utilities/firewall.cpp
+++ b/tests/native/utilities/firewall.cpp
@@ -60,10 +60,10 @@
// iif should be non-zero if and only if match == MATCH_IIF
if (match == IIF_MATCH && iif == 0) {
return Errorf("Interface match {} must have nonzero interface index",
- static_cast<int>(match));
+ static_cast<uint32_t>(match));
} else if (match != IIF_MATCH && iif != 0) {
return Errorf("Non-interface match {} must have zero interface index",
- static_cast<int>(match));
+ static_cast<uint32_t>(match));
}
std::lock_guard guard(mMutex);
@@ -71,14 +71,14 @@
if (oldMatch.ok()) {
UidOwnerValue newMatch = {
.iif = iif ? iif : oldMatch.value().iif,
- .rule = static_cast<uint8_t>(oldMatch.value().rule | match),
+ .rule = oldMatch.value().rule | match,
};
auto res = mUidOwnerMap.writeValue(uid, newMatch, BPF_ANY);
if (!res.ok()) return Errorf("Failed to update rule: {}", res.error().message());
} else {
UidOwnerValue newMatch = {
.iif = iif,
- .rule = static_cast<uint8_t>(match),
+ .rule = match,
};
auto res = mUidOwnerMap.writeValue(uid, newMatch, BPF_ANY);
if (!res.ok()) return Errorf("Failed to add rule: {}", res.error().message());
@@ -93,7 +93,7 @@
UidOwnerValue newMatch = {
.iif = (match == IIF_MATCH) ? 0 : oldMatch.value().iif,
- .rule = static_cast<uint8_t>(oldMatch.value().rule & ~match),
+ .rule = oldMatch.value().rule & ~match,
};
if (newMatch.rule == 0) {
auto res = mUidOwnerMap.deleteValue(uid);
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/metrics/ConnectivitySampleMetricsTest.kt b/tests/unit/java/com/android/metrics/ConnectivitySampleMetricsTest.kt
index 3043d50..53baee1 100644
--- a/tests/unit/java/com/android/metrics/ConnectivitySampleMetricsTest.kt
+++ b/tests/unit/java/com/android/metrics/ConnectivitySampleMetricsTest.kt
@@ -16,6 +16,7 @@
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
import android.net.NetworkCapabilities.TRANSPORT_WIFI
import android.net.NetworkScore
+import android.net.NetworkScore.KEEP_CONNECTED_FOR_TEST
import android.net.NetworkScore.POLICY_EXITING
import android.net.NetworkScore.POLICY_TRANSPORT_PRIMARY
import android.os.Build
@@ -86,7 +87,10 @@
.addCapability(NET_CAPABILITY_NOT_SUSPENDED)
.addCapability(NET_CAPABILITY_NOT_ROAMING)
.build()
- val wifi1Score = NetworkScore.Builder().setExiting(true).build()
+ val wifi1Score = NetworkScore.Builder()
+ .setKeepConnectedReason(KEEP_CONNECTED_FOR_TEST)
+ .setExiting(true)
+ .build()
val agentWifi1 = Agent(nc = wifi1Caps, score = FromS(wifi1Score)).also { it.connect() }
val wifi2Caps = NetworkCapabilities.Builder()
@@ -96,7 +100,10 @@
.addCapability(NET_CAPABILITY_NOT_ROAMING)
.addEnterpriseId(NET_ENTERPRISE_ID_3)
.build()
- val wifi2Score = NetworkScore.Builder().setTransportPrimary(true).build()
+ val wifi2Score = NetworkScore.Builder()
+ .setKeepConnectedReason(KEEP_CONNECTED_FOR_TEST)
+ .setTransportPrimary(true)
+ .build()
val agentWifi2 = Agent(nc = wifi2Caps, score = FromS(wifi2Score)).also { it.connect() }
val cellCaps = NetworkCapabilities.Builder()
@@ -107,7 +114,9 @@
.addCapability(NET_CAPABILITY_NOT_ROAMING)
.addEnterpriseId(NET_ENTERPRISE_ID_1)
.build()
- val cellScore = NetworkScore.Builder().build()
+ val cellScore = NetworkScore.Builder()
+ .setKeepConnectedReason(KEEP_CONNECTED_FOR_TEST)
+ .build()
val agentCell = Agent(nc = cellCaps, score = FromS(cellScore)).also { it.connect() }
val stats = csHandler.onHandler { service.sampleConnectivityState() }
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 5562b67..c534025 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;
@@ -420,6 +421,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;
@@ -485,6 +487,7 @@
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
@@ -523,7 +526,7 @@
// between a LOST callback that arrives immediately and a LOST callback that arrives after
// the linger/nascent timeout. For this, our assertions should run fast enough to leave
// less than (mService.mLingerDelayMs - TEST_CALLBACK_TIMEOUT_MS) between the time callbacks are
- // supposedly fired, and the time we call expectCallback.
+ // supposedly fired, and the time we call expectCapChanged.
private static final int TEST_CALLBACK_TIMEOUT_MS = 250;
// Chosen to be less than TEST_CALLBACK_TIMEOUT_MS. This ensures that requests have time to
// complete before callbacks are verified.
@@ -562,6 +565,7 @@
private static final int TEST_PACKAGE_UID2 = 321;
private static final int TEST_PACKAGE_UID3 = 456;
private static final int NETWORK_ACTIVITY_NO_UID = -1;
+ private static final int TEST_SUBSCRIPTION_ID = 1;
private static final int PACKET_WAKEUP_MARK_MASK = 0x80000000;
@@ -641,6 +645,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 +2058,21 @@
@Override
public CarrierPrivilegeAuthenticator makeCarrierPrivilegeAuthenticator(
@NonNull final Context context,
- @NonNull final TelephonyManager tm) {
+ @NonNull final TelephonyManager tm,
+ final boolean requestRestrictedWifiEnabled,
+ BiConsumer<Integer, Integer> 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 +2162,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 +2180,8 @@
return true;
case LOG_BPF_RC:
return true;
+ case ALLOW_SATALLITE_NETWORK_FALLBACK:
+ return true;
default:
return super.isFeatureNotChickenedOut(context, name);
}
@@ -11468,7 +11487,7 @@
doTestInterfaceClassActivityChanged(TRANSPORT_CELLULAR);
}
- private void doTestOnNetworkActive_NewNetworkConnects(int transportType, boolean expectCallback)
+ private void doTestOnNetworkActive_NewNetworkConnects(int transportType, boolean expectCapChanged)
throws Exception {
final ConditionVariable onNetworkActiveCv = new ConditionVariable();
final ConnectivityManager.OnNetworkActiveListener listener = onNetworkActiveCv::open;
@@ -11480,7 +11499,7 @@
testAndCleanup(() -> {
mCm.addDefaultNetworkActiveListener(listener);
agent.connect(true);
- if (expectCallback) {
+ if (expectCapChanged) {
assertTrue(onNetworkActiveCv.block(TEST_CALLBACK_TIMEOUT_MS));
} else {
assertFalse(onNetworkActiveCv.block(TEST_CALLBACK_TIMEOUT_MS));
@@ -11495,7 +11514,7 @@
@Test
public void testOnNetworkActive_NewCellConnects_CallbackCalled() throws Exception {
- doTestOnNetworkActive_NewNetworkConnects(TRANSPORT_CELLULAR, true /* expectCallback */);
+ doTestOnNetworkActive_NewNetworkConnects(TRANSPORT_CELLULAR, true /* expectCapChanged */);
}
@Test
@@ -11504,8 +11523,8 @@
// networks that tracker adds the idle timer to. And the tracker does not set the idle timer
// for the ethernet network.
// So onNetworkActive is not called when the ethernet becomes the default network
- final boolean expectCallback = mDeps.isAtLeastV();
- doTestOnNetworkActive_NewNetworkConnects(TRANSPORT_ETHERNET, expectCallback);
+ final boolean expectCapChanged = mDeps.isAtLeastV();
+ doTestOnNetworkActive_NewNetworkConnects(TRANSPORT_ETHERNET, expectCapChanged);
}
@Test
@@ -17353,6 +17372,14 @@
.build();
}
+ private NetworkRequest getRestrictedRequestForWifiWithSubIds() {
+ return new NetworkRequest.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+ .setSubscriptionIds(Collections.singleton(TEST_SUBSCRIPTION_ID))
+ .build();
+ }
+
@Test
public void testNetworkRequestWithSubIdsWithNetworkFactoryPermission() throws Exception {
mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_GRANTED);
@@ -17386,6 +17413,141 @@
}
@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);
+ }
+
+ private void doTestNetworkRequestWithCarrierPrivilegesLost(
+ boolean shouldGrantRestrictedNetworkPermission,
+ int lostPrivilegeUid,
+ int lostPrivilegeSubId,
+ boolean expectUnavailable,
+ boolean expectCapChanged) throws Exception {
+ if (shouldGrantRestrictedNetworkPermission) {
+ mServiceContext.setPermission(CONNECTIVITY_USE_RESTRICTED_NETWORKS, PERMISSION_GRANTED);
+ } else {
+ 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 networkCallback = new TestNetworkCallback();
+ final NetworkRequest networkrequest =
+ getRestrictedRequestForWifiWithSubIds();
+ mCm.requestNetwork(networkrequest, networkCallback);
+ 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(false);
+ networkCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+ final NetworkAgentInfo nai = mService.getNetworkAgentInfoForNetwork(
+ mWiFiAgent.getNetwork());
+
+ doReturn(false).when(mCarrierPrivilegeAuthenticator)
+ .isCarrierServiceUidForNetworkCapabilities(eq(Process.myUid()), any());
+ doReturn(TEST_SUBSCRIPTION_ID).when(mCarrierPrivilegeAuthenticator)
+ .getSubIdFromNetworkCapabilities(any());
+ mService.onCarrierPrivilegesLost(lostPrivilegeUid, lostPrivilegeSubId);
+ waitForIdle();
+
+ if (expectCapChanged) {
+ networkCallback.expect(NETWORK_CAPS_UPDATED);
+ }
+ if (expectUnavailable) {
+ networkCallback.expect(UNAVAILABLE);
+ }
+ if (!expectCapChanged && !expectUnavailable) {
+ networkCallback.assertNoCallback();
+ }
+
+ mWiFiAgent.disconnect();
+ waitForIdle();
+
+ if (expectUnavailable) {
+ testFactory.assertRequestCountEquals(0);
+ } else {
+ testFactory.assertRequestCountEquals(1);
+ }
+
+ handlerThread.quitSafely();
+ handlerThread.join();
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+ public void testRestrictedRequestRemovedDueToCarrierPrivilegesLost() throws Exception {
+ doTestNetworkRequestWithCarrierPrivilegesLost(
+ false /* shouldGrantRestrictedNetworkPermission */,
+ Process.myUid(),
+ TEST_SUBSCRIPTION_ID,
+ true /* expectUnavailable */,
+ true /* expectCapChanged */);
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+ public void testRequestNotRemoved_MismatchSubId() throws Exception {
+ doTestNetworkRequestWithCarrierPrivilegesLost(
+ false /* shouldGrantRestrictedNetworkPermission */,
+ Process.myUid(),
+ TEST_SUBSCRIPTION_ID + 1,
+ false /* expectUnavailable */,
+ false /* expectCapChanged */);
+ }
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+ public void testRequestNotRemoved_MismatchUid() throws Exception {
+ doTestNetworkRequestWithCarrierPrivilegesLost(
+ false /* shouldGrantRestrictedNetworkPermission */,
+ Process.myUid() + 1,
+ TEST_SUBSCRIPTION_ID,
+ false /* expectUnavailable */,
+ false /* expectCapChanged */);
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+ public void testRequestNotRemoved_HasRestrictedNetworkPermission() throws Exception {
+ doTestNetworkRequestWithCarrierPrivilegesLost(
+ true /* shouldGrantRestrictedNetworkPermission */,
+ Process.myUid(),
+ TEST_SUBSCRIPTION_ID,
+ false /* expectUnavailable */,
+ true /* expectCapChanged */);
+ }
+ @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 b60f0b4..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;
@@ -257,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.
@@ -717,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)
diff --git a/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java b/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java
index f07593e..7bd2b56 100644
--- a/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java
@@ -46,7 +46,6 @@
import android.net.TelephonyNetworkSpecifier;
import android.os.Build;
import android.os.HandlerThread;
-import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import com.android.net.module.util.CollectionUtils;
@@ -54,10 +53,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 +68,8 @@
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
+import java.util.Set;
+import java.util.function.BiConsumer;
/**
* Tests for CarrierPrivilegeAuthenticatorTest.
@@ -77,6 +80,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 +91,9 @@
@NonNull private final TelephonyManagerShimImpl mTelephonyManagerShim;
@NonNull private final PackageManager mPackageManager;
@NonNull private TestCarrierPrivilegeAuthenticator mCarrierPrivilegeAuthenticator;
+ @NonNull private final BiConsumer<Integer, Integer> 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,12 +102,12 @@
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) {
- if (SubscriptionManager.DEFAULT_SUBSCRIPTION_ID == subId) return TEST_SUBSCRIPTION_ID;
- return subId;
+ protected int getSubId(int slotIndex) {
+ return TEST_SUBSCRIPTION_ID;
}
}
@@ -119,7 +127,9 @@
mTelephonyManager = mock(TelephonyManager.class);
mTelephonyManagerShim = mock(TelephonyManagerShimImpl.class);
mPackageManager = mock(PackageManager.class);
+ mListener = mock(BiConsumer.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));
@@ -172,7 +182,7 @@
final NetworkCapabilities.Builder ncBuilder = new NetworkCapabilities.Builder()
.addTransportType(TRANSPORT_CELLULAR)
- .setNetworkSpecifier(new TelephonyNetworkSpecifier(0));
+ .setNetworkSpecifier(new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID));
assertTrue(mCarrierPrivilegeAuthenticator.isCarrierServiceUidForNetworkCapabilities(
mCarrierConfigPkgUid, ncBuilder.build()));
@@ -208,7 +218,8 @@
newListeners.get(0).onCarrierServiceChanged(null, mCarrierConfigPkgUid);
- final TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier(0);
+ final TelephonyNetworkSpecifier specifier =
+ new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID);
final NetworkCapabilities nc = new NetworkCapabilities.Builder()
.addTransportType(TRANSPORT_CELLULAR)
.setNetworkSpecifier(specifier)
@@ -220,10 +231,27 @@
}
@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).accept(eq(mCarrierConfigPkgUid), eq(TEST_SUBSCRIPTION_ID));
+ }
+ l.onCarrierServiceChanged(null, mCarrierConfigPkgUid + 2);
+ if (mUseCallbacks) {
+ verify(mListener).accept(eq(mCarrierConfigPkgUid + 1), eq(TEST_SUBSCRIPTION_ID));
+ }
+ }
+
+ @Test
public void testOnCarrierPrivilegesChanged() throws Exception {
final CarrierPrivilegesListenerShim listener = getCarrierPrivilegesListeners().get(0);
- final TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier(0);
+ final TelephonyNetworkSpecifier specifier =
+ new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID);
final NetworkCapabilities nc = new NetworkCapabilities.Builder()
.addTransportType(TRANSPORT_CELLULAR)
.setNetworkSpecifier(specifier)
@@ -251,7 +279,7 @@
assertFalse(mCarrierPrivilegeAuthenticator.isCarrierServiceUidForNetworkCapabilities(
mCarrierConfigPkgUid, ncBuilder.build()));
- ncBuilder.setNetworkSpecifier(new TelephonyNetworkSpecifier(0));
+ ncBuilder.setNetworkSpecifier(new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID));
assertTrue(mCarrierPrivilegeAuthenticator.isCarrierServiceUidForNetworkCapabilities(
mCarrierConfigPkgUid, ncBuilder.build()));
@@ -260,7 +288,35 @@
ncBuilder.setNetworkSpecifier(null);
ncBuilder.removeTransportType(TRANSPORT_CELLULAR);
ncBuilder.addTransportType(TRANSPORT_WIFI);
- ncBuilder.setNetworkSpecifier(new TelephonyNetworkSpecifier(0));
+ ncBuilder.setNetworkSpecifier(new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID));
+ 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(TEST_SUBSCRIPTION_ID));
+ 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/SatelliteAccessControllerTest.kt b/tests/unit/java/com/android/server/connectivity/SatelliteAccessControllerTest.kt
new file mode 100644
index 0000000..193078b
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/SatelliteAccessControllerTest.kt
@@ -0,0 +1,222 @@
+/*
+ * 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.content.pm.UserInfo
+import android.os.Build
+import android.os.Handler
+import android.os.UserHandle
+import android.util.ArraySet
+import com.android.server.makeMockUserManager
+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.verify
+import java.util.concurrent.Executor
+import java.util.function.Consumer
+
+private const val USER = 0
+val USER_INFO = UserInfo(USER, "" /* name */, UserInfo.FLAG_PRIMARY)
+val USER_HANDLE = UserHandle(USER)
+private const val PRIMARY_USER = 0
+private const val SECONDARY_USER = 10
+private val PRIMARY_USER_HANDLE = UserHandle.of(PRIMARY_USER)
+private val SECONDARY_USER_HANDLE = UserHandle.of(SECONDARY_USER)
+// sms app names
+private const val SMS_APP1 = "sms_app_1"
+private const val SMS_APP2 = "sms_app_2"
+// sms app ids
+private const val SMS_APP_ID1 = 100
+private const val SMS_APP_ID2 = 101
+// UID for app1 and app2 on primary user
+// These app could become default sms app for user1
+private val PRIMARY_USER_SMS_APP_UID1 = UserHandle.getUid(PRIMARY_USER, SMS_APP_ID1)
+private val PRIMARY_USER_SMS_APP_UID2 = UserHandle.getUid(PRIMARY_USER, SMS_APP_ID2)
+// UID for app1 and app2 on secondary user
+// These app could become default sms app for user2
+private val SECONDARY_USER_SMS_APP_UID1 = UserHandle.getUid(SECONDARY_USER, SMS_APP_ID1)
+private val SECONDARY_USER_SMS_APP_UID2 = UserHandle.getUid(SECONDARY_USER, SMS_APP_ID2)
+
+@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 =
+ SatelliteAccessController(context, mRoleManager, mCallback, mHandler)
+ private lateinit var mRoleHolderChangedListener: OnRoleHoldersChangedListener
+ @Before
+ @Throws(PackageManager.NameNotFoundException::class)
+ fun setup() {
+ makeMockUserManager(USER_INFO, USER_HANDLE)
+ doReturn(context).`when`(context).createContextAsUser(any(), anyInt())
+ doReturn(mPackageManager).`when`(context).packageManager
+
+ doReturn(PackageManager.PERMISSION_GRANTED)
+ .`when`(mPackageManager)
+ .checkPermission(Manifest.permission.SATELLITE_COMMUNICATION, SMS_APP1)
+ doReturn(PackageManager.PERMISSION_GRANTED)
+ .`when`(mPackageManager)
+ .checkPermission(Manifest.permission.SATELLITE_COMMUNICATION, SMS_APP2)
+
+ // Initialise default message application primary user package1
+ val applicationInfo1 = ApplicationInfo()
+ applicationInfo1.uid = PRIMARY_USER_SMS_APP_UID1
+ doReturn(applicationInfo1)
+ .`when`(mPackageManager)
+ .getApplicationInfo(eq(SMS_APP1), anyInt())
+
+ // Initialise default message application primary user package2
+ val applicationInfo2 = ApplicationInfo()
+ applicationInfo2.uid = PRIMARY_USER_SMS_APP_UID2
+ doReturn(applicationInfo2)
+ .`when`(mPackageManager)
+ .getApplicationInfo(eq(SMS_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_SatelliteFallbackUid_Changed_SingleUser() {
+ doReturn(listOf<String>()).`when`(mRoleManager).getRoleHoldersAsUser(RoleManager.ROLE_SMS,
+ PRIMARY_USER_HANDLE)
+ mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
+ verify(mCallback, never()).accept(any())
+
+ // check DEFAULT_MESSAGING_APP1 is available as satellite network fallback uid
+ doReturn(listOf(SMS_APP1))
+ .`when`(mRoleManager).getRoleHoldersAsUser(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
+ mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
+ verify(mCallback).accept(setOf(PRIMARY_USER_SMS_APP_UID1))
+
+ // check SMS_APP2 is available as satellite network Fallback uid
+ doReturn(listOf(SMS_APP2)).`when`(mRoleManager).getRoleHoldersAsUser(RoleManager.ROLE_SMS,
+ PRIMARY_USER_HANDLE)
+ mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
+ verify(mCallback).accept(setOf(PRIMARY_USER_SMS_APP_UID2))
+
+ // check no uid is available as satellite network fallback uid
+ doReturn(listOf<String>()).`when`(mRoleManager).getRoleHoldersAsUser(RoleManager.ROLE_SMS,
+ PRIMARY_USER_HANDLE)
+ mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
+ verify(mCallback).accept(ArraySet())
+ }
+
+ @Test
+ fun test_onRoleHoldersChanged_NoSatelliteCommunicationPermission() {
+ doReturn(listOf<Any>()).`when`(mRoleManager).getRoleHoldersAsUser(RoleManager.ROLE_SMS,
+ PRIMARY_USER_HANDLE)
+ mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
+ verify(mCallback, never()).accept(any())
+
+ // check DEFAULT_MESSAGING_APP1 is not available as satellite network fallback uid
+ // since satellite communication permission not available.
+ doReturn(PackageManager.PERMISSION_DENIED)
+ .`when`(mPackageManager)
+ .checkPermission(Manifest.permission.SATELLITE_COMMUNICATION, SMS_APP1)
+ doReturn(listOf(SMS_APP1))
+ .`when`(mRoleManager).getRoleHoldersAsUser(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
+ mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
+ verify(mCallback, never()).accept(any())
+ }
+
+ @Test
+ fun test_onRoleHoldersChanged_RoleSms_NotAvailable() {
+ doReturn(listOf(SMS_APP1))
+ .`when`(mRoleManager).getRoleHoldersAsUser(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
+ mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_BROWSER,
+ PRIMARY_USER_HANDLE)
+ verify(mCallback, never()).accept(any())
+ }
+
+ @Test
+ fun test_onRoleHoldersChanged_SatelliteNetworkFallbackUid_Changed_multiUser() {
+ doReturn(listOf<String>()).`when`(mRoleManager).getRoleHoldersAsUser(RoleManager.ROLE_SMS,
+ PRIMARY_USER_HANDLE)
+ mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
+ verify(mCallback, never()).accept(any())
+
+ // check SMS_APP1 is available as satellite network fallback uid at primary user
+ doReturn(listOf(SMS_APP1))
+ .`when`(mRoleManager).getRoleHoldersAsUser(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
+ mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
+ verify(mCallback).accept(setOf(PRIMARY_USER_SMS_APP_UID1))
+
+ // check SMS_APP2 is available as satellite network fallback uid at primary user
+ doReturn(listOf(SMS_APP2)).`when`(mRoleManager).getRoleHoldersAsUser(RoleManager.ROLE_SMS,
+ PRIMARY_USER_HANDLE)
+ mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_SMS, PRIMARY_USER_HANDLE)
+ verify(mCallback).accept(setOf(PRIMARY_USER_SMS_APP_UID2))
+
+ // check SMS_APP1 is available as satellite network fallback uid at secondary user
+ val applicationInfo1 = ApplicationInfo()
+ applicationInfo1.uid = SECONDARY_USER_SMS_APP_UID1
+ doReturn(applicationInfo1).`when`(mPackageManager)
+ .getApplicationInfo(eq(SMS_APP1), anyInt())
+ doReturn(listOf(SMS_APP1)).`when`(mRoleManager).getRoleHoldersAsUser(RoleManager.ROLE_SMS,
+ SECONDARY_USER_HANDLE)
+ mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_SMS, SECONDARY_USER_HANDLE)
+ verify(mCallback).accept(setOf(PRIMARY_USER_SMS_APP_UID2, SECONDARY_USER_SMS_APP_UID1))
+
+ // check no uid is available as satellite network fallback uid at primary user
+ doReturn(listOf<String>()).`when`(mRoleManager).getRoleHoldersAsUser(RoleManager.ROLE_SMS,
+ PRIMARY_USER_HANDLE)
+ mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_SMS,
+ PRIMARY_USER_HANDLE)
+ verify(mCallback).accept(setOf(SECONDARY_USER_SMS_APP_UID1))
+
+ // check SMS_APP2 is available as satellite network fallback uid at secondary user
+ applicationInfo1.uid = SECONDARY_USER_SMS_APP_UID2
+ doReturn(applicationInfo1).`when`(mPackageManager)
+ .getApplicationInfo(eq(SMS_APP2), anyInt())
+ doReturn(listOf(SMS_APP2))
+ .`when`(mRoleManager).getRoleHoldersAsUser(RoleManager.ROLE_SMS, SECONDARY_USER_HANDLE)
+ mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_SMS, SECONDARY_USER_HANDLE)
+ verify(mCallback).accept(setOf(SECONDARY_USER_SMS_APP_UID2))
+
+ // check no uid is available as satellite network fallback uid at secondary user
+ doReturn(listOf<String>()).`when`(mRoleManager).getRoleHoldersAsUser(RoleManager.ROLE_SMS,
+ SECONDARY_USER_HANDLE)
+ mRoleHolderChangedListener.onRoleHoldersChanged(RoleManager.ROLE_SMS, SECONDARY_USER_HANDLE)
+ verify(mCallback).accept(ArraySet())
+ }
+}
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 58124f3..09236b1 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
@@ -43,6 +43,7 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -1207,10 +1208,14 @@
final String ipV4Address = "192.0.2.0";
final String ipV6Address = "2001:db8::";
- final MdnsSearchOptions resolveOptions = MdnsSearchOptions.newBuilder()
+ final MdnsSearchOptions resolveOptions1 = MdnsSearchOptions.newBuilder()
+ .setResolveInstanceName(instanceName).build();
+ final MdnsSearchOptions resolveOptions2 = MdnsSearchOptions.newBuilder()
.setResolveInstanceName(instanceName).build();
- startSendAndReceive(mockListenerOne, resolveOptions);
+ startSendAndReceive(mockListenerOne, resolveOptions1);
+ startSendAndReceive(mockListenerTwo, resolveOptions2);
+ // No need to verify order for both listeners; and order is not guaranteed between them
InOrder inOrder = inOrder(mockListenerOne, mockSocketClient);
// Verify a query for SRV/TXT was sent, but no PTR query
@@ -1223,13 +1228,19 @@
eq(socketKey), eq(false));
verify(mockDeps, times(1)).sendMessage(any(), any(Message.class));
assertNotNull(delayMessage);
+ inOrder.verify(mockListenerOne).onDiscoveryQuerySent(any(), anyInt());
+ verify(mockListenerTwo).onDiscoveryQuerySent(any(), anyInt());
final MdnsPacket srvTxtQueryPacket = MdnsPacket.parse(
new MdnsPacketReader(srvTxtQueryCaptor.getValue()));
final String[] serviceName = getTestServiceName(instanceName);
+ assertEquals(1, srvTxtQueryPacket.questions.size());
assertFalse(hasQuestion(srvTxtQueryPacket, MdnsRecord.TYPE_PTR));
assertTrue(hasQuestion(srvTxtQueryPacket, MdnsRecord.TYPE_ANY, serviceName));
+ assertEquals(0, srvTxtQueryPacket.answers.size());
+ assertEquals(0, srvTxtQueryPacket.authorityRecords.size());
+ assertEquals(0, srvTxtQueryPacket.additionalRecords.size());
// Process a response with SRV+TXT
final MdnsPacket srvTxtResponse = new MdnsPacket(
@@ -1246,6 +1257,10 @@
Collections.emptyList() /* additionalRecords */);
processResponse(srvTxtResponse, socketKey);
+ inOrder.verify(mockListenerOne).onServiceNameDiscovered(
+ matchServiceName(instanceName), eq(false) /* isServiceFromCache */);
+ verify(mockListenerTwo).onServiceNameDiscovered(
+ matchServiceName(instanceName), eq(false) /* isServiceFromCache */);
// Expect a query for A/AAAA
dispatchMessage();
@@ -1255,11 +1270,18 @@
inOrder.verify(mockSocketClient, times(2)).sendPacketRequestingMulticastResponse(
addressQueryCaptor.capture(),
eq(socketKey), eq(false));
+ inOrder.verify(mockListenerOne).onDiscoveryQuerySent(any(), anyInt());
+ // onDiscoveryQuerySent was called 2 times in total
+ verify(mockListenerTwo, times(2)).onDiscoveryQuerySent(any(), anyInt());
final MdnsPacket addressQueryPacket = MdnsPacket.parse(
new MdnsPacketReader(addressQueryCaptor.getValue()));
+ assertEquals(2, addressQueryPacket.questions.size());
assertTrue(hasQuestion(addressQueryPacket, MdnsRecord.TYPE_A, hostname));
assertTrue(hasQuestion(addressQueryPacket, MdnsRecord.TYPE_AAAA, hostname));
+ assertEquals(0, addressQueryPacket.answers.size());
+ assertEquals(0, addressQueryPacket.authorityRecords.size());
+ assertEquals(0, addressQueryPacket.additionalRecords.size());
// Process a response with address records
final MdnsPacket addressResponse = new MdnsPacket(
@@ -1276,10 +1298,12 @@
Collections.emptyList() /* additionalRecords */);
inOrder.verify(mockListenerOne, never()).onServiceNameDiscovered(any(), anyBoolean());
+ verifyNoMoreInteractions(mockListenerTwo);
processResponse(addressResponse, socketKey);
inOrder.verify(mockListenerOne).onServiceFound(
serviceInfoCaptor.capture(), eq(false) /* isServiceFromCache */);
+ verify(mockListenerTwo).onServiceFound(any(), anyBoolean());
verifyServiceInfo(serviceInfoCaptor.getValue(),
instanceName,
SERVICE_TYPE_LABELS,
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSCaptivePortalAppTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSCaptivePortalAppTest.kt
new file mode 100644
index 0000000..be2b29c
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivityservice/CSCaptivePortalAppTest.kt
@@ -0,0 +1,127 @@
+/*
+ * 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
+
+import android.Manifest.permission.NETWORK_STACK
+import android.content.Intent
+import android.content.pm.PackageManager.PERMISSION_DENIED
+import android.content.pm.PackageManager.PERMISSION_GRANTED
+import android.net.ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN
+import android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL
+import android.net.IpPrefix
+import android.net.LinkAddress
+import android.net.LinkProperties
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL
+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.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.NetworkStack
+import android.net.CaptivePortal
+import android.net.NetworkRequest
+import android.net.NetworkScore
+import android.net.NetworkScore.KEEP_CONNECTED_FOR_TEST
+import android.net.RouteInfo
+import android.os.Build
+import android.os.Bundle
+import androidx.test.filters.SmallTest
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.assertThrows
+import com.android.testutils.TestableNetworkCallback
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+import kotlin.test.assertEquals
+
+// 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()
+)
+
+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))
+}
+
+@DevSdkIgnoreRunner.MonitorThreadLeak
+@RunWith(DevSdkIgnoreRunner::class)
+@SmallTest
+@IgnoreUpTo(Build.VERSION_CODES.R)
+class CSCaptivePortalAppTest : CSTest() {
+ private val WIFI_IFACE = "wifi0"
+ private val TEST_REDIRECT_URL = "http://example.com/firstPath"
+ private val TIMEOUT_MS = 2_000L
+
+ @Test
+ fun testCaptivePortalApp_Reevaluate_Nopermission() {
+ val captivePortalCallback = TestableNetworkCallback()
+ val captivePortalRequest = NetworkRequest.Builder()
+ .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build()
+ cm.registerNetworkCallback(captivePortalRequest, captivePortalCallback)
+ val wifiAgent = createWifiAgent()
+ wifiAgent.connectWithCaptivePortal(TEST_REDIRECT_URL)
+ captivePortalCallback.expectAvailableCallbacksUnvalidated(wifiAgent)
+ val signInIntent = startCaptivePortalApp(wifiAgent)
+ // Remove the granted permissions
+ context.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ PERMISSION_DENIED)
+ context.setPermission(NETWORK_STACK, PERMISSION_DENIED)
+ val captivePortal: CaptivePortal? = signInIntent.getParcelableExtra(EXTRA_CAPTIVE_PORTAL)
+ assertThrows(SecurityException::class.java, { captivePortal?.reevaluateNetwork() })
+ }
+
+ private fun createWifiAgent(): CSAgentWrapper {
+ return Agent(score = keepScore(), lp = lp(WIFI_IFACE),
+ nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET))
+ }
+
+ private fun startCaptivePortalApp(networkAgent: CSAgentWrapper): Intent {
+ val network = networkAgent.network
+ cm.startCaptivePortalApp(network)
+ waitForIdle()
+ verify(networkAgent.networkMonitor).launchCaptivePortalApp()
+
+ val testBundle = Bundle()
+ val testKey = "testkey"
+ val testValue = "testvalue"
+ testBundle.putString(testKey, testValue)
+ context.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, PERMISSION_GRANTED)
+ cm.startCaptivePortalApp(network, testBundle)
+ val signInIntent: Intent = context.expectStartActivityIntent(TIMEOUT_MS)
+ assertEquals(ACTION_CAPTIVE_PORTAL_SIGN_IN, signInIntent.getAction())
+ assertEquals(testValue, signInIntent.getStringExtra(testKey))
+ return signInIntent
+ }
+}
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/CSAgentWrapper.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt
index d41c742..d7343b1 100644
--- a/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt
@@ -19,6 +19,8 @@
import android.content.Context
import android.net.ConnectivityManager
import android.net.INetworkMonitor
+import android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_DNS
+import android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTP
import android.net.INetworkMonitorCallbacks
import android.net.LinkProperties
import android.net.LocalNetworkConfig
@@ -75,10 +77,15 @@
) : TestableNetworkCallback.HasNetwork {
private val TAG = "CSAgent${nextAgentId()}"
private val VALIDATION_RESULT_INVALID = 0
+ private val NO_PROBE_RESULT = 0
private val VALIDATION_TIMESTAMP = 1234L
private val agent: NetworkAgent
private val nmCallbacks: INetworkMonitorCallbacks
val networkMonitor = mock<INetworkMonitor>()
+ private var nmValidationRedirectUrl: String? = null
+ private var nmValidationResult = NO_PROBE_RESULT
+ private var nmProbesCompleted = NO_PROBE_RESULT
+ private var nmProbesSucceeded = NO_PROBE_RESULT
override val network: Network get() = agent.network!!
@@ -120,10 +127,10 @@
}
nmCallbacks.notifyProbeStatusChanged(0 /* completed */, 0 /* succeeded */)
val p = NetworkTestResultParcelable()
- p.result = VALIDATION_RESULT_INVALID
- p.probesAttempted = 0
- p.probesSucceeded = 0
- p.redirectUrl = null
+ p.result = nmValidationResult
+ p.probesAttempted = nmProbesCompleted
+ p.probesSucceeded = nmProbesSucceeded
+ p.redirectUrl = nmValidationRedirectUrl
p.timestampMillis = VALIDATION_TIMESTAMP
nmCallbacks.notifyNetworkTestedWithExtras(p)
}
@@ -171,4 +178,26 @@
fun sendLocalNetworkConfig(lnc: LocalNetworkConfig) = agent.sendLocalNetworkConfig(lnc)
fun sendNetworkCapabilities(nc: NetworkCapabilities) = agent.sendNetworkCapabilities(nc)
+
+ fun connectWithCaptivePortal(redirectUrl: String) {
+ setCaptivePortal(redirectUrl)
+ connect()
+ }
+
+ fun setProbesStatus(probesCompleted: Int, probesSucceeded: Int) {
+ nmProbesCompleted = probesCompleted
+ nmProbesSucceeded = probesSucceeded
+ }
+
+ fun setCaptivePortal(redirectUrl: String) {
+ nmValidationResult = VALIDATION_RESULT_INVALID
+ nmValidationRedirectUrl = redirectUrl
+ // Suppose the portal is found when NetworkMonitor probes NETWORK_VALIDATION_PROBE_HTTP
+ // in the beginning. Because NETWORK_VALIDATION_PROBE_HTTP is the decisive probe for captive
+ // portal, considering the NETWORK_VALIDATION_PROBE_HTTPS hasn't probed yet and set only
+ // DNS and HTTP probes completed.
+ setProbesStatus(
+ NETWORK_VALIDATION_PROBE_DNS or NETWORK_VALIDATION_PROBE_HTTP /* probesCompleted */,
+ VALIDATION_RESULT_INVALID /* probesSucceeded */)
+ }
}
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..595ca47 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
@@ -46,8 +47,10 @@
import android.os.Bundle
import android.os.Handler
import android.os.HandlerThread
+import android.os.Process
import android.os.UserHandle
import android.os.UserManager
+import android.permission.PermissionManager.PermissionResult
import android.telephony.TelephonyManager
import android.testing.TestableContext
import android.util.ArraySet
@@ -66,19 +69,25 @@
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.concurrent.LinkedBlockingQueue
+import java.util.concurrent.TimeUnit
+import java.util.function.Consumer
+import java.util.function.BiConsumer
+import kotlin.test.assertNotNull
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
import org.mockito.Mockito.mock
-internal const val HANDLER_TIMEOUT_MS = 2_000
+internal const val HANDLER_TIMEOUT_MS = 2_000L
internal const val BROADCAST_TIMEOUT_MS = 3_000L
internal const val TEST_PACKAGE_NAME = "com.android.test.package"
internal const val WIFI_WOL_IFNAME = "test_wlan_wol"
@@ -133,10 +142,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 +174,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 +182,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 +225,21 @@
override fun makeCarrierPrivilegeAuthenticator(
context: Context,
- tm: TelephonyManager
+ tm: TelephonyManager,
+ requestRestrictedWifiEnabled: Boolean,
+ listener: BiConsumer<Int, Int>
) = 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)
@@ -268,13 +305,65 @@
val pacProxyManager = mock<PacProxyManager>()
val networkPolicyManager = mock<NetworkPolicyManager>()
+ // Map of permission name -> PermissionManager.Permission_{GRANTED|DENIED} constant
+ // For permissions granted across the board, the key is only the permission name.
+ // For permissions only granted to a combination of uid/pid, the key
+ // is "<permission name>,<pid>,<uid>". PID+UID permissions have priority over generic ones.
+ private val mMockedPermissions: HashMap<String, Int> = HashMap()
+ private val mStartedActivities = LinkedBlockingQueue<Intent>()
override fun getPackageManager() = this@CSTest.packageManager
override fun getContentResolver() = this@CSTest.contentResolver
- // TODO : buff up the capabilities of this permission scheme to allow checking for
- // permission rejections
- override fun checkPermission(permission: String, pid: Int, uid: Int) = PERMISSION_GRANTED
- override fun checkCallingOrSelfPermission(permission: String) = PERMISSION_GRANTED
+ // If the permission result does not set in the mMockedPermissions, it will be
+ // considered as PERMISSION_GRANTED as existing design to prevent breaking other tests.
+ override fun checkPermission(permission: String, pid: Int, uid: Int) =
+ checkMockedPermission(permission, pid, uid, PERMISSION_GRANTED)
+
+ override fun enforceCallingOrSelfPermission(permission: String, message: String?) {
+ // If the permission result does not set in the mMockedPermissions, it will be
+ // considered as PERMISSION_GRANTED as existing design to prevent breaking other tests.
+ val granted = checkMockedPermission(permission, Process.myPid(), Process.myUid(),
+ PERMISSION_GRANTED)
+ if (!granted.equals(PERMISSION_GRANTED)) {
+ throw SecurityException("[Test] permission denied: " + permission)
+ }
+ }
+
+ // If the permission result does not set in the mMockedPermissions, it will be
+ // considered as PERMISSION_GRANTED as existing design to prevent breaking other tests.
+ override fun checkCallingOrSelfPermission(permission: String) =
+ checkMockedPermission(permission, Process.myPid(), Process.myUid(), PERMISSION_GRANTED)
+
+ private fun checkMockedPermission(permission: String, pid: Int, uid: Int, default: Int):
+ Int {
+ val processSpecificKey = "$permission,$pid,$uid"
+ return mMockedPermissions[processSpecificKey]
+ ?: mMockedPermissions[permission] ?: default
+ }
+
+ /**
+ * Mock checks for the specified permission, and have them behave as per `granted` or
+ * `denied`.
+ *
+ * This will apply to all calls no matter what the checked UID and PID are.
+ *
+ * @param granted One of {@link PackageManager#PermissionResult}.
+ */
+ fun setPermission(permission: String, @PermissionResult granted: Int) {
+ mMockedPermissions.put(permission, granted)
+ }
+
+ /**
+ * Mock checks for the specified permission, and have them behave as per `granted` or
+ * `denied`.
+ *
+ * This will only apply to the passed UID and PID.
+ *
+ * @param granted One of {@link PackageManager#PermissionResult}.
+ */
+ fun setPermission(permission: String, pid: Int, uid: Int, @PermissionResult granted: Int) {
+ mMockedPermissions.put("$permission,$pid,$uid", granted)
+ }
// Necessary for MultinetworkPolicyTracker, which tries to register a receiver for
// all users. The test can't do that since it doesn't hold INTERACT_ACROSS_USERS.
@@ -332,6 +421,16 @@
) {
orderedBroadcastAsUserHistory.add(intent)
}
+
+ override fun startActivityAsUser(intent: Intent, handle: UserHandle) {
+ mStartedActivities.put(intent)
+ }
+
+ fun expectStartActivityIntent(timeoutMs: Long = HANDLER_TIMEOUT_MS): Intent {
+ val intent = mStartedActivities.poll(timeoutMs, TimeUnit.MILLISECONDS)
+ assertNotNull(intent, "Did not receive sign-in intent after " + timeoutMs + "ms")
+ return intent
+ }
}
// Utility methods for subclasses to use
diff --git a/tests/unit/java/com/android/server/net/TrafficStatsRateLimitCacheTest.kt b/tests/unit/java/com/android/server/net/TrafficStatsRateLimitCacheTest.kt
new file mode 100644
index 0000000..27e6f96
--- /dev/null
+++ b/tests/unit/java/com/android/server/net/TrafficStatsRateLimitCacheTest.kt
@@ -0,0 +1,89 @@
+/*
+ * 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.net
+
+import android.net.NetworkStats
+import com.android.testutils.DevSdkIgnoreRunner
+import java.time.Clock
+import kotlin.test.assertEquals
+import kotlin.test.assertNull
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.`when`
+
+@RunWith(DevSdkIgnoreRunner::class)
+class TrafficStatsRateLimitCacheTest {
+ companion object {
+ private const val expiryDurationMs = 1000L
+ }
+
+ private val clock = mock(Clock::class.java)
+ private val entry = mock(NetworkStats.Entry::class.java)
+ private val cache = TrafficStatsRateLimitCache(clock, expiryDurationMs)
+
+ @Test
+ fun testGet_returnsEntryIfNotExpired() {
+ cache.put("iface", 2, entry)
+ `when`(clock.millis()).thenReturn(500L) // Set clock to before expiry
+ val result = cache.get("iface", 2)
+ assertEquals(entry, result)
+ }
+
+ @Test
+ fun testGet_returnsNullIfExpired() {
+ cache.put("iface", 2, entry)
+ `when`(clock.millis()).thenReturn(2000L) // Set clock to after expiry
+ assertNull(cache.get("iface", 2))
+ }
+
+ @Test
+ fun testGet_returnsNullForNonExistentKey() {
+ val result = cache.get("otherIface", 99)
+ assertNull(result)
+ }
+
+ @Test
+ fun testPutAndGet_retrievesCorrectEntryForDifferentKeys() {
+ val entry1 = mock(NetworkStats.Entry::class.java)
+ val entry2 = mock(NetworkStats.Entry::class.java)
+
+ cache.put("iface1", 2, entry1)
+ cache.put("iface2", 4, entry2)
+
+ assertEquals(entry1, cache.get("iface1", 2))
+ assertEquals(entry2, cache.get("iface2", 4))
+ }
+
+ @Test
+ fun testPut_overridesExistingEntry() {
+ val entry1 = mock(NetworkStats.Entry::class.java)
+ val entry2 = mock(NetworkStats.Entry::class.java)
+
+ cache.put("iface", 2, entry1)
+ cache.put("iface", 2, entry2) // Put with the same key
+
+ assertEquals(entry2, cache.get("iface", 2))
+ }
+
+ @Test
+ fun testClear() {
+ cache.put("iface", 2, entry)
+ cache.clear()
+ assertNull(cache.get("iface", 2))
+ }
+}
diff --git a/tests/unit/vpn-jarjar-rules.txt b/tests/unit/vpn-jarjar-rules.txt
index 1a6bddc..f74eab8 100644
--- a/tests/unit/vpn-jarjar-rules.txt
+++ b/tests/unit/vpn-jarjar-rules.txt
@@ -1,4 +1,2 @@
# Only keep classes imported by ConnectivityServiceTest
-keep com.android.server.connectivity.Vpn
keep com.android.server.connectivity.VpnProfileStore
-keep com.android.server.net.LockdownVpnTracker
diff --git a/thread/framework/java/android/net/thread/ThreadNetworkException.java b/thread/framework/java/android/net/thread/ThreadNetworkException.java
index 66f13ce..4def0fb 100644
--- a/thread/framework/java/android/net/thread/ThreadNetworkException.java
+++ b/thread/framework/java/android/net/thread/ThreadNetworkException.java
@@ -89,8 +89,9 @@
/**
* The operation failed because required preconditions were not satisfied. For example, trying
- * to schedule a network migration when this device is not attached will receive this error. The
- * caller should not retry the same operation before the precondition is satisfied.
+ * to schedule a network migration when this device is not attached will receive this error or
+ * enable Thread while User Resitration has disabled it. The caller should not retry the same
+ * operation before the precondition is satisfied.
*/
public static final int ERROR_FAILED_PRECONDITION = 6;
diff --git a/thread/framework/java/android/net/thread/ThreadNetworkManager.java b/thread/framework/java/android/net/thread/ThreadNetworkManager.java
index 28012a7..150b759 100644
--- a/thread/framework/java/android/net/thread/ThreadNetworkManager.java
+++ b/thread/framework/java/android/net/thread/ThreadNetworkManager.java
@@ -79,6 +79,17 @@
public static final String PERMISSION_THREAD_NETWORK_PRIVILEGED =
"android.permission.THREAD_NETWORK_PRIVILEGED";
+ /**
+ * This user restriction specifies if Thread network is disallowed on the device. If Thread
+ * network is disallowed it cannot be turned on via Settings.
+ *
+ * <p>this is a mirror of {@link UserManager#DISALLOW_THREAD_NETWORK} which is not available on
+ * Android U devices.
+ *
+ * @hide
+ */
+ public static final String DISALLOW_THREAD_NETWORK = "no_thread_network";
+
@NonNull private final Context mContext;
@NonNull private final List<ThreadNetworkController> mUnmodifiableControllerServices;
diff --git a/thread/service/java/com/android/server/thread/ActiveOperationalDatasetReceiverWrapper.java b/thread/service/java/com/android/server/thread/ActiveOperationalDatasetReceiverWrapper.java
new file mode 100644
index 0000000..e3b4e1a
--- /dev/null
+++ b/thread/service/java/com/android/server/thread/ActiveOperationalDatasetReceiverWrapper.java
@@ -0,0 +1,87 @@
+/*
+ * 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.thread.ThreadNetworkException.ERROR_UNAVAILABLE;
+
+import android.net.thread.ActiveOperationalDataset;
+import android.net.thread.IActiveOperationalDatasetReceiver;
+import android.os.RemoteException;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * A {@link IActiveOperationalDatasetReceiver} wrapper which makes it easier to invoke the
+ * callbacks.
+ */
+final class ActiveOperationalDatasetReceiverWrapper {
+ private final IActiveOperationalDatasetReceiver mReceiver;
+
+ private static final Object sPendingReceiversLock = new Object();
+
+ @GuardedBy("sPendingReceiversLock")
+ private static final Set<ActiveOperationalDatasetReceiverWrapper> sPendingReceivers =
+ new HashSet<>();
+
+ public ActiveOperationalDatasetReceiverWrapper(IActiveOperationalDatasetReceiver receiver) {
+ this.mReceiver = receiver;
+
+ synchronized (sPendingReceiversLock) {
+ sPendingReceivers.add(this);
+ }
+ }
+
+ public static void onOtDaemonDied() {
+ synchronized (sPendingReceiversLock) {
+ for (ActiveOperationalDatasetReceiverWrapper receiver : sPendingReceivers) {
+ try {
+ receiver.mReceiver.onError(ERROR_UNAVAILABLE, "Thread daemon died");
+ } catch (RemoteException e) {
+ // The client is dead, do nothing
+ }
+ }
+ sPendingReceivers.clear();
+ }
+ }
+
+ public void onSuccess(ActiveOperationalDataset dataset) {
+ synchronized (sPendingReceiversLock) {
+ sPendingReceivers.remove(this);
+ }
+
+ try {
+ mReceiver.onSuccess(dataset);
+ } catch (RemoteException e) {
+ // The client is dead, do nothing
+ }
+ }
+
+ public void onError(int errorCode, String errorMessage) {
+ synchronized (sPendingReceiversLock) {
+ sPendingReceivers.remove(this);
+ }
+
+ try {
+ mReceiver.onError(errorCode, errorMessage);
+ } catch (RemoteException e) {
+ // The client is dead, do nothing
+ }
+ }
+}
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
index 21e3927..0623b87 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
@@ -16,7 +16,6 @@
import static android.Manifest.permission.NETWORK_SETTINGS;
import static android.net.MulticastRoutingConfig.CONFIG_FORWARD_NONE;
-import static android.net.MulticastRoutingConfig.FORWARD_NONE;
import static android.net.MulticastRoutingConfig.FORWARD_SELECTED;
import static android.net.MulticastRoutingConfig.FORWARD_WITH_MIN_SCOPE;
import static android.net.thread.ActiveOperationalDataset.CHANNEL_PAGE_24_GHZ;
@@ -41,11 +40,12 @@
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.DISALLOW_THREAD_NETWORK;
import static android.net.thread.ThreadNetworkManager.PERMISSION_THREAD_NETWORK_PRIVILEGED;
import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_ABORT;
import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_BUSY;
-import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_DETACHED;
+import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_FAILED_PRECONDITION;
import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_INVALID_STATE;
import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_NO_BUFS;
import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_PARSE;
@@ -64,8 +64,12 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.TargetApi;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.net.ConnectivityManager;
+import android.net.InetAddresses;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.LocalNetworkConfig;
@@ -98,12 +102,15 @@
import android.os.Looper;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.os.UserManager;
import android.util.Log;
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.ServiceManagerWrapper;
+import com.android.server.thread.openthread.BackboneRouterState;
import com.android.server.thread.openthread.BorderRouterConfigurationParcel;
+import com.android.server.thread.openthread.IChannelMasksReceiver;
import com.android.server.thread.openthread.IOtDaemon;
import com.android.server.thread.openthread.IOtDaemonCallback;
import com.android.server.thread.openthread.IOtStatusReceiver;
@@ -117,6 +124,7 @@
import java.security.SecureRandom;
import java.time.Instant;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Random;
@@ -152,9 +160,6 @@
private final NsdPublisher mNsdPublisher;
private final OtDaemonCallbackProxy mOtDaemonCallbackProxy = new OtDaemonCallbackProxy();
- // TODO(b/308310823): read supported channel from Thread dameon
- private final int mSupportedChannelMask = 0x07FFF800; // from channel 11 to 26
-
@Nullable private IOtDaemon mOtDaemon;
@Nullable private NetworkAgent mNetworkAgent;
@Nullable private NetworkAgent mTestNetworkAgent;
@@ -167,6 +172,8 @@
private TestNetworkSpecifier mUpstreamTestNetworkSpecifier;
private final HashMap<Network, String> mNetworkToInterface;
private final ThreadPersistentSettings mPersistentSettings;
+ private final UserManager mUserManager;
+ private boolean mUserRestricted;
private BorderRouterConfigurationParcel mBorderRouterConfig;
@@ -180,7 +187,8 @@
TunInterfaceController tunIfController,
InfraInterfaceController infraIfController,
ThreadPersistentSettings persistentSettings,
- NsdPublisher nsdPublisher) {
+ NsdPublisher nsdPublisher,
+ UserManager userManager) {
mContext = context;
mHandler = handler;
mNetworkProvider = networkProvider;
@@ -193,6 +201,7 @@
mBorderRouterConfig = new BorderRouterConfigurationParcel();
mPersistentSettings = persistentSettings;
mNsdPublisher = nsdPublisher;
+ mUserManager = userManager;
}
public static ThreadNetworkControllerService newInstance(
@@ -212,7 +221,8 @@
new TunInterfaceController(TUN_IF_NAME),
new InfraInterfaceController(),
persistentSettings,
- NsdPublisher.newInstance(context, handler));
+ NsdPublisher.newInstance(context, handler),
+ context.getSystemService(UserManager.class));
}
private static Inet6Address bytesToInet6Address(byte[] addressBytes) {
@@ -288,10 +298,7 @@
if (otDaemon == null) {
throw new RemoteException("Internal error: failed to start OT daemon");
}
- otDaemon.initialize(
- mTunIfController.getTunFd(),
- mPersistentSettings.get(ThreadPersistentSettings.THREAD_ENABLED),
- mNsdPublisher);
+ otDaemon.initialize(mTunIfController.getTunFd(), isEnabled(), mNsdPublisher);
otDaemon.registerStateCallback(mOtDaemonCallbackProxy, -1);
otDaemon.asBinder().linkToDeath(() -> mHandler.post(this::onOtDaemonDied), 0);
mOtDaemon = otDaemon;
@@ -323,23 +330,39 @@
mConnectivityManager.registerNetworkProvider(mNetworkProvider);
requestUpstreamNetwork();
requestThreadNetwork();
-
+ mUserRestricted = isThreadUserRestricted();
+ registerUserRestrictionsReceiver();
initializeOtDaemon();
});
}
- public void setEnabled(@NonNull boolean isEnabled, @NonNull IOperationReceiver receiver) {
+ public void setEnabled(boolean isEnabled, @NonNull IOperationReceiver receiver) {
enforceAllPermissionsGranted(PERMISSION_THREAD_NETWORK_PRIVILEGED);
- mHandler.post(() -> setEnabledInternal(isEnabled, new OperationReceiverWrapper(receiver)));
+ mHandler.post(
+ () ->
+ setEnabledInternal(
+ isEnabled,
+ true /* persist */,
+ 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);
+ boolean isEnabled, boolean persist, @NonNull OperationReceiverWrapper receiver) {
+ if (isEnabled && isThreadUserRestricted()) {
+ receiver.onError(
+ ERROR_FAILED_PRECONDITION,
+ "Cannot enable Thread: forbidden by user restriction");
+ return;
+ }
+
+ if (persist) {
+ // 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) {
@@ -348,6 +371,67 @@
}
}
+ private void registerUserRestrictionsReceiver() {
+ mContext.registerReceiver(
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ onUserRestrictionsChanged(isThreadUserRestricted());
+ }
+ },
+ new IntentFilter(UserManager.ACTION_USER_RESTRICTIONS_CHANGED),
+ null /* broadcastPermission */,
+ mHandler);
+ }
+
+ private void onUserRestrictionsChanged(boolean newUserRestrictedState) {
+ checkOnHandlerThread();
+ if (mUserRestricted == newUserRestrictedState) {
+ return;
+ }
+ Log.i(
+ TAG,
+ "Thread user restriction changed: "
+ + mUserRestricted
+ + " -> "
+ + newUserRestrictedState);
+ mUserRestricted = newUserRestrictedState;
+
+ final boolean isEnabled = isEnabled();
+ final IOperationReceiver receiver =
+ new IOperationReceiver.Stub() {
+ @Override
+ public void onSuccess() {
+ Log.d(
+ TAG,
+ (isEnabled ? "Enabled" : "Disabled")
+ + " Thread due to user restriction change");
+ }
+
+ @Override
+ public void onError(int otError, String messages) {
+ Log.e(
+ TAG,
+ "Failed to "
+ + (isEnabled ? "enable" : "disable")
+ + " Thread for user restriction change");
+ }
+ };
+ // Do not save the user restriction state to persistent settings so that the user
+ // configuration won't be overwritten
+ setEnabledInternal(isEnabled, false /* persist */, new OperationReceiverWrapper(receiver));
+ }
+
+ /** Returns {@code true} if Thread is set enabled. */
+ private boolean isEnabled() {
+ return !mUserRestricted && mPersistentSettings.get(ThreadPersistentSettings.THREAD_ENABLED);
+ }
+
+ /** Returns {@code true} if Thread has been restricted for the user. */
+ private boolean isThreadUserRestricted() {
+ return mUserManager.hasUserRestriction(DISALLOW_THREAD_NETWORK);
+ }
+
private void requestUpstreamNetwork() {
if (mUpstreamNetworkCallback != null) {
throw new AssertionError("The upstream network request is already there.");
@@ -509,26 +593,51 @@
@Override
public void createRandomizedDataset(
String networkName, IActiveOperationalDatasetReceiver receiver) {
- mHandler.post(
- () -> {
- ActiveOperationalDataset dataset =
- createRandomizedDatasetInternal(
- networkName,
- mSupportedChannelMask,
- Instant.now(),
- new Random(),
- new SecureRandom());
- try {
- receiver.onSuccess(dataset);
- } catch (RemoteException e) {
- // The client is dead, do nothing
- }
- });
+ ActiveOperationalDatasetReceiverWrapper receiverWrapper =
+ new ActiveOperationalDatasetReceiverWrapper(receiver);
+ mHandler.post(() -> createRandomizedDatasetInternal(networkName, receiverWrapper));
}
- private static ActiveOperationalDataset createRandomizedDatasetInternal(
+ private void createRandomizedDatasetInternal(
+ String networkName, @NonNull ActiveOperationalDatasetReceiverWrapper receiver) {
+ checkOnHandlerThread();
+
+ try {
+ getOtDaemon().getChannelMasks(newChannelMasksReceiver(networkName, receiver));
+ } catch (RemoteException e) {
+ Log.e(TAG, "otDaemon.getChannelMasks failed", e);
+ receiver.onError(ERROR_INTERNAL_ERROR, "Thread stack error");
+ }
+ }
+
+ private IChannelMasksReceiver newChannelMasksReceiver(
+ String networkName, ActiveOperationalDatasetReceiverWrapper receiver) {
+ return new IChannelMasksReceiver.Stub() {
+ @Override
+ public void onSuccess(int supportedChannelMask, int preferredChannelMask) {
+ ActiveOperationalDataset dataset =
+ createRandomizedDataset(
+ networkName,
+ supportedChannelMask,
+ preferredChannelMask,
+ Instant.now(),
+ new Random(),
+ new SecureRandom());
+
+ receiver.onSuccess(dataset);
+ }
+
+ @Override
+ public void onError(int errorCode, String errorMessage) {
+ receiver.onError(otErrorToAndroidError(errorCode), errorMessage);
+ }
+ };
+ }
+
+ private static ActiveOperationalDataset createRandomizedDataset(
String networkName,
int supportedChannelMask,
+ int preferredChannelMask,
Instant now,
Random random,
SecureRandom secureRandom) {
@@ -538,6 +647,7 @@
final SparseArray<byte[]> channelMask = new SparseArray<>(1);
channelMask.put(CHANNEL_PAGE_24_GHZ, channelMaskToByteArray(supportedChannelMask));
+ final int channel = selectChannel(supportedChannelMask, preferredChannelMask, random);
final byte[] securityFlags = new byte[] {(byte) 0xff, (byte) 0xf8};
@@ -548,7 +658,7 @@
.setExtendedPanId(newRandomBytes(random, LENGTH_EXTENDED_PAN_ID))
.setPanId(panId)
.setNetworkName(networkName)
- .setChannel(CHANNEL_PAGE_24_GHZ, selectRandomChannel(supportedChannelMask, random))
+ .setChannel(CHANNEL_PAGE_24_GHZ, channel)
.setChannelMask(channelMask)
.setPskc(newRandomBytes(secureRandom, LENGTH_PSKC))
.setNetworkKey(newRandomBytes(secureRandom, LENGTH_NETWORK_KEY))
@@ -557,6 +667,18 @@
.build();
}
+ private static int selectChannel(
+ int supportedChannelMask, int preferredChannelMask, Random random) {
+ // If the preferred channel mask is not empty, select a random channel from it, otherwise
+ // choose one from the supported channel mask.
+ preferredChannelMask = preferredChannelMask & supportedChannelMask;
+ if (preferredChannelMask == 0) {
+ preferredChannelMask = supportedChannelMask;
+ }
+
+ return selectRandomChannel(preferredChannelMask, random);
+ }
+
private static byte[] newRandomBytes(Random random, int length) {
byte[] result = new byte[length];
random.nextBytes(result);
@@ -656,9 +778,6 @@
return ERROR_ABORTED;
case OT_ERROR_BUSY:
return ERROR_BUSY;
- case OT_ERROR_DETACHED:
- case OT_ERROR_INVALID_STATE:
- return ERROR_FAILED_PRECONDITION;
case OT_ERROR_NO_BUFS:
return ERROR_RESOURCE_EXHAUSTED;
case OT_ERROR_PARSE:
@@ -672,6 +791,9 @@
return ERROR_UNSUPPORTED_CHANNEL;
case OT_ERROR_THREAD_DISABLED:
return ERROR_THREAD_DISABLED;
+ case OT_ERROR_FAILED_PRECONDITION:
+ return ERROR_FAILED_PRECONDITION;
+ case OT_ERROR_INVALID_STATE:
default:
return ERROR_INTERNAL_ERROR;
}
@@ -881,11 +1003,6 @@
}
}
- private boolean isMulticastForwardingEnabled() {
- return !(mUpstreamMulticastRoutingConfig.getForwardingMode() == FORWARD_NONE
- && mDownstreamMulticastRoutingConfig.getForwardingMode() == FORWARD_NONE);
- }
-
private void sendLocalNetworkConfig() {
if (mNetworkAgent == null) {
return;
@@ -895,72 +1012,44 @@
Log.d(TAG, "Sent localNetworkConfig: " + localNetworkConfig);
}
- private void handleMulticastForwardingStateChanged(boolean isEnabled) {
- if (isMulticastForwardingEnabled() == isEnabled) {
- return;
- }
+ private void handleMulticastForwardingChanged(BackboneRouterState state) {
+ MulticastRoutingConfig upstreamMulticastRoutingConfig;
+ MulticastRoutingConfig downstreamMulticastRoutingConfig;
- Log.i(TAG, "Multicast forwaring is " + (isEnabled ? "enabled" : "disabled"));
-
- if (isEnabled) {
+ if (state.multicastForwardingEnabled) {
// When multicast forwarding is enabled, setup upstream forwarding to any address
// with minimal scope 4
// setup downstream forwarding with addresses subscribed from Thread network
- mUpstreamMulticastRoutingConfig =
+ upstreamMulticastRoutingConfig =
new MulticastRoutingConfig.Builder(FORWARD_WITH_MIN_SCOPE, 4).build();
- mDownstreamMulticastRoutingConfig =
- new MulticastRoutingConfig.Builder(FORWARD_SELECTED).build();
+ downstreamMulticastRoutingConfig =
+ buildDownstreamMulticastRoutingConfigSelected(state.listeningAddresses);
} else {
// When multicast forwarding is disabled, set both upstream and downstream
// forwarding config to FORWARD_NONE.
- mUpstreamMulticastRoutingConfig = CONFIG_FORWARD_NONE;
- mDownstreamMulticastRoutingConfig = CONFIG_FORWARD_NONE;
+ upstreamMulticastRoutingConfig = CONFIG_FORWARD_NONE;
+ downstreamMulticastRoutingConfig = CONFIG_FORWARD_NONE;
}
+
+ if (upstreamMulticastRoutingConfig.equals(mUpstreamMulticastRoutingConfig)
+ && downstreamMulticastRoutingConfig.equals(mDownstreamMulticastRoutingConfig)) {
+ return;
+ }
+
+ mUpstreamMulticastRoutingConfig = upstreamMulticastRoutingConfig;
+ mDownstreamMulticastRoutingConfig = downstreamMulticastRoutingConfig;
sendLocalNetworkConfig();
}
- private void handleMulticastForwardingAddressChanged(byte[] addressBytes, boolean isAdded) {
- Inet6Address address = bytesToInet6Address(addressBytes);
- MulticastRoutingConfig newDownstreamConfig;
- MulticastRoutingConfig.Builder builder;
-
- if (mDownstreamMulticastRoutingConfig.getForwardingMode()
- != MulticastRoutingConfig.FORWARD_SELECTED) {
- Log.e(
- TAG,
- "Ignore multicast listening address updates when downstream multicast "
- + "forwarding mode is not FORWARD_SELECTED");
- // Don't update the address set if downstream multicast forwarding is disabled.
- return;
- }
- if (isAdded
- == mDownstreamMulticastRoutingConfig.getListeningAddresses().contains(address)) {
- return;
- }
-
- builder = new MulticastRoutingConfig.Builder(FORWARD_SELECTED);
- for (Inet6Address listeningAddress :
- mDownstreamMulticastRoutingConfig.getListeningAddresses()) {
- builder.addListeningAddress(listeningAddress);
- }
-
- if (isAdded) {
+ private MulticastRoutingConfig buildDownstreamMulticastRoutingConfigSelected(
+ List<String> listeningAddresses) {
+ MulticastRoutingConfig.Builder builder =
+ new MulticastRoutingConfig.Builder(FORWARD_SELECTED);
+ for (String addressStr : listeningAddresses) {
+ Inet6Address address = (Inet6Address) InetAddresses.parseNumericAddress(addressStr);
builder.addListeningAddress(address);
- } else {
- builder.clearListeningAddress(address);
}
-
- newDownstreamConfig = builder.build();
- if (!newDownstreamConfig.equals(mDownstreamMulticastRoutingConfig)) {
- Log.d(
- TAG,
- "Multicast listening address "
- + address.getHostAddress()
- + " is "
- + (isAdded ? "added" : "removed"));
- mDownstreamMulticastRoutingConfig = newDownstreamConfig;
- sendLocalNetworkConfig();
- }
+ return builder.build();
}
private static final class CallbackMetadata {
@@ -1128,7 +1217,6 @@
onInterfaceStateChanged(newState.isInterfaceUp);
onDeviceRoleChanged(newState.deviceRole, listenerId);
onPartitionIdChanged(newState.partitionId, listenerId);
- onMulticastForwardingStateChanged(newState.multicastForwardingEnabled);
mState = newState;
ActiveOperationalDataset newActiveDataset;
@@ -1237,19 +1325,14 @@
}
}
- private void onMulticastForwardingStateChanged(boolean isEnabled) {
- checkOnHandlerThread();
- handleMulticastForwardingStateChanged(isEnabled);
- }
-
@Override
public void onAddressChanged(Ipv6AddressInfo addressInfo, boolean isAdded) {
mHandler.post(() -> handleAddressChanged(addressInfo, isAdded));
}
@Override
- public void onMulticastForwardingAddressChanged(byte[] address, boolean isAdded) {
- mHandler.post(() -> handleMulticastForwardingAddressChanged(address, isAdded));
+ public void onBackboneRouterStateChanged(BackboneRouterState state) {
+ mHandler.post(() -> handleMulticastForwardingChanged(state));
}
}
}
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkService.java b/thread/service/java/com/android/server/thread/ThreadNetworkService.java
index 5cf27f7..5664922 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkService.java
@@ -18,21 +18,16 @@
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;
@@ -51,12 +46,7 @@
/** Creates a new {@link ThreadNetworkService} object. */
public ThreadNetworkService(Context context) {
mContext = context;
- mPersistentSettings =
- new ThreadPersistentSettings(
- new AtomicFile(
- new File(
- getOrCreateThreadnetworkDir(),
- ThreadPersistentSettings.FILE_NAME)));
+ mPersistentSettings = ThreadPersistentSettings.newInstance(context);
}
/**
@@ -123,19 +113,4 @@
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/ThreadPersistentSettings.java b/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java
index d32f0bf..aba4193 100644
--- a/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java
+++ b/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java
@@ -16,15 +16,23 @@
package com.android.server.thread;
+import static com.android.net.module.util.DeviceConfigUtils.TETHERING_MODULE_NAME;
+
import android.annotation.Nullable;
+import android.content.ApexEnvironment;
+import android.content.Context;
import android.os.PersistableBundle;
import android.util.AtomicFile;
import android.util.Log;
+import com.android.connectivity.resources.R;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.connectivity.ConnectivityResources;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
+import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
@@ -39,7 +47,7 @@
public class ThreadPersistentSettings {
private static final String TAG = "ThreadPersistentSettings";
/** File name used for storing settings. */
- public static final String FILE_NAME = "ThreadPersistentSettings.xml";
+ private static final String FILE_NAME = "ThreadPersistentSettings.xml";
/** Current config store data version. This will be incremented for any additions. */
private static final int CURRENT_SETTINGS_STORE_DATA_VERSION = 1;
/**
@@ -62,16 +70,29 @@
@GuardedBy("mLock")
private final PersistableBundle mSettings = new PersistableBundle();
- public ThreadPersistentSettings(AtomicFile atomicFile) {
+ private final ConnectivityResources mResources;
+
+ public static ThreadPersistentSettings newInstance(Context context) {
+ return new ThreadPersistentSettings(
+ new AtomicFile(new File(getOrCreateThreadNetworkDir(), FILE_NAME)),
+ new ConnectivityResources(context));
+ }
+
+ @VisibleForTesting
+ ThreadPersistentSettings(AtomicFile atomicFile, ConnectivityResources resources) {
mAtomicFile = atomicFile;
+ mResources = resources;
}
/** Initialize the settings by reading from the settings file. */
public void initialize() {
readFromStoreFile();
synchronized (mLock) {
- if (mSettings.isEmpty()) {
- put(THREAD_ENABLED.key, THREAD_ENABLED.defaultValue);
+ if (!mSettings.containsKey(THREAD_ENABLED.key)) {
+ Log.i(TAG, "\"thread_enabled\" is missing in settings file, using default value");
+ put(
+ THREAD_ENABLED.key,
+ mResources.get().getBoolean(R.bool.config_thread_default_enabled));
}
}
}
@@ -240,4 +261,19 @@
throw e;
}
}
+
+ /** 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/tests/cts/Android.bp b/thread/tests/cts/Android.bp
index 522120c..676eb0e 100644
--- a/thread/tests/cts/Android.bp
+++ b/thread/tests/cts/Android.bp
@@ -32,6 +32,7 @@
test_suites: [
"cts",
"general-tests",
+ "mcts-tethering",
"mts-tethering",
],
static_libs: [
diff --git a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
index 29ada1b..7aaae86 100644
--- a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
+++ b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
@@ -18,28 +18,39 @@
import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
import static android.Manifest.permission.NETWORK_SETTINGS;
-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.RESTART_JOIN_TIMEOUT;
import static android.net.thread.utils.IntegrationTestUtils.isExpectedIcmpv6Packet;
+import static android.net.thread.utils.IntegrationTestUtils.isFromIpv6Source;
+import static android.net.thread.utils.IntegrationTestUtils.isInMulticastGroup;
import static android.net.thread.utils.IntegrationTestUtils.isSimulatedThreadRadioSupported;
+import static android.net.thread.utils.IntegrationTestUtils.isToIpv6Destination;
import static android.net.thread.utils.IntegrationTestUtils.newPacketReader;
-import static android.net.thread.utils.IntegrationTestUtils.readPacketFrom;
+import static android.net.thread.utils.IntegrationTestUtils.pollForPacket;
+import static android.net.thread.utils.IntegrationTestUtils.sendUdpMessage;
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.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE;
+import static com.android.testutils.DeviceInfoUtils.isKernelVersionAtLeast;
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.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assume.assumeNotNull;
import static org.junit.Assume.assumeTrue;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
import android.content.Context;
+import android.net.InetAddresses;
import android.net.LinkProperties;
import android.net.MacAddress;
import android.net.thread.utils.FullThreadDevice;
@@ -61,9 +72,12 @@
import java.net.Inet6Address;
import java.time.Duration;
+import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
/** Integration test cases for Thread Border Routing feature. */
@RunWith(AndroidJUnit4.class)
@@ -75,6 +89,18 @@
private HandlerThread mHandlerThread;
private Handler mHandler;
private TestNetworkTracker mInfraNetworkTracker;
+ private List<FullThreadDevice> mFtds;
+ private TapPacketReader mInfraNetworkReader;
+ private InfraNetworkDevice mInfraDevice;
+
+ private static final int NUM_FTD = 2;
+ private static final String KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED = "5.15.0";
+ private static final Inet6Address GROUP_ADDR_SCOPE_5 =
+ (Inet6Address) InetAddresses.parseNumericAddress("ff05::1234");
+ private static final Inet6Address GROUP_ADDR_SCOPE_4 =
+ (Inet6Address) InetAddresses.parseNumericAddress("ff04::1234");
+ private static final Inet6Address GROUP_ADDR_SCOPE_3 =
+ (Inet6Address) InetAddresses.parseNumericAddress("ff03::1234");
// A valid Thread Active Operational Dataset generated from OpenThread CLI "dataset init new".
private static final byte[] DEFAULT_DATASET_TLVS =
@@ -89,6 +115,7 @@
@Before
public void setUp() throws Exception {
+ assumeTrue(isSimulatedThreadRadioSupported());
final ThreadNetworkManager manager = mContext.getSystemService(ThreadNetworkManager.class);
if (manager != null) {
mController = manager.getAllThreadNetworkControllers().get(0);
@@ -100,24 +127,21 @@
mHandlerThread = new HandlerThread(getClass().getSimpleName());
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
+ mFtds = new ArrayList<>();
- mInfraNetworkTracker =
- runAsShell(
- MANAGE_TEST_NETWORKS,
- () ->
- initTestNetwork(
- mContext, new LinkProperties(), 5000 /* timeoutMs */));
- runAsShell(
- PERMISSION_THREAD_NETWORK_PRIVILEGED,
- NETWORK_SETTINGS,
- () -> {
- CountDownLatch latch = new CountDownLatch(1);
- mController.setTestNetworkAsUpstream(
- mInfraNetworkTracker.getTestIface().getInterfaceName(),
- directExecutor(),
- v -> latch.countDown());
- latch.await();
- });
+ setUpInfraNetwork();
+
+ // BR forms a network.
+ startBrLeader();
+
+ // Creates a infra network device.
+ mInfraNetworkReader = newPacketReader(mInfraNetworkTracker.getTestIface(), mHandler);
+ startInfraDevice();
+
+ // Create Ftds
+ for (int i = 0; i < NUM_FTD; ++i) {
+ mFtds.add(new FullThreadDevice(15 + i /* node ID */));
+ }
}
@After
@@ -136,16 +160,19 @@
mController.leave(directExecutor(), v -> latch.countDown());
latch.await(10, TimeUnit.SECONDS);
});
- runAsShell(MANAGE_TEST_NETWORKS, () -> mInfraNetworkTracker.teardown());
+ tearDownInfraNetwork();
mHandlerThread.quitSafely();
mHandlerThread.join();
+
+ for (var ftd : mFtds) {
+ ftd.destroy();
+ }
+ mFtds.clear();
}
@Test
public void unicastRouting_infraDevicePingTheadDeviceOmr_replyReceived() throws Exception {
- assumeTrue(isSimulatedThreadRadioSupported());
-
/*
* <pre>
* Topology:
@@ -155,36 +182,479 @@
* </pre>
*/
+ // Let ftd join the network.
+ FullThreadDevice ftd = mFtds.get(0);
+ startFtdChild(ftd);
+
+ // Infra device sends an echo request to FTD's OMR.
+ mInfraDevice.sendEchoRequest(ftd.getOmrAddress());
+
+ // Infra device receives an echo reply sent by FTD.
+ assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, null /* srcAddress */));
+ }
+
+ @Test
+ public void unicastRouting_borderRouterSendsUdpToThreadDevice_datagramReceived()
+ throws Exception {
+ assumeTrue(isSimulatedThreadRadioSupported());
+
+ /*
+ * <pre>
+ * Topology:
+ * Thread
+ * Border Router -------------- Full Thread device
+ * (Cuttlefish)
+ * </pre>
+ */
+
// BR forms a network.
+ CompletableFuture<Void> joinFuture = new CompletableFuture<>();
runAsShell(
PERMISSION_THREAD_NETWORK_PRIVILEGED,
- () -> mController.join(DEFAULT_DATASET, directExecutor(), result -> {}));
- waitForStateAnyOf(mController, List.of(DEVICE_ROLE_LEADER), JOIN_TIMEOUT);
+ () -> mController.join(DEFAULT_DATASET, directExecutor(), joinFuture::complete));
+ joinFuture.get(RESTART_JOIN_TIMEOUT.toMillis(), MILLISECONDS);
// Creates a Full Thread Device (FTD) and lets it join the network.
- FullThreadDevice ftd = new FullThreadDevice(5 /* node ID */);
+ FullThreadDevice ftd = mFtds.get(0);
+ startFtdChild(ftd);
+ Inet6Address ftdOmr = ftd.getOmrAddress();
+ Inet6Address ftdMlEid = ftd.getMlEid();
+ assertNotNull(ftdMlEid);
+
+ ftd.udpBind(ftdOmr, 12345);
+ sendUdpMessage(ftdOmr, 12345, "aaaaaaaa");
+ assertEquals("aaaaaaaa", ftd.udpReceive());
+
+ ftd.udpBind(ftdMlEid, 12345);
+ sendUdpMessage(ftdMlEid, 12345, "bbbbbbbb");
+ assertEquals("bbbbbbbb", ftd.udpReceive());
+ }
+
+ @Test
+ public void multicastRouting_ftdSubscribedMulticastAddress_infraLinkJoinsMulticastGroup()
+ throws Exception {
+ assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
+ /*
+ * <pre>
+ * Topology:
+ * infra network Thread
+ * infra device -------------------- Border Router -------------- Full Thread device
+ * (Cuttlefish)
+ * </pre>
+ */
+
+ FullThreadDevice ftd = mFtds.get(0);
+ startFtdChild(ftd);
+
+ ftd.subscribeMulticastAddress(GROUP_ADDR_SCOPE_5);
+
+ assertInfraLinkMemberOfGroup(GROUP_ADDR_SCOPE_5);
+ }
+
+ @Test
+ public void
+ multicastRouting_ftdSubscribedScope3MulticastAddress_infraLinkNotJoinMulticastGroup()
+ throws Exception {
+ assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
+ /*
+ * <pre>
+ * Topology:
+ * infra network Thread
+ * infra device -------------------- Border Router -------------- Full Thread device
+ * (Cuttlefish)
+ * </pre>
+ */
+
+ FullThreadDevice ftd = mFtds.get(0);
+ startFtdChild(ftd);
+
+ ftd.subscribeMulticastAddress(GROUP_ADDR_SCOPE_3);
+
+ assertInfraLinkNotMemberOfGroup(GROUP_ADDR_SCOPE_3);
+ }
+
+ @Test
+ public void multicastRouting_ftdSubscribedMulticastAddress_canPingfromInfraLink()
+ throws Exception {
+ assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
+ /*
+ * <pre>
+ * Topology:
+ * infra network Thread
+ * infra device -------------------- Border Router -------------- Full Thread device
+ * (Cuttlefish)
+ * </pre>
+ */
+
+ FullThreadDevice ftd = mFtds.get(0);
+ startFtdChild(ftd);
+ subscribeMulticastAddressAndWait(ftd, GROUP_ADDR_SCOPE_5);
+
+ mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_5);
+
+ assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress()));
+ }
+
+ @Test
+ public void multicastRouting_inboundForwarding_afterBrRejoinFtdRepliesSubscribedAddress()
+ throws Exception {
+ assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
+
+ // TODO (b/327311034): Testing bbr state switch from primary mode to secondary mode and back
+ // to primary mode requires an additional BR in the Thread network. This is not currently
+ // supported, to be implemented when possible.
+ }
+
+ @Test
+ public void multicastRouting_ftdSubscribedScope3MulticastAddress_cannotPingfromInfraLink()
+ throws Exception {
+ assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
+ /*
+ * <pre>
+ * Topology:
+ * infra network Thread
+ * infra device -------------------- Border Router -------------- Full Thread device
+ * (Cuttlefish)
+ * </pre>
+ */
+
+ FullThreadDevice ftd = mFtds.get(0);
+ startFtdChild(ftd);
+ ftd.subscribeMulticastAddress(GROUP_ADDR_SCOPE_3);
+
+ mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_3);
+
+ assertNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress()));
+ }
+
+ @Test
+ public void multicastRouting_ftdNotSubscribedMulticastAddress_cannotPingFromInfraDevice()
+ throws Exception {
+ assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
+ /*
+ * <pre>
+ * Topology:
+ * infra network Thread
+ * infra device -------------------- Border Router -------------- Full Thread device
+ * (Cuttlefish)
+ * </pre>
+ */
+
+ FullThreadDevice ftd = mFtds.get(0);
+ startFtdChild(ftd);
+
+ mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_4);
+
+ assertNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress()));
+ }
+
+ @Test
+ public void multicastRouting_multipleFtdsSubscribedDifferentAddresses_canPingFromInfraDevice()
+ throws Exception {
+ assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
+ /*
+ * <pre>
+ * Topology:
+ * infra network Thread
+ * infra device -------------------- Border Router -------------- Full Thread device 1
+ * (Cuttlefish)
+ * |
+ * | Thread
+ * |
+ * Full Thread device 2
+ * </pre>
+ */
+
+ FullThreadDevice ftd1 = mFtds.get(0);
+ startFtdChild(ftd1);
+ subscribeMulticastAddressAndWait(ftd1, GROUP_ADDR_SCOPE_5);
+
+ FullThreadDevice ftd2 = mFtds.get(1);
+ startFtdChild(ftd2);
+ subscribeMulticastAddressAndWait(ftd2, GROUP_ADDR_SCOPE_4);
+
+ mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_5);
+ mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_4);
+
+ assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd1.getOmrAddress()));
+ assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd2.getOmrAddress()));
+ }
+
+ @Test
+ public void multicastRouting_multipleFtdsSubscribedSameAddress_canPingFromInfraDevice()
+ throws Exception {
+ assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
+ /*
+ * <pre>
+ * Topology:
+ * infra network Thread
+ * infra device -------------------- Border Router -------------- Full Thread device 1
+ * (Cuttlefish)
+ * |
+ * | Thread
+ * |
+ * Full Thread device 2
+ * </pre>
+ */
+
+ FullThreadDevice ftd1 = mFtds.get(0);
+ startFtdChild(ftd1);
+ subscribeMulticastAddressAndWait(ftd1, GROUP_ADDR_SCOPE_5);
+
+ FullThreadDevice ftd2 = mFtds.get(1);
+ startFtdChild(ftd2);
+ subscribeMulticastAddressAndWait(ftd2, GROUP_ADDR_SCOPE_5);
+
+ // Send the request twice as the order of replies from ftd1 and ftd2 are not guaranteed
+ mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_5);
+ mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_5);
+
+ assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd1.getOmrAddress()));
+ assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd2.getOmrAddress()));
+ }
+
+ @Test
+ public void multicastRouting_outboundForwarding_scopeLargerThan3IsForwarded() throws Exception {
+ assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
+ /*
+ * <pre>
+ * Topology:
+ * infra network Thread
+ * infra device -------------------- Border Router -------------- Full Thread device
+ * (Cuttlefish)
+ * </pre>
+ */
+
+ FullThreadDevice ftd = mFtds.get(0);
+ startFtdChild(ftd);
+ Inet6Address ftdOmr = ftd.getOmrAddress();
+
+ ftd.ping(GROUP_ADDR_SCOPE_5);
+ ftd.ping(GROUP_ADDR_SCOPE_4);
+
+ assertNotNull(
+ pollForPacketOnInfraNetwork(ICMPV6_ECHO_REQUEST_TYPE, ftdOmr, GROUP_ADDR_SCOPE_5));
+ assertNotNull(
+ pollForPacketOnInfraNetwork(ICMPV6_ECHO_REQUEST_TYPE, ftdOmr, GROUP_ADDR_SCOPE_4));
+ }
+
+ @Test
+ public void multicastRouting_outboundForwarding_scopeSmallerThan4IsNotForwarded()
+ throws Exception {
+ assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
+ /*
+ * <pre>
+ * Topology:
+ * infra network Thread
+ * infra device -------------------- Border Router -------------- Full Thread device
+ * (Cuttlefish)
+ * </pre>
+ */
+
+ FullThreadDevice ftd = mFtds.get(0);
+ startFtdChild(ftd);
+
+ ftd.ping(GROUP_ADDR_SCOPE_3);
+
+ assertNull(
+ pollForPacketOnInfraNetwork(
+ ICMPV6_ECHO_REQUEST_TYPE, ftd.getOmrAddress(), GROUP_ADDR_SCOPE_3));
+ }
+
+ @Test
+ public void multicastRouting_outboundForwarding_llaToScope4IsNotForwarded() throws Exception {
+ assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
+ /*
+ * <pre>
+ * Topology:
+ * infra network Thread
+ * infra device -------------------- Border Router -------------- Full Thread device
+ * (Cuttlefish)
+ * </pre>
+ */
+
+ FullThreadDevice ftd = mFtds.get(0);
+ startFtdChild(ftd);
+ Inet6Address ftdLla = ftd.getLinkLocalAddress();
+ assertNotNull(ftdLla);
+
+ ftd.ping(GROUP_ADDR_SCOPE_4, ftdLla, 100 /* size */, 1 /* count */);
+
+ assertNull(
+ pollForPacketOnInfraNetwork(ICMPV6_ECHO_REQUEST_TYPE, ftdLla, GROUP_ADDR_SCOPE_4));
+ }
+
+ @Test
+ public void multicastRouting_outboundForwarding_mlaToScope4IsNotForwarded() throws Exception {
+ assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
+ /*
+ * <pre>
+ * Topology:
+ * infra network Thread
+ * infra device -------------------- Border Router -------------- Full Thread device
+ * (Cuttlefish)
+ * </pre>
+ */
+
+ FullThreadDevice ftd = mFtds.get(0);
+ startFtdChild(ftd);
+ List<Inet6Address> ftdMlas = ftd.getMeshLocalAddresses();
+ assertFalse(ftdMlas.isEmpty());
+
+ for (Inet6Address ftdMla : ftdMlas) {
+ ftd.ping(GROUP_ADDR_SCOPE_4, ftdMla, 100 /* size */, 1 /* count */);
+
+ assertNull(
+ pollForPacketOnInfraNetwork(
+ ICMPV6_ECHO_REQUEST_TYPE, ftdMla, GROUP_ADDR_SCOPE_4));
+ }
+ }
+
+ @Test
+ public void multicastRouting_infraNetworkSwitch_ftdRepliesToSubscribedAddress()
+ throws Exception {
+ assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
+ /*
+ * <pre>
+ * Topology:
+ * infra network Thread
+ * infra device -------------------- Border Router -------------- Full Thread device
+ * (Cuttlefish)
+ * </pre>
+ */
+
+ FullThreadDevice ftd = mFtds.get(0);
+ startFtdChild(ftd);
+ subscribeMulticastAddressAndWait(ftd, GROUP_ADDR_SCOPE_5);
+ Inet6Address ftdOmr = ftd.getOmrAddress();
+
+ // Destroy infra link and re-create
+ tearDownInfraNetwork();
+ setUpInfraNetwork();
+ mInfraNetworkReader = newPacketReader(mInfraNetworkTracker.getTestIface(), mHandler);
+ startInfraDevice();
+
+ mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_5);
+
+ assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftdOmr));
+ }
+
+ @Test
+ public void multicastRouting_infraNetworkSwitch_outboundPacketIsForwarded() throws Exception {
+ assumeTrue(isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED));
+ /*
+ * <pre>
+ * Topology:
+ * infra network Thread
+ * infra device -------------------- Border Router -------------- Full Thread device
+ * (Cuttlefish)
+ * </pre>
+ */
+
+ FullThreadDevice ftd = mFtds.get(0);
+ startFtdChild(ftd);
+ Inet6Address ftdOmr = ftd.getOmrAddress();
+
+ // Destroy infra link and re-create
+ tearDownInfraNetwork();
+ setUpInfraNetwork();
+ mInfraNetworkReader = newPacketReader(mInfraNetworkTracker.getTestIface(), mHandler);
+ startInfraDevice();
+
+ ftd.ping(GROUP_ADDR_SCOPE_5);
+ ftd.ping(GROUP_ADDR_SCOPE_4);
+
+ assertNotNull(
+ pollForPacketOnInfraNetwork(ICMPV6_ECHO_REQUEST_TYPE, ftdOmr, GROUP_ADDR_SCOPE_5));
+ assertNotNull(
+ pollForPacketOnInfraNetwork(ICMPV6_ECHO_REQUEST_TYPE, ftdOmr, GROUP_ADDR_SCOPE_4));
+ }
+
+ private void setUpInfraNetwork() {
+ mInfraNetworkTracker =
+ runAsShell(
+ MANAGE_TEST_NETWORKS,
+ () ->
+ initTestNetwork(
+ mContext, new LinkProperties(), 5000 /* timeoutMs */));
+ runAsShell(
+ PERMISSION_THREAD_NETWORK_PRIVILEGED,
+ NETWORK_SETTINGS,
+ () -> {
+ CompletableFuture<Void> future = new CompletableFuture<>();
+ mController.setTestNetworkAsUpstream(
+ mInfraNetworkTracker.getTestIface().getInterfaceName(),
+ directExecutor(),
+ future::complete);
+ future.get(5, TimeUnit.SECONDS);
+ });
+ }
+
+ private void tearDownInfraNetwork() {
+ runAsShell(MANAGE_TEST_NETWORKS, () -> mInfraNetworkTracker.teardown());
+ }
+
+ private void startBrLeader() throws Exception {
+ CompletableFuture<Void> joinFuture = new CompletableFuture<>();
+ runAsShell(
+ PERMISSION_THREAD_NETWORK_PRIVILEGED,
+ () -> mController.join(DEFAULT_DATASET, directExecutor(), joinFuture::complete));
+ joinFuture.get(RESTART_JOIN_TIMEOUT.toSeconds(), TimeUnit.SECONDS);
+ }
+
+ private void startFtdChild(FullThreadDevice ftd) throws Exception {
ftd.factoryReset();
ftd.joinNetwork(DEFAULT_DATASET);
ftd.waitForStateAnyOf(List.of("router", "child"), JOIN_TIMEOUT);
waitFor(() -> ftd.getOmrAddress() != null, Duration.ofSeconds(60));
Inet6Address ftdOmr = ftd.getOmrAddress();
assertNotNull(ftdOmr);
+ }
- // Creates a infra network device.
- TapPacketReader infraNetworkReader =
- newPacketReader(mInfraNetworkTracker.getTestIface(), mHandler);
- InfraNetworkDevice infraDevice =
- new InfraNetworkDevice(MacAddress.fromString("1:2:3:4:5:6"), infraNetworkReader);
- infraDevice.runSlaac(Duration.ofSeconds(60));
- assertNotNull(infraDevice.ipv6Addr);
+ private void startInfraDevice() throws Exception {
+ mInfraDevice =
+ new InfraNetworkDevice(MacAddress.fromString("1:2:3:4:5:6"), mInfraNetworkReader);
+ mInfraDevice.runSlaac(Duration.ofSeconds(60));
+ assertNotNull(mInfraDevice.ipv6Addr);
+ }
- // Infra device sends an echo request to FTD's OMR.
- infraDevice.sendEchoRequest(ftdOmr);
+ private void assertInfraLinkMemberOfGroup(Inet6Address address) throws Exception {
+ waitFor(
+ () ->
+ isInMulticastGroup(
+ mInfraNetworkTracker.getTestIface().getInterfaceName(), address),
+ Duration.ofSeconds(3));
+ }
- // Infra device receives an echo reply sent by FTD.
- assertNotNull(
- readPacketFrom(
- infraNetworkReader,
- p -> isExpectedIcmpv6Packet(p, ICMPV6_ECHO_REPLY_TYPE)));
+ private void assertInfraLinkNotMemberOfGroup(Inet6Address address) throws Exception {
+ waitFor(
+ () ->
+ !isInMulticastGroup(
+ mInfraNetworkTracker.getTestIface().getInterfaceName(), address),
+ Duration.ofSeconds(3));
+ }
+
+ private void subscribeMulticastAddressAndWait(FullThreadDevice ftd, Inet6Address address)
+ throws Exception {
+ ftd.subscribeMulticastAddress(address);
+
+ assertInfraLinkMemberOfGroup(address);
+ }
+
+ private byte[] pollForPacketOnInfraNetwork(int type, Inet6Address srcAddress) {
+ return pollForPacketOnInfraNetwork(type, srcAddress, null);
+ }
+
+ private byte[] pollForPacketOnInfraNetwork(
+ int type, Inet6Address srcAddress, Inet6Address destAddress) {
+ Predicate<byte[]> filter;
+ filter =
+ p ->
+ (isExpectedIcmpv6Packet(p, type)
+ && (srcAddress == null ? true : isFromIpv6Source(p, srcAddress))
+ && (destAddress == null
+ ? true
+ : isToIpv6Destination(p, destAddress)));
+ return pollForPacket(mInfraNetworkReader, filter);
}
}
diff --git a/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java b/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java
index 031d205..6cb1675 100644
--- a/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java
+++ b/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java
@@ -35,6 +35,8 @@
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeoutException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* A class that launches and controls a simulation Full Thread Device (FTD).
@@ -73,6 +75,10 @@
mActiveOperationalDataset = null;
}
+ public void destroy() {
+ mProcess.destroy();
+ }
+
/**
* Returns an OMR (Off-Mesh-Routable) address on this device if any.
*
@@ -94,6 +100,45 @@
return null;
}
+ /** Returns the Mesh-local EID address on this device if any. */
+ public Inet6Address getMlEid() {
+ List<String> addresses = executeCommand("ipaddr mleid");
+ return (Inet6Address) InetAddresses.parseNumericAddress(addresses.get(0));
+ }
+
+ /**
+ * Returns the link-local address of the device.
+ *
+ * <p>This methods goes through all unicast addresses on the device and returns the address that
+ * begins with fe80.
+ */
+ public Inet6Address getLinkLocalAddress() {
+ List<String> output = executeCommand("ipaddr linklocal");
+ if (!output.isEmpty() && output.get(0).startsWith("fe80:")) {
+ return (Inet6Address) InetAddresses.parseNumericAddress(output.get(0));
+ }
+ return null;
+ }
+
+ /**
+ * Returns the mesh-local addresses of the device.
+ *
+ * <p>This methods goes through all unicast addresses on the device and returns the address that
+ * begins with mesh-local prefix.
+ */
+ public List<Inet6Address> getMeshLocalAddresses() {
+ List<String> addresses = executeCommand("ipaddr");
+ List<Inet6Address> meshLocalAddresses = new ArrayList<>();
+ IpPrefix meshLocalPrefix = mActiveOperationalDataset.getMeshLocalPrefix();
+ for (String address : addresses) {
+ Inet6Address addr = (Inet6Address) InetAddresses.parseNumericAddress(address);
+ if (meshLocalPrefix.contains(addr)) {
+ meshLocalAddresses.add(addr);
+ }
+ }
+ return meshLocalAddresses;
+ }
+
/**
* Joins the Thread network using the given {@link ActiveOperationalDataset}.
*
@@ -132,6 +177,33 @@
return executeCommand("state").get(0);
}
+ /** Closes the UDP socket. */
+ public void udpClose() {
+ executeCommand("udp close");
+ }
+
+ /** Opens the UDP socket. */
+ public void udpOpen() {
+ executeCommand("udp open");
+ }
+
+ /** Opens the UDP socket and binds it to a specific address and port. */
+ public void udpBind(Inet6Address address, int port) {
+ udpClose();
+ udpOpen();
+ executeCommand(String.format("udp bind %s %d", address.getHostAddress(), port));
+ }
+
+ /** Returns the message received on the UDP socket. */
+ public String udpReceive() throws IOException {
+ Pattern pattern =
+ Pattern.compile("> (\\d+) bytes from ([\\da-f:]+) (\\d+) ([\\x00-\\x7F]+)");
+ Matcher matcher = pattern.matcher(mReader.readLine());
+ matcher.matches();
+
+ return matcher.group(4);
+ }
+
/** Runs the "factoryreset" command on the device. */
public void factoryReset() {
try {
@@ -147,6 +219,27 @@
}
}
+ public void subscribeMulticastAddress(Inet6Address address) {
+ executeCommand("ipmaddr add " + address.getHostAddress());
+ }
+
+ public void ping(Inet6Address address, Inet6Address source, int size, int count) {
+ String cmd =
+ "ping"
+ + ((source == null) ? "" : (" -I " + source.getHostAddress()))
+ + " "
+ + address.getHostAddress()
+ + " "
+ + size
+ + " "
+ + count;
+ executeCommand(cmd);
+ }
+
+ public void ping(Inet6Address address) {
+ ping(address, null, 100 /* size */, 1 /* count */);
+ }
+
private List<String> executeCommand(String command) {
try {
mWriter.write(command + "\n");
diff --git a/thread/tests/integration/src/android/net/thread/utils/InfraNetworkDevice.java b/thread/tests/integration/src/android/net/thread/utils/InfraNetworkDevice.java
index 3081f9f..72a278c 100644
--- a/thread/tests/integration/src/android/net/thread/utils/InfraNetworkDevice.java
+++ b/thread/tests/integration/src/android/net/thread/utils/InfraNetworkDevice.java
@@ -16,7 +16,7 @@
package android.net.thread.utils;
import static android.net.thread.utils.IntegrationTestUtils.getRaPios;
-import static android.net.thread.utils.IntegrationTestUtils.readPacketFrom;
+import static android.net.thread.utils.IntegrationTestUtils.pollForPacket;
import static android.net.thread.utils.IntegrationTestUtils.waitFor;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_SLLA;
@@ -109,7 +109,7 @@
try {
sendRsPacket();
- final byte[] raPacket = readPacketFrom(packetReader, p -> !getRaPios(p).isEmpty());
+ final byte[] raPacket = pollForPacket(packetReader, p -> !getRaPios(p).isEmpty());
final List<PrefixInformationOption> options = getRaPios(raPacket);
diff --git a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
index f223367..74251a6 100644
--- a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
+++ b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
@@ -17,6 +17,7 @@
import static android.system.OsConstants.IPPROTO_ICMPV6;
+import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_PIO;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
@@ -39,6 +40,13 @@
import com.google.common.util.concurrent.SettableFuture;
import java.io.FileDescriptor;
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.time.Duration;
import java.util.ArrayList;
@@ -143,17 +151,17 @@
}
/**
- * Reads a packet from a given {@link TapPacketReader} that satisfies the {@code filter}.
+ * Polls for 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
* @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
*/
- public static byte[] readPacketFrom(TapPacketReader packetReader, Predicate<byte[]> filter) {
+ public static byte[] pollForPacket(TapPacketReader packetReader, Predicate<byte[]> filter) {
byte[] packet;
- while ((packet = packetReader.poll(3000 /* timeoutMs */)) != null) {
- if (filter.test(packet)) return packet;
+ while ((packet = packetReader.poll(3000 /* timeoutMs */, filter)) != null) {
+ return packet;
}
return null;
}
@@ -176,6 +184,34 @@
return false;
}
+ public static boolean isFromIpv6Source(byte[] packet, Inet6Address src) {
+ if (packet == null) {
+ return false;
+ }
+ ByteBuffer buf = ByteBuffer.wrap(packet);
+ try {
+ return Struct.parse(Ipv6Header.class, buf).srcIp.equals(src);
+ } catch (IllegalArgumentException ignored) {
+ // It's fine that the passed in packet is malformed because it's could be sent
+ // by anybody.
+ }
+ return false;
+ }
+
+ public static boolean isToIpv6Destination(byte[] packet, Inet6Address dest) {
+ if (packet == null) {
+ return false;
+ }
+ ByteBuffer buf = ByteBuffer.wrap(packet);
+ try {
+ return Struct.parse(Ipv6Header.class, buf).dstIp.equals(dest);
+ } catch (IllegalArgumentException ignored) {
+ // It's fine that the passed in packet is malformed because it's could be sent
+ // by anybody.
+ }
+ return false;
+ }
+
/** Returns the Prefix Information Options (PIO) extracted from an ICMPv6 RA message. */
public static List<PrefixInformationOption> getRaPios(byte[] raMsg) {
final ArrayList<PrefixInformationOption> pioList = new ArrayList<>();
@@ -219,4 +255,38 @@
}
return pioList;
}
+
+ /**
+ * Sends a UDP message to a destination.
+ *
+ * @param dstAddress the IP address of the destination
+ * @param dstPort the port of the destination
+ * @param message the message in UDP payload
+ * @throws IOException if failed to send the message
+ */
+ public static void sendUdpMessage(InetAddress dstAddress, int dstPort, String message)
+ throws IOException {
+ SocketAddress dstSockAddr = new InetSocketAddress(dstAddress, dstPort);
+
+ try (DatagramSocket socket = new DatagramSocket()) {
+ socket.connect(dstSockAddr);
+
+ byte[] msgBytes = message.getBytes();
+ DatagramPacket packet = new DatagramPacket(msgBytes, msgBytes.length);
+
+ socket.send(packet);
+ }
+ }
+
+ public static boolean isInMulticastGroup(String interfaceName, Inet6Address address) {
+ final String cmd = "ip -6 maddr show dev " + interfaceName;
+ final String output = runShellCommandOrThrow(cmd);
+ final String addressStr = address.getHostAddress();
+ for (final String line : output.split("\\n")) {
+ if (line.contains(addressStr)) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/thread/tests/unit/AndroidTest.xml b/thread/tests/unit/AndroidTest.xml
index 26813c1..d16e423 100644
--- a/thread/tests/unit/AndroidTest.xml
+++ b/thread/tests/unit/AndroidTest.xml
@@ -19,6 +19,18 @@
<option name="test-tag" value="ThreadNetworkUnitTests" />
<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.suite.SuiteApkInstaller">
<option name="test-file-name" value="ThreadNetworkUnitTests.apk" />
<option name="check-min-sdk" value="true" />
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 f626edf..4948c22 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
@@ -16,32 +16,50 @@
package com.android.server.thread;
+import static android.Manifest.permission.ACCESS_NETWORK_STATE;
+import static android.net.thread.ActiveOperationalDataset.CHANNEL_PAGE_24_GHZ;
+import static android.net.thread.ThreadNetworkController.STATE_DISABLED;
+import static android.net.thread.ThreadNetworkController.STATE_ENABLED;
+import static android.net.thread.ThreadNetworkException.ERROR_FAILED_PRECONDITION;
import static android.net.thread.ThreadNetworkException.ERROR_INTERNAL_ERROR;
+import static android.net.thread.ThreadNetworkManager.DISALLOW_THREAD_NETWORK;
import static android.net.thread.ThreadNetworkManager.PERMISSION_THREAD_NETWORK_PRIVILEGED;
-import static com.android.testutils.TestPermissionUtil.runAsShell;
+import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_INVALID_STATE;
import static com.google.common.io.BaseEncoding.base16;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkAgent;
import android.net.NetworkProvider;
import android.net.thread.ActiveOperationalDataset;
+import android.net.thread.IActiveOperationalDatasetReceiver;
import android.net.thread.IOperationReceiver;
+import android.net.thread.ThreadNetworkException;
import android.os.Handler;
+import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
+import android.os.UserManager;
import android.os.test.TestLooper;
import androidx.test.core.app.ApplicationProvider;
@@ -53,9 +71,15 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicReference;
+
/** Unit tests for {@link ThreadNetworkControllerService}. */
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -80,6 +104,12 @@
+ "B9D351B40C0402A0FFF8");
private static final ActiveOperationalDataset DEFAULT_ACTIVE_DATASET =
ActiveOperationalDataset.fromThreadTlvs(DEFAULT_ACTIVE_DATASET_TLVS);
+ private static final String DEFAULT_NETWORK_NAME = "thread-wpan0";
+ private static final int OT_ERROR_NONE = 0;
+ private static final int DEFAULT_SUPPORTED_CHANNEL_MASK = 0x07FFF800; // from channel 11 to 26
+ private static final int DEFAULT_PREFERRED_CHANNEL_MASK = 0x00000800; // channel 11
+ private static final int DEFAULT_SELECTED_CHANNEL = 11;
+ private static final byte[] DEFAULT_SUPPORTED_CHANNEL_MASK_ARRAY = base16().decode("001FFFE0");
@Mock private ConnectivityManager mMockConnectivityManager;
@Mock private NetworkAgent mMockNetworkAgent;
@@ -88,30 +118,38 @@
@Mock private InfraInterfaceController mMockInfraIfController;
@Mock private ThreadPersistentSettings mMockPersistentSettings;
@Mock private NsdPublisher mMockNsdPublisher;
+ @Mock private UserManager mMockUserManager;
+ @Mock private IBinder mIBinder;
private Context mContext;
private TestLooper mTestLooper;
private FakeOtDaemon mFakeOtDaemon;
private ThreadNetworkControllerService mService;
+ @Captor private ArgumentCaptor<ActiveOperationalDataset> mActiveDatasetCaptor;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mContext = ApplicationProvider.getApplicationContext();
+ mContext = spy(ApplicationProvider.getApplicationContext());
+ doNothing()
+ .when(mContext)
+ .enforceCallingOrSelfPermission(
+ eq(PERMISSION_THREAD_NETWORK_PRIVILEGED), anyString());
+
mTestLooper = new TestLooper();
final Handler handler = new Handler(mTestLooper.getLooper());
NetworkProvider networkProvider =
new NetworkProvider(mContext, mTestLooper.getLooper(), "ThreadNetworkProvider");
mFakeOtDaemon = new FakeOtDaemon(handler);
-
when(mMockTunIfController.getTunFd()).thenReturn(mMockTunFd);
when(mMockPersistentSettings.get(any())).thenReturn(true);
+ when(mMockUserManager.hasUserRestriction(eq(DISALLOW_THREAD_NETWORK))).thenReturn(false);
mService =
new ThreadNetworkControllerService(
- ApplicationProvider.getApplicationContext(),
+ mContext,
handler,
networkProvider,
() -> mFakeOtDaemon,
@@ -119,7 +157,8 @@
mMockTunIfController,
mMockInfraIfController,
mMockPersistentSettings,
- mMockNsdPublisher);
+ mMockNsdPublisher,
+ mMockUserManager);
mService.setTestNetworkAgent(mMockNetworkAgent);
}
@@ -141,9 +180,7 @@
final IOperationReceiver mockReceiver = mock(IOperationReceiver.class);
mFakeOtDaemon.setJoinException(new RemoteException("ot-daemon join() throws"));
- runAsShell(
- PERMISSION_THREAD_NETWORK_PRIVILEGED,
- () -> mService.join(DEFAULT_ACTIVE_DATASET, mockReceiver));
+ mService.join(DEFAULT_ACTIVE_DATASET, mockReceiver);
mTestLooper.dispatchAll();
verify(mockReceiver, never()).onSuccess();
@@ -155,9 +192,7 @@
mService.initialize();
final IOperationReceiver mockReceiver = mock(IOperationReceiver.class);
- runAsShell(
- PERMISSION_THREAD_NETWORK_PRIVILEGED,
- () -> mService.join(DEFAULT_ACTIVE_DATASET, mockReceiver));
+ mService.join(DEFAULT_ACTIVE_DATASET, mockReceiver);
// Here needs to call Testlooper#dispatchAll twices because TestLooper#moveTimeForward
// operates on only currently enqueued messages but the delayed message is posted from
// another Handler task.
@@ -168,4 +203,134 @@
verify(mockReceiver, times(1)).onSuccess();
verify(mMockNetworkAgent, times(1)).register();
}
+
+ @Test
+ public void userRestriction_initWithUserRestricted_threadIsDisabled() {
+ when(mMockUserManager.hasUserRestriction(eq(DISALLOW_THREAD_NETWORK))).thenReturn(true);
+
+ mService.initialize();
+ mTestLooper.dispatchAll();
+
+ assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(STATE_DISABLED);
+ }
+
+ @Test
+ public void userRestriction_initWithUserNotRestricted_threadIsEnabled() {
+ when(mMockUserManager.hasUserRestriction(eq(DISALLOW_THREAD_NETWORK))).thenReturn(false);
+
+ mService.initialize();
+ mTestLooper.dispatchAll();
+
+ assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(STATE_ENABLED);
+ }
+
+ @Test
+ public void userRestriction_userBecomesRestricted_stateIsDisabledButNotPersisted() {
+ AtomicReference<BroadcastReceiver> receiverRef = new AtomicReference<>();
+ when(mMockUserManager.hasUserRestriction(eq(DISALLOW_THREAD_NETWORK))).thenReturn(false);
+ doAnswer(
+ invocation -> {
+ receiverRef.set((BroadcastReceiver) invocation.getArguments()[0]);
+ return null;
+ })
+ .when(mContext)
+ .registerReceiver(any(BroadcastReceiver.class), any(), any(), any());
+ mService.initialize();
+ mTestLooper.dispatchAll();
+
+ when(mMockUserManager.hasUserRestriction(eq(DISALLOW_THREAD_NETWORK))).thenReturn(true);
+ receiverRef.get().onReceive(mContext, new Intent());
+ mTestLooper.dispatchAll();
+
+ assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(STATE_DISABLED);
+ verify(mMockPersistentSettings, never())
+ .put(eq(ThreadPersistentSettings.THREAD_ENABLED.key), eq(false));
+ }
+
+ @Test
+ public void userRestriction_userBecomesNotRestricted_stateIsEnabledButNotPersisted() {
+ AtomicReference<BroadcastReceiver> receiverRef = new AtomicReference<>();
+ when(mMockUserManager.hasUserRestriction(eq(DISALLOW_THREAD_NETWORK))).thenReturn(true);
+ doAnswer(
+ invocation -> {
+ receiverRef.set((BroadcastReceiver) invocation.getArguments()[0]);
+ return null;
+ })
+ .when(mContext)
+ .registerReceiver(any(BroadcastReceiver.class), any(), any(), any());
+ mService.initialize();
+ mTestLooper.dispatchAll();
+
+ when(mMockUserManager.hasUserRestriction(eq(DISALLOW_THREAD_NETWORK))).thenReturn(false);
+ receiverRef.get().onReceive(mContext, new Intent());
+ mTestLooper.dispatchAll();
+
+ assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(STATE_ENABLED);
+ verify(mMockPersistentSettings, never())
+ .put(eq(ThreadPersistentSettings.THREAD_ENABLED.key), eq(true));
+ }
+
+ @Test
+ public void userRestriction_setEnabledWhenUserRestricted_failedPreconditionError() {
+ when(mMockUserManager.hasUserRestriction(eq(DISALLOW_THREAD_NETWORK))).thenReturn(true);
+ mService.initialize();
+
+ CompletableFuture<Void> setEnabledFuture = new CompletableFuture<>();
+ mService.setEnabled(true, newOperationReceiver(setEnabledFuture));
+ mTestLooper.dispatchAll();
+
+ var thrown = assertThrows(ExecutionException.class, () -> setEnabledFuture.get());
+ ThreadNetworkException failure = (ThreadNetworkException) thrown.getCause();
+ assertThat(failure.getErrorCode()).isEqualTo(ERROR_FAILED_PRECONDITION);
+ }
+
+ private static IOperationReceiver newOperationReceiver(CompletableFuture<Void> future) {
+ return new IOperationReceiver.Stub() {
+ @Override
+ public void onSuccess() {
+ future.complete(null);
+ }
+
+ @Override
+ public void onError(int errorCode, String errorMessage) {
+ future.completeExceptionally(new ThreadNetworkException(errorCode, errorMessage));
+ }
+ };
+ }
+
+ @Test
+ public void createRandomizedDataset_succeed_activeDatasetCreated() throws Exception {
+ final IActiveOperationalDatasetReceiver mockReceiver =
+ mock(IActiveOperationalDatasetReceiver.class);
+ mFakeOtDaemon.setChannelMasks(
+ DEFAULT_SUPPORTED_CHANNEL_MASK, DEFAULT_PREFERRED_CHANNEL_MASK);
+ mFakeOtDaemon.setChannelMasksReceiverOtError(OT_ERROR_NONE);
+
+ mService.createRandomizedDataset(DEFAULT_NETWORK_NAME, mockReceiver);
+ mTestLooper.dispatchAll();
+
+ verify(mockReceiver, never()).onError(anyInt(), anyString());
+ verify(mockReceiver, times(1)).onSuccess(mActiveDatasetCaptor.capture());
+ ActiveOperationalDataset activeDataset = mActiveDatasetCaptor.getValue();
+ assertThat(activeDataset.getNetworkName()).isEqualTo(DEFAULT_NETWORK_NAME);
+ assertThat(activeDataset.getChannelMask().size()).isEqualTo(1);
+ assertThat(activeDataset.getChannelMask().get(CHANNEL_PAGE_24_GHZ))
+ .isEqualTo(DEFAULT_SUPPORTED_CHANNEL_MASK_ARRAY);
+ assertThat(activeDataset.getChannel()).isEqualTo(DEFAULT_SELECTED_CHANNEL);
+ }
+
+ @Test
+ public void createRandomizedDataset_otDaemonRemoteFailure_returnsPreconditionError()
+ throws Exception {
+ final IActiveOperationalDatasetReceiver mockReceiver =
+ mock(IActiveOperationalDatasetReceiver.class);
+ mFakeOtDaemon.setChannelMasksReceiverOtError(OT_ERROR_INVALID_STATE);
+ when(mockReceiver.asBinder()).thenReturn(mIBinder);
+
+ mService.createRandomizedDataset(DEFAULT_NETWORK_NAME, mockReceiver);
+ mTestLooper.dispatchAll();
+
+ verify(mockReceiver, never()).onSuccess(any(ActiveOperationalDataset.class));
+ verify(mockReceiver, times(1)).onError(eq(ERROR_INTERNAL_ERROR), anyString());
+ }
}
diff --git a/thread/tests/unit/src/android/net/thread/ThreadPersistentSettingsTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java
similarity index 67%
rename from thread/tests/unit/src/android/net/thread/ThreadPersistentSettingsTest.java
rename to thread/tests/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java
index 11aabb8..927b5ae 100644
--- a/thread/tests/unit/src/android/net/thread/ThreadPersistentSettingsTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java
@@ -23,18 +23,22 @@
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
import static org.mockito.Mockito.validateMockitoUsage;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.res.Resources;
import android.os.PersistableBundle;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.AtomicFile;
import androidx.test.runner.AndroidJUnit4;
+import com.android.connectivity.resources.R;
+import com.android.server.connectivity.ConnectivityResources;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -51,16 +55,22 @@
@SmallTest
public class ThreadPersistentSettingsTest {
@Mock private AtomicFile mAtomicFile;
+ @Mock Resources mResources;
+ @Mock ConnectivityResources mConnectivityResources;
- private ThreadPersistentSettings mThreadPersistentSetting;
+ private ThreadPersistentSettings mThreadPersistentSettings;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ when(mConnectivityResources.get()).thenReturn(mResources);
+ when(mResources.getBoolean(eq(R.bool.config_thread_default_enabled))).thenReturn(true);
+
FileOutputStream fos = mock(FileOutputStream.class);
when(mAtomicFile.startWrite()).thenReturn(fos);
- mThreadPersistentSetting = new ThreadPersistentSettings(mAtomicFile);
+ mThreadPersistentSettings =
+ new ThreadPersistentSettings(mAtomicFile, mConnectivityResources);
}
/** Called after each test */
@@ -70,10 +80,42 @@
}
@Test
- public void put_ThreadFeatureEnabledTrue_returnsTrue() throws Exception {
- mThreadPersistentSetting.put(THREAD_ENABLED.key, true);
+ public void initialize_readsFromFile() throws Exception {
+ byte[] data = createXmlForParsing(THREAD_ENABLED.key, false);
+ setupAtomicFileMockForRead(data);
- assertThat(mThreadPersistentSetting.get(THREAD_ENABLED)).isTrue();
+ mThreadPersistentSettings.initialize();
+
+ assertThat(mThreadPersistentSettings.get(THREAD_ENABLED)).isFalse();
+ }
+
+ @Test
+ public void initialize_ThreadDisabledInResources_returnsThreadDisabled() throws Exception {
+ when(mResources.getBoolean(eq(R.bool.config_thread_default_enabled))).thenReturn(false);
+ setupAtomicFileMockForRead(new byte[0]);
+
+ mThreadPersistentSettings.initialize();
+
+ assertThat(mThreadPersistentSettings.get(THREAD_ENABLED)).isFalse();
+ }
+
+ @Test
+ public void initialize_ThreadDisabledInResourcesButEnabledInXml_returnsThreadEnabled()
+ throws Exception {
+ when(mResources.getBoolean(eq(R.bool.config_thread_default_enabled))).thenReturn(false);
+ byte[] data = createXmlForParsing(THREAD_ENABLED.key, true);
+ setupAtomicFileMockForRead(data);
+
+ mThreadPersistentSettings.initialize();
+
+ assertThat(mThreadPersistentSettings.get(THREAD_ENABLED)).isTrue();
+ }
+
+ @Test
+ public void put_ThreadFeatureEnabledTrue_returnsTrue() throws Exception {
+ mThreadPersistentSettings.put(THREAD_ENABLED.key, true);
+
+ assertThat(mThreadPersistentSettings.get(THREAD_ENABLED)).isTrue();
// Confirm that file writes have been triggered.
verify(mAtomicFile).startWrite();
verify(mAtomicFile).finishWrite(any());
@@ -81,26 +123,14 @@
@Test
public void put_ThreadFeatureEnabledFalse_returnsFalse() throws Exception {
- mThreadPersistentSetting.put(THREAD_ENABLED.key, false);
+ mThreadPersistentSettings.put(THREAD_ENABLED.key, false);
- assertThat(mThreadPersistentSetting.get(THREAD_ENABLED)).isFalse();
+ assertThat(mThreadPersistentSettings.get(THREAD_ENABLED)).isFalse();
// Confirm that file writes have been triggered.
verify(mAtomicFile).startWrite();
verify(mAtomicFile).finishWrite(any());
}
- @Test
- public void initialize_readsFromFile() throws Exception {
- byte[] data = createXmlForParsing(THREAD_ENABLED.key, false);
- setupAtomicFileMockForRead(data);
-
- // Trigger file read.
- mThreadPersistentSetting.initialize();
-
- assertThat(mThreadPersistentSetting.get(THREAD_ENABLED)).isFalse();
- verify(mAtomicFile, never()).startWrite();
- }
-
private byte[] createXmlForParsing(String key, Boolean value) throws Exception {
PersistableBundle bundle = new PersistableBundle();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
diff --git a/tools/Android.bp b/tools/Android.bp
index b7b2aaa..9216b5b 100644
--- a/tools/Android.bp
+++ b/tools/Android.bp
@@ -42,6 +42,7 @@
name: "jarjar-rules-generator-testjavalib",
srcs: ["testdata/java/**/*.java"],
libs: ["unsupportedappusage"],
+ sdk_version: "core_platform",
visibility: ["//visibility:private"],
}
@@ -56,6 +57,7 @@
static_libs: [
"framework-connectivity.stubs.module_lib",
],
+ sdk_version: "module_current",
// Not strictly necessary but specified as this MUST not have generate
// a dex jar as that will break the tests.
compile_dex: false,
@@ -67,6 +69,7 @@
static_libs: [
"framework-connectivity-t.stubs.module_lib",
],
+ sdk_version: "module_current",
// Not strictly necessary but specified as this MUST not have generate
// a dex jar as that will break the tests.
compile_dex: false,