Merge "Use custom defined DNS/HTTP port" 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 2cb9b2f..c382e76 100644
--- a/common/FlaggedApi.bp
+++ b/common/FlaggedApi.bp
@@ -17,6 +17,7 @@
aconfig_declarations {
name: "com.android.net.flags-aconfig",
package: "com.android.net.flags",
+ container: "system",
srcs: ["flags.aconfig"],
visibility: ["//packages/modules/Connectivity:__subpackages__"],
}
diff --git a/common/flags.aconfig b/common/flags.aconfig
index 8eb3cbf..8c448e6 100644
--- a/common/flags.aconfig
+++ b/common/flags.aconfig
@@ -1,16 +1,10 @@
package: "com.android.net.flags"
+container: "system"
# This file contains aconfig flags for FlaggedAPI annotations
# Flags used from platform code must be in under frameworks
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"
@@ -38,3 +32,51 @@
bug: "294777050"
}
+flag {
+ 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/framework-t/Android.bp b/framework-t/Android.bp
index f485a44..9203a3e 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: [
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 1356eea..8fa336a 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -195,6 +195,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/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java
index efae754..f6ef75e 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",
};
/**
@@ -2794,10 +2813,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..2bfaee4 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,59 @@
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) {
+ const char * args[] = { apexNetBpfLoad, NULL, };
+ execve(args[0], (char**)args, envp);
+ ALOGW("exec '%s' fail: %d[%s]", apexNetBpfLoad, errno, strerror(errno));
+ }
+
+ if (!has_platform_bpfloader_rc && !has_platform_netbpfload_rc) {
+ ALOGE("Unable to find platform's bpfloader & netbpfload init scripts.");
+ return 1;
+ }
+
+ if (has_platform_bpfloader_rc && has_platform_netbpfload_rc) {
+ ALOGE("Platform has *both* bpfloader & netbpfload init scripts.");
+ return 1;
+ }
+
+ logTetheringApexVersion();
+
+ if (is_mainline && has_platform_bpfloader_rc && !has_platform_netbpfload_rc) {
+ // Tethering apex shipped initrc file causes us to reach here
+ // but we're not ready to correctly handle anything before U QPR2
+ // in which the 'bpfloader' vs 'netbpfload' split happened
+ const char * args[] = { platformBpfLoader, NULL, };
+ execve(args[0], (char**)args, envp);
+ ALOGE("exec '%s' fail: %d[%s]", platformBpfLoader, errno, strerror(errno));
+ return 1;
+ }
+
if (isAtLeastT && !android::bpf::isAtLeastKernelVersion(4, 9, 0)) {
ALOGE("Android T requires kernel 4.9.");
return 1;
@@ -210,6 +301,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 +391,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/remoteauth/service/jni/src/remoteauth_jni_android_platform.rs b/remoteauth/service/jni/src/remoteauth_jni_android_platform.rs
index 0a189f2..421fe7e 100644
--- a/remoteauth/service/jni/src/remoteauth_jni_android_platform.rs
+++ b/remoteauth/service/jni/src/remoteauth_jni_android_platform.rs
@@ -67,7 +67,7 @@
logger::init(
logger::Config::default()
.with_tag_on_device("remoteauth")
- .with_min_level(log::Level::Trace)
+ .with_max_level(log::LevelFilter::Trace)
.with_filter("trace,jni=info"),
);
}
diff --git a/remoteauth/service/jni/src/remoteauth_jni_android_protocol.rs b/remoteauth/service/jni/src/remoteauth_jni_android_protocol.rs
index ac2eb8c..e44ab8b 100644
--- a/remoteauth/service/jni/src/remoteauth_jni_android_protocol.rs
+++ b/remoteauth/service/jni/src/remoteauth_jni_android_protocol.rs
@@ -30,7 +30,7 @@
logger::init(
logger::Config::default()
.with_tag_on_device("remoteauth")
- .with_min_level(log::Level::Trace)
+ .with_max_level(log::LevelFilter::Trace)
.with_filter("trace,jni=info"),
);
get_boolean_result(native_init(env), "native_init")
diff --git a/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/MdnsRecordRepository.java b/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
index fb45454..96a59e2 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);
}
}
diff --git a/service/Android.bp b/service/Android.bp
index c6ecadd..403ba7d 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",
@@ -265,6 +267,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/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..3d646fd 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.
@@ -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..0968aff
--- /dev/null
+++ b/service/src/com/android/server/connectivity/SatelliteAccessController.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.app.role.OnRoleHoldersChangedListener;
+import android.app.role.RoleManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.Process;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Tracks the uid of all the default messaging application which are role_sms role and
+ * satellite_communication permission complaint and requests ConnectivityService to create multi
+ * layer request with satellite internet access support for the default message application.
+ * @hide
+ */
+public class SatelliteAccessController {
+ private static final String TAG = SatelliteAccessController.class.getSimpleName();
+ private final PackageManager mPackageManager;
+ private final Dependencies mDeps;
+ private final DefaultMessageRoleListener mDefaultMessageRoleListener;
+ private final Consumer<Set<Integer>> mCallback;
+ private final Set<Integer> mSatelliteNetworkPreferredUidCache = new ArraySet<>();
+ private final Handler mConnectivityServiceHandler;
+
+ /**
+ * Monitor {@link android.app.role.OnRoleHoldersChangedListener#onRoleHoldersChanged(String,
+ * UserHandle)},
+ *
+ */
+ private final class DefaultMessageRoleListener
+ implements OnRoleHoldersChangedListener {
+ @Override
+ public void onRoleHoldersChanged(String role, UserHandle user) {
+ if (RoleManager.ROLE_SMS.equals(role)) {
+ Log.i(TAG, "ROLE_SMS Change detected ");
+ onRoleSmsChanged();
+ }
+ }
+
+ public void register() {
+ try {
+ mDeps.addOnRoleHoldersChangedListenerAsUser(
+ mConnectivityServiceHandler::post, this, UserHandle.ALL);
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Could not register satellite controller listener due to " + e);
+ }
+ }
+ }
+
+ public SatelliteAccessController(@NonNull final Context c,
+ Consumer<Set<Integer>> callback,
+ @NonNull final Handler connectivityServiceInternalHandler) {
+ this(c, new Dependencies(c), callback, connectivityServiceInternalHandler);
+ }
+
+ public static class Dependencies {
+ private final RoleManager mRoleManager;
+
+ private Dependencies(Context context) {
+ mRoleManager = context.getSystemService(RoleManager.class);
+ }
+
+ /** See {@link RoleManager#getRoleHolders(String)} */
+ public List<String> getRoleHolders(String roleName) {
+ return mRoleManager.getRoleHolders(roleName);
+ }
+
+ /** See {@link RoleManager#addOnRoleHoldersChangedListenerAsUser} */
+ public void addOnRoleHoldersChangedListenerAsUser(@NonNull Executor executor,
+ @NonNull OnRoleHoldersChangedListener listener, UserHandle user) {
+ mRoleManager.addOnRoleHoldersChangedListenerAsUser(executor, listener, user);
+ }
+ }
+
+ @VisibleForTesting
+ SatelliteAccessController(@NonNull final Context c, @NonNull final Dependencies deps,
+ Consumer<Set<Integer>> callback,
+ @NonNull final Handler connectivityServiceInternalHandler) {
+ mDeps = deps;
+ mPackageManager = c.getPackageManager();
+ mDefaultMessageRoleListener = new DefaultMessageRoleListener();
+ mCallback = callback;
+ mConnectivityServiceHandler = connectivityServiceInternalHandler;
+ }
+
+ private void updateSatelliteNetworkPreferredUidListCache(List<String> packageNames) {
+ for (String packageName : packageNames) {
+ // Check if SATELLITE_COMMUNICATION permission is enabled for default sms application
+ // package before adding it part of satellite network preferred uid cache list.
+ if (isSatellitePermissionEnabled(packageName)) {
+ mSatelliteNetworkPreferredUidCache.add(getUidForPackage(packageName));
+ }
+ }
+ }
+
+ //Check if satellite communication is enabled for the package
+ private boolean isSatellitePermissionEnabled(String packageName) {
+ if (mPackageManager != null) {
+ return mPackageManager.checkPermission(
+ Manifest.permission.SATELLITE_COMMUNICATION, packageName)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+ return false;
+ }
+
+ private int getUidForPackage(String pkgName) {
+ if (pkgName == null) {
+ return Process.INVALID_UID;
+ }
+ try {
+ if (mPackageManager != null) {
+ ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo(pkgName, 0);
+ if (applicationInfo != null) {
+ return applicationInfo.uid;
+ }
+ }
+ } catch (PackageManager.NameNotFoundException exception) {
+ Log.e(TAG, "Unable to find uid for package: " + pkgName);
+ }
+ return Process.INVALID_UID;
+ }
+
+ //on Role sms change triggered by OnRoleHoldersChangedListener()
+ private void onRoleSmsChanged() {
+ final List<String> packageNames = getRoleSmsChangedPackageName();
+
+ // Create a new Set
+ Set<Integer> previousSatellitePreferredUid = new ArraySet<>(
+ mSatelliteNetworkPreferredUidCache);
+
+ mSatelliteNetworkPreferredUidCache.clear();
+
+ if (packageNames != null) {
+ Log.i(TAG, "role_sms_packages: " + packageNames);
+ // On Role change listener, update the satellite network preferred uid cache list
+ updateSatelliteNetworkPreferredUidListCache(packageNames);
+ Log.i(TAG, "satellite_preferred_uid: " + mSatelliteNetworkPreferredUidCache);
+ } else {
+ Log.wtf(TAG, "package name was found null");
+ }
+
+ // on Role change, update the multilayer request at ConnectivityService with updated
+ // satellite network preferred uid cache list if changed or to revoke for previous default
+ // sms app
+ if (!mSatelliteNetworkPreferredUidCache.equals(previousSatellitePreferredUid)) {
+ Log.i(TAG, "update multi layer request");
+ mCallback.accept(mSatelliteNetworkPreferredUidCache);
+ }
+ }
+
+ private List<String> getRoleSmsChangedPackageName() {
+ try {
+ return mDeps.getRoleHolders(RoleManager.ROLE_SMS);
+ } catch (RuntimeException e) {
+ Log.wtf(TAG, "Could not get package name at role sms change update due to: " + e);
+ return null;
+ }
+ }
+
+ /** Register OnRoleHoldersChangedListener */
+ public void start() {
+ mConnectivityServiceHandler.post(this::onRoleSmsChanged);
+ mDefaultMessageRoleListener.register();
+ }
+}
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index 3cbabcc..47e897d 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -248,7 +248,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/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..f0edee2 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -1552,6 +1552,40 @@
}
}
+ @Test @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) @ConnectivityModuleTest
+ public void testSetBackgroundNetworkingShellCommand() {
+ final int testUid = 54352;
+ runShellCommand("cmd connectivity set-background-networking-enabled-for-uid " + testUid
+ + " true");
+ int rule = runAsShell(NETWORK_SETTINGS,
+ () -> mCm.getUidFirewallRule(FIREWALL_CHAIN_BACKGROUND, testUid));
+ assertEquals(rule, FIREWALL_RULE_ALLOW);
+
+ runShellCommand("cmd connectivity set-background-networking-enabled-for-uid " + testUid
+ + " false");
+ rule = runAsShell(NETWORK_SETTINGS,
+ () -> mCm.getUidFirewallRule(FIREWALL_CHAIN_BACKGROUND, testUid));
+ assertEquals(rule, FIREWALL_RULE_DENY);
+ }
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) @ConnectivityModuleTest
+ public void testGetBackgroundNetworkingShellCommand() {
+ final int testUid = 54312;
+ runAsShell(NETWORK_SETTINGS,
+ () -> mCm.setUidFirewallRule(FIREWALL_CHAIN_BACKGROUND, testUid,
+ FIREWALL_RULE_ALLOW));
+ String output = runShellCommand(
+ "cmd connectivity get-background-networking-enabled-for-uid " + testUid);
+ assertTrue(output.contains("allow"));
+
+ runAsShell(NETWORK_SETTINGS,
+ () -> mCm.setUidFirewallRule(FIREWALL_CHAIN_BACKGROUND, testUid,
+ FIREWALL_RULE_DEFAULT));
+ output = runShellCommand(
+ "cmd connectivity get-background-networking-enabled-for-uid " + testUid);
+ assertTrue(output.contains("deny"));
+ }
+
// TODO: move the following socket keep alive test to dedicated test class.
/**
* Callback used in tcp keepalive offload that allows caller to wait callback fires.
diff --git a/tests/cts/net/src/android/net/cts/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/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..6db372f 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"
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/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..64a515a
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/SatelliteAccessControllerTest.kt
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.connectivity
+
+import android.Manifest
+import android.app.role.OnRoleHoldersChangedListener
+import android.app.role.RoleManager
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.os.Build
+import android.os.Handler
+import android.os.UserHandle
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import java.util.concurrent.Executor
+import java.util.function.Consumer
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+private const val DEFAULT_MESSAGING_APP1 = "default_messaging_app_1"
+private const val DEFAULT_MESSAGING_APP2 = "default_messaging_app_2"
+private const val DEFAULT_MESSAGING_APP1_UID = 1234
+private const val DEFAULT_MESSAGING_APP2_UID = 5678
+
+@RunWith(DevSdkIgnoreRunner::class)
+@IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+class SatelliteAccessControllerTest {
+ private val context = mock(Context::class.java)
+ private val mPackageManager = mock(PackageManager::class.java)
+ private val mHandler = mock(Handler::class.java)
+ private val mRoleManager =
+ mock(SatelliteAccessController.Dependencies::class.java)
+ private val mCallback = mock(Consumer::class.java) as Consumer<Set<Int>>
+ private val mSatelliteAccessController by lazy {
+ SatelliteAccessController(context, mRoleManager, mCallback, mHandler)}
+ private var mRoleHolderChangedListener: OnRoleHoldersChangedListener? = null
+ @Before
+ @Throws(PackageManager.NameNotFoundException::class)
+ fun setup() {
+ doReturn(mPackageManager).`when`(context).packageManager
+ doReturn(PackageManager.PERMISSION_GRANTED)
+ .`when`(mPackageManager)
+ .checkPermission(Manifest.permission.SATELLITE_COMMUNICATION, DEFAULT_MESSAGING_APP1)
+ doReturn(PackageManager.PERMISSION_GRANTED)
+ .`when`(mPackageManager)
+ .checkPermission(Manifest.permission.SATELLITE_COMMUNICATION, DEFAULT_MESSAGING_APP2)
+
+ // Initialise default message application package1
+ val applicationInfo1 = ApplicationInfo()
+ applicationInfo1.uid = DEFAULT_MESSAGING_APP1_UID
+ doReturn(applicationInfo1)
+ .`when`(mPackageManager)
+ .getApplicationInfo(eq(DEFAULT_MESSAGING_APP1), anyInt())
+
+ // Initialise default message application package2
+ val applicationInfo2 = ApplicationInfo()
+ applicationInfo2.uid = DEFAULT_MESSAGING_APP2_UID
+ doReturn(applicationInfo2)
+ .`when`(mPackageManager)
+ .getApplicationInfo(eq(DEFAULT_MESSAGING_APP2), anyInt())
+
+ // Get registered listener using captor
+ val listenerCaptor = ArgumentCaptor.forClass(
+ OnRoleHoldersChangedListener::class.java
+ )
+ mSatelliteAccessController.start()
+ verify(mRoleManager).addOnRoleHoldersChangedListenerAsUser(
+ any(Executor::class.java), listenerCaptor.capture(), any(UserHandle::class.java))
+ mRoleHolderChangedListener = listenerCaptor.value
+ }
+
+ @Test
+ fun test_onRoleHoldersChanged_SatellitePreferredUid_Changed() {
+ doReturn(listOf<String>()).`when`(mRoleManager).getRoleHolders(RoleManager.ROLE_SMS)
+ val satelliteNetworkPreferredSet =
+ ArgumentCaptor.forClass(Set::class.java) as ArgumentCaptor<Set<Int>>
+ mRoleHolderChangedListener?.onRoleHoldersChanged(RoleManager.ROLE_SMS, UserHandle.ALL)
+ verify(mCallback, never()).accept(satelliteNetworkPreferredSet.capture())
+
+ // check DEFAULT_MESSAGING_APP1 is available as satellite network preferred uid
+ doReturn(listOf(DEFAULT_MESSAGING_APP1))
+ .`when`(mRoleManager).getRoleHolders(RoleManager.ROLE_SMS)
+ mRoleHolderChangedListener?.onRoleHoldersChanged(RoleManager.ROLE_SMS, UserHandle.ALL)
+ verify(mCallback).accept(satelliteNetworkPreferredSet.capture())
+ var satelliteNetworkPreferredUids = satelliteNetworkPreferredSet.value
+ assertEquals(1, satelliteNetworkPreferredUids.size)
+ assertTrue(satelliteNetworkPreferredUids.contains(DEFAULT_MESSAGING_APP1_UID))
+ assertFalse(satelliteNetworkPreferredUids.contains(DEFAULT_MESSAGING_APP2_UID))
+
+ // check DEFAULT_MESSAGING_APP1 and DEFAULT_MESSAGING_APP2 is available
+ // as satellite network preferred uid
+ val dmas: MutableList<String> = ArrayList()
+ dmas.add(DEFAULT_MESSAGING_APP1)
+ dmas.add(DEFAULT_MESSAGING_APP2)
+ doReturn(dmas).`when`(mRoleManager).getRoleHolders(RoleManager.ROLE_SMS)
+ mRoleHolderChangedListener?.onRoleHoldersChanged(RoleManager.ROLE_SMS, UserHandle.ALL)
+ verify(mCallback, times(2))
+ .accept(satelliteNetworkPreferredSet.capture())
+ satelliteNetworkPreferredUids = satelliteNetworkPreferredSet.value
+ assertEquals(2, satelliteNetworkPreferredUids.size)
+ assertTrue(satelliteNetworkPreferredUids.contains(DEFAULT_MESSAGING_APP1_UID))
+ assertTrue(satelliteNetworkPreferredUids.contains(DEFAULT_MESSAGING_APP2_UID))
+
+ // check no uid is available as satellite network preferred uid
+ doReturn(listOf<String>()).`when`(mRoleManager).getRoleHolders(RoleManager.ROLE_SMS)
+ mRoleHolderChangedListener?.onRoleHoldersChanged(RoleManager.ROLE_SMS, UserHandle.ALL)
+ verify(mCallback, times(3))
+ .accept(satelliteNetworkPreferredSet.capture())
+ satelliteNetworkPreferredUids = satelliteNetworkPreferredSet.value
+ assertEquals(0, satelliteNetworkPreferredUids.size)
+ assertFalse(satelliteNetworkPreferredUids.contains(DEFAULT_MESSAGING_APP1_UID))
+ assertFalse(satelliteNetworkPreferredUids.contains(DEFAULT_MESSAGING_APP2_UID))
+
+ // No Change received at OnRoleSmsChanged, check callback not triggered
+ doReturn(listOf<String>()).`when`(mRoleManager).getRoleHolders(RoleManager.ROLE_SMS)
+ mRoleHolderChangedListener?.onRoleHoldersChanged(RoleManager.ROLE_SMS, UserHandle.ALL)
+ verify(mCallback, times(3))
+ .accept(satelliteNetworkPreferredSet.capture())
+ }
+
+ @Test
+ fun test_onRoleHoldersChanged_NoSatelliteCommunicationPermission() {
+ doReturn(listOf<Any>()).`when`(mRoleManager).getRoleHolders(RoleManager.ROLE_SMS)
+ val satelliteNetworkPreferredSet =
+ ArgumentCaptor.forClass(Set::class.java) as ArgumentCaptor<Set<Int>>
+ mRoleHolderChangedListener?.onRoleHoldersChanged(RoleManager.ROLE_SMS, UserHandle.ALL)
+ verify(mCallback, never()).accept(satelliteNetworkPreferredSet.capture())
+
+ // check DEFAULT_MESSAGING_APP1 is not available as satellite network preferred uid
+ // since satellite communication permission not available.
+ doReturn(PackageManager.PERMISSION_DENIED)
+ .`when`(mPackageManager)
+ .checkPermission(Manifest.permission.SATELLITE_COMMUNICATION, DEFAULT_MESSAGING_APP1)
+ doReturn(listOf(DEFAULT_MESSAGING_APP1))
+ .`when`(mRoleManager).getRoleHolders(RoleManager.ROLE_SMS)
+ mRoleHolderChangedListener?.onRoleHoldersChanged(RoleManager.ROLE_SMS, UserHandle.ALL)
+ verify(mCallback, never()).accept(satelliteNetworkPreferredSet.capture())
+ }
+
+ @Test
+ fun test_onRoleHoldersChanged_RoleSms_NotAvailable() {
+ doReturn(listOf(DEFAULT_MESSAGING_APP1))
+ .`when`(mRoleManager).getRoleHolders(RoleManager.ROLE_SMS)
+ val satelliteNetworkPreferredSet =
+ ArgumentCaptor.forClass(Set::class.java) as ArgumentCaptor<Set<Int>>
+ mRoleHolderChangedListener?.onRoleHoldersChanged(RoleManager.ROLE_BROWSER, UserHandle.ALL)
+ verify(mCallback, never()).accept(satelliteNetworkPreferredSet.capture())
+ }
+}
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkFallbackTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkFallbackTest.kt
new file mode 100644
index 0000000..9024641
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkFallbackTest.kt
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server
+
+import android.net.IpPrefix
+import android.net.INetd
+import android.net.LinkAddress
+import android.net.LinkProperties
+import android.net.NativeNetworkConfig
+import android.net.NativeNetworkType
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED
+import android.net.NetworkScore
+import android.net.NetworkCapabilities.TRANSPORT_SATELLITE
+import android.net.NetworkScore.KEEP_CONNECTED_FOR_TEST
+import android.net.RouteInfo
+import android.net.UidRange
+import android.net.UidRangeParcel
+import android.net.VpnManager
+import android.net.netd.aidl.NativeUidRangeConfig
+import android.os.Build
+import android.os.UserHandle
+import android.util.ArraySet
+import com.android.net.module.util.CollectionUtils
+import com.android.server.ConnectivityService.PREFERENCE_ORDER_SATELLITE_FALLBACK
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.visibleOnHandlerThread
+import org.junit.Assert
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.never
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+private const val SECONDARY_USER = 10
+private val SECONDARY_USER_HANDLE = UserHandle(SECONDARY_USER)
+private const val TEST_PACKAGE_UID = 123
+private const val TEST_PACKAGE_UID2 = 321
+
+@DevSdkIgnoreRunner.MonitorThreadLeak
+@RunWith(DevSdkIgnoreRunner::class)
+@IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+class CSSatelliteNetworkPreferredTest : CSTest() {
+ /**
+ * Test createMultiLayerNrisFromSatelliteNetworkPreferredUids returns correct
+ * NetworkRequestInfo.
+ */
+ @Test
+ fun testCreateMultiLayerNrisFromSatelliteNetworkPreferredUids() {
+ // Verify that empty uid set should not create any NRI for it.
+ val nrisNoUid = service.createMultiLayerNrisFromSatelliteNetworkFallbackUids(emptySet())
+ Assert.assertEquals(0, nrisNoUid.size.toLong())
+ val uid1 = PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID)
+ val uid2 = PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID2)
+ val uid3 = SECONDARY_USER_HANDLE.getUid(TEST_PACKAGE_UID)
+ assertCreateMultiLayerNrisFromSatelliteNetworkPreferredUids(mutableSetOf(uid1))
+ assertCreateMultiLayerNrisFromSatelliteNetworkPreferredUids(mutableSetOf(uid1, uid3))
+ assertCreateMultiLayerNrisFromSatelliteNetworkPreferredUids(mutableSetOf(uid1, uid2))
+ }
+
+ /**
+ * Test that SATELLITE_NETWORK_PREFERENCE_UIDS changes will send correct net id and uid ranges
+ * to netd.
+ */
+ @Test
+ fun testSatelliteNetworkPreferredUidsChanged() {
+ val netdInOrder = inOrder(netd)
+
+ val satelliteAgent = createSatelliteAgent("satellite0")
+ satelliteAgent.connect()
+
+ val satelliteNetId = satelliteAgent.network.netId
+ netdInOrder.verify(netd).networkCreate(
+ nativeNetworkConfigPhysical(satelliteNetId, INetd.PERMISSION_NONE))
+
+ val uid1 = PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID)
+ val uid2 = PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID2)
+ val uid3 = SECONDARY_USER_HANDLE.getUid(TEST_PACKAGE_UID)
+
+ // Initial satellite network preferred uids status.
+ setAndUpdateSatelliteNetworkPreferredUids(setOf())
+ netdInOrder.verify(netd, never()).networkAddUidRangesParcel(any())
+ netdInOrder.verify(netd, never()).networkRemoveUidRangesParcel(any())
+
+ // Set SATELLITE_NETWORK_PREFERENCE_UIDS setting and verify that net id and uid ranges
+ // send to netd
+ var uids = mutableSetOf(uid1, uid2, uid3)
+ val uidRanges1 = toUidRangeStableParcels(uidRangesForUids(uids))
+ val config1 = NativeUidRangeConfig(
+ satelliteNetId, uidRanges1,
+ PREFERENCE_ORDER_SATELLITE_FALLBACK
+ )
+ setAndUpdateSatelliteNetworkPreferredUids(uids)
+ netdInOrder.verify(netd).networkAddUidRangesParcel(config1)
+ netdInOrder.verify(netd, never()).networkRemoveUidRangesParcel(any())
+
+ // Set SATELLITE_NETWORK_PREFERENCE_UIDS setting again and verify that old rules are removed
+ // and new rules are added.
+ uids = mutableSetOf(uid1)
+ val uidRanges2: Array<UidRangeParcel?> = toUidRangeStableParcels(uidRangesForUids(uids))
+ val config2 = NativeUidRangeConfig(
+ satelliteNetId, uidRanges2,
+ PREFERENCE_ORDER_SATELLITE_FALLBACK
+ )
+ setAndUpdateSatelliteNetworkPreferredUids(uids)
+ netdInOrder.verify(netd).networkRemoveUidRangesParcel(config1)
+ netdInOrder.verify(netd).networkAddUidRangesParcel(config2)
+ }
+
+ private fun assertCreateMultiLayerNrisFromSatelliteNetworkPreferredUids(uids: Set<Int>) {
+ val nris: Set<ConnectivityService.NetworkRequestInfo> =
+ service.createMultiLayerNrisFromSatelliteNetworkFallbackUids(uids)
+ val nri = nris.iterator().next()
+ // Verify that one NRI is created with multilayer requests. Because one NRI can contain
+ // multiple uid ranges, so it only need create one NRI here.
+ assertEquals(1, nris.size.toLong())
+ assertTrue(nri.isMultilayerRequest)
+ assertEquals(nri.uids, uidRangesForUids(uids))
+ assertEquals(PREFERENCE_ORDER_SATELLITE_FALLBACK, nri.mPreferenceOrder)
+ }
+
+ private fun setAndUpdateSatelliteNetworkPreferredUids(uids: Set<Int>) {
+ visibleOnHandlerThread(csHandler) {
+ deps.satelliteNetworkFallbackUidUpdate!!.accept(uids)
+ }
+ }
+
+ private fun nativeNetworkConfigPhysical(netId: Int, permission: Int) =
+ NativeNetworkConfig(netId, NativeNetworkType.PHYSICAL, permission,
+ false /* secure */, VpnManager.TYPE_VPN_NONE, false /* excludeLocalRoutes */)
+
+ private fun createSatelliteAgent(name: String): CSAgentWrapper {
+ return Agent(score = keepScore(), lp = lp(name),
+ nc = nc(TRANSPORT_SATELLITE, NET_CAPABILITY_INTERNET)
+ )
+ }
+
+ private fun toUidRangeStableParcels(ranges: Set<UidRange>): Array<UidRangeParcel?> {
+ val stableRanges = arrayOfNulls<UidRangeParcel>(ranges.size)
+ for ((index, range) in ranges.withIndex()) {
+ stableRanges[index] = UidRangeParcel(range.start, range.stop)
+ }
+ return stableRanges
+ }
+
+ private fun uidRangesForUids(vararg uids: Int): Set<UidRange> {
+ val ranges = ArraySet<UidRange>()
+ for (uid in uids) {
+ ranges.add(UidRange(uid, uid))
+ }
+ return ranges
+ }
+
+ private fun uidRangesForUids(uids: Collection<Int>): Set<UidRange> {
+ return uidRangesForUids(*CollectionUtils.toIntArray(uids))
+ }
+
+ private fun nc(transport: Int, vararg caps: Int) = NetworkCapabilities.Builder().apply {
+ addTransportType(transport)
+ caps.forEach {
+ addCapability(it)
+ }
+ // Useful capabilities for everybody
+ addCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ addCapability(NET_CAPABILITY_NOT_SUSPENDED)
+ addCapability(NET_CAPABILITY_NOT_ROAMING)
+ addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ }.build()
+
+ private fun lp(iface: String) = LinkProperties().apply {
+ interfaceName = iface
+ addLinkAddress(LinkAddress(LOCAL_IPV4_ADDRESS, 32))
+ addRoute(RouteInfo(IpPrefix("0.0.0.0/0"), null, null))
+ }
+
+ // This allows keeping all the networks connected without having to file individual requests
+ // for them.
+ private fun keepScore() = FromS(
+ NetworkScore.Builder().setKeepConnectedReason(KEEP_CONNECTED_FOR_TEST).build()
+ )
+}
diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
index 0708669..e401434 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
@@ -66,13 +67,16 @@
import com.android.server.connectivity.MultinetworkPolicyTrackerTestDependencies
import com.android.server.connectivity.NetworkRequestStateStatsMetrics
import com.android.server.connectivity.ProxyTracker
-import com.android.server.connectivity.RoutingCoordinatorService
+import com.android.server.connectivity.SatelliteAccessController
import com.android.testutils.visibleOnHandlerThread
import com.android.testutils.waitForIdle
import java.util.concurrent.Executors
+import java.util.function.Consumer
+import java.util.function.BiConsumer
import kotlin.test.assertNull
import kotlin.test.fail
import org.junit.After
+import org.junit.Before
import org.mockito.AdditionalAnswers.delegatesTo
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.doReturn
@@ -133,10 +137,12 @@
// permissions using static contexts.
val enabledFeatures = HashMap<String, Boolean>().also {
it[ConnectivityFlags.NO_REMATCH_ALL_REQUESTS_ON_REGISTER] = true
+ it[ConnectivityFlags.REQUEST_RESTRICTED_WIFI] = true
it[ConnectivityService.KEY_DESTROY_FROZEN_SOCKETS_VERSION] = true
it[ConnectivityService.DELAY_DESTROY_FROZEN_SOCKETS_VERSION] = true
it[ConnectivityService.ALLOW_SYSUI_CONNECTIVITY_REPORTS] = true
it[ConnectivityService.LOG_BPF_RC] = true
+ it[ConnectivityService.ALLOW_SATALLITE_NETWORK_FALLBACK] = true
}
fun enableFeature(f: String) = enabledFeatures.set(f, true)
fun disableFeature(f: String) = enabledFeatures.set(f, false)
@@ -163,8 +169,6 @@
val clatCoordinator = mock<ClatCoordinator>()
val networkRequestStateStatsMetrics = mock<NetworkRequestStateStatsMetrics>()
val proxyTracker = ProxyTracker(context, mock<Handler>(), 16 /* EVENT_PROXY_HAS_CHANGED */)
- val alrmHandlerThread = HandlerThread("TestAlarmManager").also { it.start() }
- val alarmManager = makeMockAlarmManager(alrmHandlerThread)
val systemConfigManager = makeMockSystemConfigManager()
val batteryStats = mock<IBatteryStats>()
val batteryManager = BatteryStatsManager(batteryStats)
@@ -173,18 +177,34 @@
}
val multicastRoutingCoordinatorService = mock<MulticastRoutingCoordinatorService>()
+ val satelliteAccessController = mock<SatelliteAccessController>()
val deps = CSDeps()
- val service = makeConnectivityService(context, netd, deps).also { it.systemReadyInternal() }
- val cm = ConnectivityManager(context, service)
- val csHandler = Handler(csHandlerThread.looper)
+
+ // Initializations that start threads are done from setUp to avoid thread leak
+ lateinit var alarmHandlerThread: HandlerThread
+ lateinit var alarmManager: AlarmManager
+ lateinit var service: ConnectivityService
+ lateinit var cm: ConnectivityManager
+ lateinit var csHandler: Handler
+
+ @Before
+ fun setUp() {
+ alarmHandlerThread = HandlerThread("TestAlarmManager").also { it.start() }
+ alarmManager = makeMockAlarmManager(alarmHandlerThread)
+ service = makeConnectivityService(context, netd, deps).also { it.systemReadyInternal() }
+ cm = ConnectivityManager(context, service)
+ // csHandler initialization must be after makeConnectivityService since ConnectivityService
+ // constructor starts csHandlerThread
+ csHandler = Handler(csHandlerThread.looper)
+ }
@After
fun tearDown() {
csHandlerThread.quitSafely()
csHandlerThread.join()
- alrmHandlerThread.quitSafely()
- alrmHandlerThread.join()
+ alarmHandlerThread.quitSafely()
+ alarmHandlerThread.join()
}
inner class CSDeps : ConnectivityService.Dependencies() {
@@ -200,9 +220,21 @@
override fun makeCarrierPrivilegeAuthenticator(
context: Context,
- tm: TelephonyManager
+ tm: TelephonyManager,
+ requestRestrictedWifiEnabled: Boolean,
+ listener: 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)
diff --git a/thread/flags/thread_base.aconfig b/thread/flags/thread_base.aconfig
index bf1f288..09595a6 100644
--- a/thread/flags/thread_base.aconfig
+++ b/thread/flags/thread_base.aconfig
@@ -1,4 +1,5 @@
package: "com.android.net.thread.flags"
+container: "system"
flag {
name: "thread_enabled"
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..b584487 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/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
index 21e3927..44745b3 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
@@ -41,6 +41,7 @@
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;
@@ -64,7 +65,10 @@
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.LinkAddress;
import android.net.LinkProperties;
@@ -98,6 +102,7 @@
import android.os.Looper;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.os.UserManager;
import android.util.Log;
import android.util.SparseArray;
@@ -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.");
diff --git a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkExceptionTest.java b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkExceptionTest.java
index 6770175..7d9ae81 100644
--- a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkExceptionTest.java
+++ b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkExceptionTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * 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.
@@ -32,7 +32,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-/** Tests for {@link ThreadNetworkException}. */
+/** CTS tests for {@link ThreadNetworkException}. */
@SmallTest
@RunWith(AndroidJUnit4.class)
public final class ThreadNetworkExceptionTest {
@@ -57,8 +57,9 @@
}
@Test
- public void constructor_invalidErrorCode_throwsIllegalArgumentException() throws Exception {
+ public void constructor_tooSmallErrorCode_throwsIllegalArgumentException() throws Exception {
assertThrows(IllegalArgumentException.class, () -> new ThreadNetworkException(0, "0"));
- assertThrows(IllegalArgumentException.class, () -> new ThreadNetworkException(13, "13"));
+ // TODO: add argument check for too large error code when mainline CTS is ready. This was
+ // not added here for CTS forward copatibility.
}
}
diff --git a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
index 29ada1b..2fccf6b 100644
--- a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
+++ b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
@@ -21,10 +21,12 @@
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.isSimulatedThreadRadioSupported;
import static android.net.thread.utils.IntegrationTestUtils.newPacketReader;
import static android.net.thread.utils.IntegrationTestUtils.readPacketFrom;
+import static android.net.thread.utils.IntegrationTestUtils.sendUdpMessage;
import static android.net.thread.utils.IntegrationTestUtils.waitFor;
import static android.net.thread.utils.IntegrationTestUtils.waitForStateAnyOf;
@@ -35,10 +37,13 @@
import static com.google.common.io.BaseEncoding.base16;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assume.assumeNotNull;
import static org.junit.Assume.assumeTrue;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
import android.content.Context;
import android.net.LinkProperties;
import android.net.MacAddress;
@@ -62,6 +67,7 @@
import java.net.Inet6Address;
import java.time.Duration;
import java.util.List;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -187,4 +193,44 @@
infraNetworkReader,
p -> isExpectedIcmpv6Packet(p, ICMPV6_ECHO_REPLY_TYPE)));
}
+
+ @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(), 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(6 /* node ID */);
+ 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);
+ 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());
+ }
}
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..5ca40e3 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).
@@ -94,6 +96,12 @@
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));
+ }
+
/**
* Joins the Thread network using the given {@link ActiveOperationalDataset}.
*
@@ -132,6 +140,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 {
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..4eef0e5 100644
--- a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
+++ b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
@@ -39,6 +39,12 @@
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.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.time.Duration;
import java.util.ArrayList;
@@ -219,4 +225,26 @@
}
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);
+ }
+ }
}
diff --git a/thread/tests/unit/src/android/net/thread/ThreadNetworkExceptionTest.java b/thread/tests/unit/src/android/net/thread/ThreadNetworkExceptionTest.java
new file mode 100644
index 0000000..f62b437
--- /dev/null
+++ b/thread/tests/unit/src/android/net/thread/ThreadNetworkExceptionTest.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.thread;
+
+import static org.junit.Assert.assertThrows;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit tests for {@link ThreadNetworkException} to cover what is not covered in CTS tests. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class ThreadNetworkExceptionTest {
+ @Test
+ public void constructor_tooLargeErrorCode_throwsIllegalArgumentException() throws Exception {
+ // TODO (b/323791003): move this test case to cts/ThreadNetworkExceptionTest when mainline
+ // CTS is ready.
+ assertThrows(IllegalArgumentException.class, () -> new ThreadNetworkException(13, "13"));
+ }
+}
diff --git a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
index f626edf..1640679 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
@@ -16,7 +16,11 @@
package com.android.server.thread;
+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;
@@ -24,24 +28,31 @@
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.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
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.IOperationReceiver;
+import android.net.thread.ThreadNetworkException;
import android.os.Handler;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
+import android.os.UserManager;
import android.os.test.TestLooper;
import androidx.test.core.app.ApplicationProvider;
@@ -56,6 +67,10 @@
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)
@@ -88,6 +103,7 @@
@Mock private InfraInterfaceController mMockInfraIfController;
@Mock private ThreadPersistentSettings mMockPersistentSettings;
@Mock private NsdPublisher mMockNsdPublisher;
+ @Mock private UserManager mMockUserManager;
private Context mContext;
private TestLooper mTestLooper;
private FakeOtDaemon mFakeOtDaemon;
@@ -97,21 +113,21 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
- mContext = ApplicationProvider.getApplicationContext();
+ mContext = spy(ApplicationProvider.getApplicationContext());
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 +135,8 @@
mMockTunIfController,
mMockInfraIfController,
mMockPersistentSettings,
- mMockNsdPublisher);
+ mMockNsdPublisher,
+ mMockUserManager);
mService.setTestNetworkAgent(mMockNetworkAgent);
}
@@ -168,4 +185,100 @@
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<>();
+ runAsShell(
+ PERMISSION_THREAD_NETWORK_PRIVILEGED,
+ () -> 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));
+ }
+ };
+ }
}
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,