Merge "CaptivePortal: implement setDelegateUid API." into main
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index 0c05354..19dd492 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -155,7 +155,10 @@
"framework-connectivity",
"framework-connectivity-t",
"framework-tethering",
- ],
+ ] + select(release_flag("RELEASE_MOVE_VCN_TO_MAINLINE"), {
+ true: ["framework-connectivity-b"],
+ default: [],
+ }),
apex_available: ["com.android.tethering"],
// The bootclasspath_fragments that provide APIs on which this depends.
@@ -195,6 +198,7 @@
"android.net.http",
"android.net.netstats",
"android.net.util",
+ "android.net.vcn",
],
// The following packages and all their subpackages currently only
diff --git a/Tethering/common/TetheringLib/api/current.txt b/Tethering/common/TetheringLib/api/current.txt
index d802177..932e801 100644
--- a/Tethering/common/TetheringLib/api/current.txt
+++ b/Tethering/common/TetheringLib/api/current.txt
@@ -1 +1,71 @@
// Signature format: 2.0
+package android.net {
+
+ public final class TetheringInterface implements android.os.Parcelable {
+ ctor public TetheringInterface(int, @NonNull String);
+ ctor @FlaggedApi("com.android.net.flags.tethering_with_soft_ap_config") public TetheringInterface(int, @NonNull String, @Nullable android.net.wifi.SoftApConfiguration);
+ method public int describeContents();
+ method @NonNull public String getInterface();
+ method @FlaggedApi("com.android.net.flags.tethering_with_soft_ap_config") @Nullable @RequiresPermission(value=android.Manifest.permission.NETWORK_SETTINGS, conditional=true) public android.net.wifi.SoftApConfiguration getSoftApConfiguration();
+ method public int getType();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.TetheringInterface> CREATOR;
+ }
+
+ public class TetheringManager {
+ method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.TetheringEventCallback);
+ method @RequiresPermission(value=android.Manifest.permission.TETHER_PRIVILEGED, conditional=true) public void startTethering(@NonNull android.net.TetheringManager.TetheringRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.StartTetheringCallback);
+ method @FlaggedApi("com.android.net.flags.tethering_with_soft_ap_config") @RequiresPermission(value=android.Manifest.permission.TETHER_PRIVILEGED, conditional=true) public void stopTethering(@NonNull android.net.TetheringManager.TetheringRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.StopTetheringCallback);
+ method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.ACCESS_NETWORK_STATE}) public void unregisterTetheringEventCallback(@NonNull android.net.TetheringManager.TetheringEventCallback);
+ field public static final int CONNECTIVITY_SCOPE_GLOBAL = 1; // 0x1
+ field public static final int TETHERING_WIFI = 0; // 0x0
+ field public static final int TETHER_ERROR_DHCPSERVER_ERROR = 12; // 0xc
+ field public static final int TETHER_ERROR_DISABLE_FORWARDING_ERROR = 9; // 0x9
+ field @FlaggedApi("com.android.net.flags.tethering_with_soft_ap_config") public static final int TETHER_ERROR_DUPLICATE_REQUEST = 18; // 0x12
+ field public static final int TETHER_ERROR_ENABLE_FORWARDING_ERROR = 8; // 0x8
+ field public static final int TETHER_ERROR_ENTITLEMENT_UNKNOWN = 13; // 0xd
+ field public static final int TETHER_ERROR_IFACE_CFG_ERROR = 10; // 0xa
+ field public static final int TETHER_ERROR_INTERNAL_ERROR = 5; // 0x5
+ field public static final int TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION = 15; // 0xf
+ field public static final int TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION = 14; // 0xe
+ field public static final int TETHER_ERROR_NO_ERROR = 0; // 0x0
+ field public static final int TETHER_ERROR_PROVISIONING_FAILED = 11; // 0xb
+ field public static final int TETHER_ERROR_SERVICE_UNAVAIL = 2; // 0x2
+ field public static final int TETHER_ERROR_TETHER_IFACE_ERROR = 6; // 0x6
+ field public static final int TETHER_ERROR_UNAVAIL_IFACE = 4; // 0x4
+ field public static final int TETHER_ERROR_UNKNOWN_IFACE = 1; // 0x1
+ field @FlaggedApi("com.android.net.flags.tethering_with_soft_ap_config") public static final int TETHER_ERROR_UNKNOWN_REQUEST = 17; // 0x11
+ field public static final int TETHER_ERROR_UNKNOWN_TYPE = 16; // 0x10
+ field public static final int TETHER_ERROR_UNSUPPORTED = 3; // 0x3
+ field public static final int TETHER_ERROR_UNTETHER_IFACE_ERROR = 7; // 0x7
+ }
+
+ public static interface TetheringManager.StartTetheringCallback {
+ method public default void onTetheringFailed(int);
+ method public default void onTetheringStarted();
+ }
+
+ @FlaggedApi("com.android.net.flags.tethering_with_soft_ap_config") public static interface TetheringManager.StopTetheringCallback {
+ method public default void onStopTetheringFailed(int);
+ method public default void onStopTetheringSucceeded();
+ }
+
+ public static interface TetheringManager.TetheringEventCallback {
+ method public default void onTetheredInterfacesChanged(@NonNull java.util.Set<android.net.TetheringInterface>);
+ }
+
+ public static final class TetheringManager.TetheringRequest implements android.os.Parcelable {
+ method @FlaggedApi("com.android.net.flags.tethering_with_soft_ap_config") public int describeContents();
+ method @FlaggedApi("com.android.net.flags.tethering_with_soft_ap_config") @Nullable public android.net.wifi.SoftApConfiguration getSoftApConfiguration();
+ method @FlaggedApi("com.android.net.flags.tethering_with_soft_ap_config") public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @FlaggedApi("com.android.net.flags.tethering_with_soft_ap_config") @NonNull public static final android.os.Parcelable.Creator<android.net.TetheringManager.TetheringRequest> CREATOR;
+ }
+
+ public static class TetheringManager.TetheringRequest.Builder {
+ ctor public TetheringManager.TetheringRequest.Builder(int);
+ method @NonNull public android.net.TetheringManager.TetheringRequest build();
+ method @FlaggedApi("com.android.net.flags.tethering_with_soft_ap_config") @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setSoftApConfiguration(@Nullable android.net.wifi.SoftApConfiguration);
+ }
+
+}
+
diff --git a/Tethering/common/TetheringLib/api/module-lib-current.txt b/Tethering/common/TetheringLib/api/module-lib-current.txt
index e893894..01bd983 100644
--- a/Tethering/common/TetheringLib/api/module-lib-current.txt
+++ b/Tethering/common/TetheringLib/api/module-lib-current.txt
@@ -47,9 +47,14 @@
}
public static final class TetheringManager.TetheringRequest implements android.os.Parcelable {
+ method @FlaggedApi("com.android.net.flags.tethering_with_soft_ap_config") @Nullable public String getInterfaceName();
method @FlaggedApi("com.android.net.flags.tethering_with_soft_ap_config") @Nullable public String getPackageName();
method @FlaggedApi("com.android.net.flags.tethering_with_soft_ap_config") public int getUid();
}
+ public static class TetheringManager.TetheringRequest.Builder {
+ method @FlaggedApi("com.android.net.flags.tethering_with_soft_ap_config") @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public android.net.TetheringManager.TetheringRequest.Builder setInterfaceName(@Nullable String);
+ }
+
}
diff --git a/Tethering/common/TetheringLib/api/system-current.txt b/Tethering/common/TetheringLib/api/system-current.txt
index 0e85956..3b9708e 100644
--- a/Tethering/common/TetheringLib/api/system-current.txt
+++ b/Tethering/common/TetheringLib/api/system-current.txt
@@ -19,26 +19,11 @@
field @NonNull public static final android.os.Parcelable.Creator<android.net.TetheredClient.AddressInfo> CREATOR;
}
- public final class TetheringInterface implements android.os.Parcelable {
- ctor public TetheringInterface(int, @NonNull String);
- ctor @FlaggedApi("com.android.net.flags.tethering_with_soft_ap_config") public TetheringInterface(int, @NonNull String, @Nullable android.net.wifi.SoftApConfiguration);
- method public int describeContents();
- method @NonNull public String getInterface();
- method @FlaggedApi("com.android.net.flags.tethering_with_soft_ap_config") @Nullable @RequiresPermission(value=android.Manifest.permission.NETWORK_SETTINGS, conditional=true) public android.net.wifi.SoftApConfiguration getSoftApConfiguration();
- method public int getType();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.net.TetheringInterface> CREATOR;
- }
-
public class TetheringManager {
- method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.TetheringEventCallback);
method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void requestLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.OnTetheringEntitlementResultListener);
- method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void startTethering(@NonNull android.net.TetheringManager.TetheringRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.StartTetheringCallback);
method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void stopAllTethering();
method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void stopTethering(int);
- method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.ACCESS_NETWORK_STATE}) public void unregisterTetheringEventCallback(@NonNull android.net.TetheringManager.TetheringEventCallback);
field @Deprecated public static final String ACTION_TETHER_STATE_CHANGED = "android.net.conn.TETHER_STATE_CHANGED";
- field public static final int CONNECTIVITY_SCOPE_GLOBAL = 1; // 0x1
field public static final int CONNECTIVITY_SCOPE_LOCAL = 2; // 0x2
field public static final String EXTRA_ACTIVE_LOCAL_ONLY = "android.net.extra.ACTIVE_LOCAL_ONLY";
field public static final String EXTRA_ACTIVE_TETHER = "tetherArray";
@@ -50,25 +35,7 @@
field public static final int TETHERING_NCM = 4; // 0x4
field public static final int TETHERING_USB = 1; // 0x1
field @FlaggedApi("com.android.net.flags.tethering_request_virtual") public static final int TETHERING_VIRTUAL = 7; // 0x7
- field public static final int TETHERING_WIFI = 0; // 0x0
field public static final int TETHERING_WIFI_P2P = 3; // 0x3
- field public static final int TETHER_ERROR_DHCPSERVER_ERROR = 12; // 0xc
- field public static final int TETHER_ERROR_DISABLE_FORWARDING_ERROR = 9; // 0x9
- field public static final int TETHER_ERROR_ENABLE_FORWARDING_ERROR = 8; // 0x8
- field public static final int TETHER_ERROR_ENTITLEMENT_UNKNOWN = 13; // 0xd
- field public static final int TETHER_ERROR_IFACE_CFG_ERROR = 10; // 0xa
- field public static final int TETHER_ERROR_INTERNAL_ERROR = 5; // 0x5
- field public static final int TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION = 15; // 0xf
- field public static final int TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION = 14; // 0xe
- field public static final int TETHER_ERROR_NO_ERROR = 0; // 0x0
- field public static final int TETHER_ERROR_PROVISIONING_FAILED = 11; // 0xb
- field public static final int TETHER_ERROR_SERVICE_UNAVAIL = 2; // 0x2
- field public static final int TETHER_ERROR_TETHER_IFACE_ERROR = 6; // 0x6
- field public static final int TETHER_ERROR_UNAVAIL_IFACE = 4; // 0x4
- field public static final int TETHER_ERROR_UNKNOWN_IFACE = 1; // 0x1
- field public static final int TETHER_ERROR_UNKNOWN_TYPE = 16; // 0x10
- field public static final int TETHER_ERROR_UNSUPPORTED = 3; // 0x3
- field public static final int TETHER_ERROR_UNTETHER_IFACE_ERROR = 7; // 0x7
field public static final int TETHER_HARDWARE_OFFLOAD_FAILED = 2; // 0x2
field public static final int TETHER_HARDWARE_OFFLOAD_STARTED = 1; // 0x1
field public static final int TETHER_HARDWARE_OFFLOAD_STOPPED = 0; // 0x0
@@ -78,11 +45,6 @@
method public void onTetheringEntitlementResult(int);
}
- public static interface TetheringManager.StartTetheringCallback {
- method public default void onTetheringFailed(int);
- method public default void onTetheringStarted();
- }
-
public static interface TetheringManager.TetheringEventCallback {
method public default void onClientsChanged(@NonNull java.util.Collection<android.net.TetheredClient>);
method public default void onError(@NonNull String, int);
@@ -93,31 +55,23 @@
method public default void onTetherableInterfacesChanged(@NonNull java.util.List<java.lang.String>);
method public default void onTetherableInterfacesChanged(@NonNull java.util.Set<android.net.TetheringInterface>);
method public default void onTetheredInterfacesChanged(@NonNull java.util.List<java.lang.String>);
- method public default void onTetheredInterfacesChanged(@NonNull java.util.Set<android.net.TetheringInterface>);
method public default void onTetheringSupported(boolean);
method public default void onUpstreamChanged(@Nullable android.net.Network);
}
public static final class TetheringManager.TetheringRequest implements android.os.Parcelable {
- method @FlaggedApi("com.android.net.flags.tethering_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 @FlaggedApi("com.android.net.flags.tethering_with_soft_ap_config") @Nullable public android.net.wifi.SoftApConfiguration getSoftApConfiguration();
method public int getTetheringType();
method public boolean isExemptFromEntitlementCheck();
- method @FlaggedApi("com.android.net.flags.tethering_with_soft_ap_config") public void writeToParcel(@NonNull android.os.Parcel, int);
- field @FlaggedApi("com.android.net.flags.tethering_with_soft_ap_config") @NonNull public static final android.os.Parcelable.Creator<android.net.TetheringManager.TetheringRequest> CREATOR;
}
public static class TetheringManager.TetheringRequest.Builder {
- ctor public TetheringManager.TetheringRequest.Builder(int);
- method @NonNull public android.net.TetheringManager.TetheringRequest build();
method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setConnectivityScope(int);
method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setExemptFromEntitlementCheck(boolean);
method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setShouldShowEntitlementUi(boolean);
- method @FlaggedApi("com.android.net.flags.tethering_with_soft_ap_config") @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setSoftApConfiguration(@Nullable android.net.wifi.SoftApConfiguration);
method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setStaticIpv4Addresses(@NonNull android.net.LinkAddress, @NonNull android.net.LinkAddress);
}
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringInterface.java b/Tethering/common/TetheringLib/src/android/net/TetheringInterface.java
index 0464fe0..250179a 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringInterface.java
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringInterface.java
@@ -21,7 +21,6 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
-import android.annotation.SystemApi;
import android.net.TetheringManager.TetheringType;
import android.net.wifi.SoftApConfiguration;
import android.os.Parcel;
@@ -33,9 +32,8 @@
/**
* The mapping of tethering interface and type.
- * @hide
*/
-@SystemApi
+@SuppressLint("UnflaggedApi")
public final class TetheringInterface implements Parcelable {
private final int mType;
private final String mInterface;
@@ -57,12 +55,14 @@
}
/** Get tethering type. */
+ @SuppressLint("UnflaggedApi")
public int getType() {
return mType;
}
/** Get tethering interface. */
@NonNull
+ @SuppressLint("UnflaggedApi")
public String getInterface() {
return mInterface;
}
@@ -75,11 +75,13 @@
@FlaggedApi(Flags.FLAG_TETHERING_WITH_SOFT_AP_CONFIG)
@RequiresPermission(value = android.Manifest.permission.NETWORK_SETTINGS, conditional = true)
@Nullable
+ @SuppressLint("UnflaggedApi")
public SoftApConfiguration getSoftApConfiguration() {
return mSoftApConfig;
}
@Override
+ @SuppressLint("UnflaggedApi")
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mType);
dest.writeString(mInterface);
@@ -87,11 +89,13 @@
}
@Override
+ @SuppressLint("UnflaggedApi")
public int hashCode() {
return Objects.hash(mType, mInterface, mSoftApConfig);
}
@Override
+ @SuppressLint("UnflaggedApi")
public boolean equals(@Nullable Object obj) {
if (!(obj instanceof TetheringInterface)) return false;
final TetheringInterface other = (TetheringInterface) obj;
@@ -100,11 +104,13 @@
}
@Override
+ @SuppressLint("UnflaggedApi")
public int describeContents() {
return 0;
}
@NonNull
+ @SuppressLint("UnflaggedApi")
public static final Creator<TetheringInterface> CREATOR = new Creator<TetheringInterface>() {
@NonNull
@Override
@@ -116,6 +122,7 @@
@NonNull
@Override
+ @SuppressLint("UnflaggedApi")
public TetheringInterface[] newArray(int size) {
return new TetheringInterface[size];
}
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index bc771da..25bfb45 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -63,9 +63,8 @@
* <p> The primary responsibilities of this class are to provide the APIs for applications to
* start tethering, stop tethering, query configuration and query status.
*
- * @hide
*/
-@SystemApi
+@SuppressLint({"NotCloseable", "UnflaggedApi"})
public class TetheringManager {
private static final String TAG = TetheringManager.class.getSimpleName();
private static final int DEFAULT_TIMEOUT_MS = 60_000;
@@ -95,36 +94,46 @@
* {@code TetheringManager.EXTRA_ERRORED_TETHER} to indicate
* the current state of tethering. Each include a list of
* interface names in that state (may be empty).
+ * @hide
*
* @deprecated New client should use TetheringEventCallback instead.
*/
@Deprecated
+ @SystemApi
public static final String ACTION_TETHER_STATE_CHANGED =
"android.net.conn.TETHER_STATE_CHANGED";
/**
* gives a String[] listing all the interfaces configured for
* tethering and currently available for tethering.
+ * @hide
*/
+ @SystemApi
public static final String EXTRA_AVAILABLE_TETHER = "availableArray";
/**
* gives a String[] listing all the interfaces currently in local-only
* mode (ie, has DHCPv4+IPv6-ULA support and no packet forwarding)
+ * @hide
*/
+ @SystemApi
public static final String EXTRA_ACTIVE_LOCAL_ONLY = "android.net.extra.ACTIVE_LOCAL_ONLY";
/**
* gives a String[] listing all the interfaces currently tethered
* (ie, has DHCPv4 support and packets potentially forwarded/NATed)
+ * @hide
*/
+ @SystemApi
public static final String EXTRA_ACTIVE_TETHER = "tetherArray";
/**
* gives a String[] listing all the interfaces we tried to tether and
* failed. Use {@link #getLastTetherError} to find the error code
* for any interfaces listed here.
+ * @hide
*/
+ @SystemApi
public static final String EXTRA_ERRORED_TETHER = "erroredArray";
/** @hide */
@@ -136,6 +145,7 @@
TETHERING_WIFI_P2P,
TETHERING_NCM,
TETHERING_ETHERNET,
+ TETHERING_VIRTUAL,
})
public @interface TetheringType {
}
@@ -143,44 +153,57 @@
/**
* Invalid tethering type.
* @see #startTethering.
+ * @hide
*/
+ @SystemApi
public static final int TETHERING_INVALID = -1;
/**
* Wifi tethering type.
* @see #startTethering.
*/
+ @SuppressLint("UnflaggedApi")
public static final int TETHERING_WIFI = 0;
/**
* USB tethering type.
* @see #startTethering.
+ * @hide
*/
+ @SystemApi
public static final int TETHERING_USB = 1;
/**
* Bluetooth tethering type.
* @see #startTethering.
+ * @hide
*/
+ @SystemApi
public static final int TETHERING_BLUETOOTH = 2;
/**
* Wifi P2p tethering type.
* Wifi P2p tethering is set through events automatically, and don't
* need to start from #startTethering.
+ * @hide
*/
+ @SystemApi
public static final int TETHERING_WIFI_P2P = 3;
/**
* Ncm local tethering type.
* @see #startTethering(TetheringRequest, Executor, StartTetheringCallback)
+ * @hide
*/
+ @SystemApi
public static final int TETHERING_NCM = 4;
/**
* Ethernet tethering type.
* @see #startTethering(TetheringRequest, Executor, StartTetheringCallback)
+ * @hide
*/
+ @SystemApi
public static final int TETHERING_ETHERNET = 5;
/**
@@ -253,30 +276,62 @@
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {
TETHER_ERROR_SERVICE_UNAVAIL,
+ TETHER_ERROR_UNSUPPORTED,
TETHER_ERROR_INTERNAL_ERROR,
TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION,
TETHER_ERROR_UNKNOWN_TYPE,
+ TETHER_ERROR_DUPLICATE_REQUEST,
})
public @interface StartTetheringError {
}
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {
+ TETHER_ERROR_NO_ERROR,
+ TETHER_ERROR_UNKNOWN_REQUEST,
+ })
+ public @interface StopTetheringError {
+ }
+
+ @SuppressLint("UnflaggedApi")
public static final int TETHER_ERROR_NO_ERROR = 0;
+ @SuppressLint("UnflaggedApi")
public static final int TETHER_ERROR_UNKNOWN_IFACE = 1;
+ @SuppressLint("UnflaggedApi")
public static final int TETHER_ERROR_SERVICE_UNAVAIL = 2;
+ @SuppressLint("UnflaggedApi")
public static final int TETHER_ERROR_UNSUPPORTED = 3;
+ @SuppressLint("UnflaggedApi")
public static final int TETHER_ERROR_UNAVAIL_IFACE = 4;
+ @SuppressLint("UnflaggedApi")
public static final int TETHER_ERROR_INTERNAL_ERROR = 5;
+ @SuppressLint("UnflaggedApi")
public static final int TETHER_ERROR_TETHER_IFACE_ERROR = 6;
+ @SuppressLint("UnflaggedApi")
public static final int TETHER_ERROR_UNTETHER_IFACE_ERROR = 7;
+ @SuppressLint("UnflaggedApi")
public static final int TETHER_ERROR_ENABLE_FORWARDING_ERROR = 8;
+ @SuppressLint("UnflaggedApi")
public static final int TETHER_ERROR_DISABLE_FORWARDING_ERROR = 9;
+ @SuppressLint("UnflaggedApi")
public static final int TETHER_ERROR_IFACE_CFG_ERROR = 10;
+ @SuppressLint("UnflaggedApi")
public static final int TETHER_ERROR_PROVISIONING_FAILED = 11;
+ @SuppressLint("UnflaggedApi")
public static final int TETHER_ERROR_DHCPSERVER_ERROR = 12;
+ @SuppressLint("UnflaggedApi")
public static final int TETHER_ERROR_ENTITLEMENT_UNKNOWN = 13;
+ @SuppressLint("UnflaggedApi")
public static final int TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION = 14;
+ @SuppressLint("UnflaggedApi")
public static final int TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION = 15;
+ @SuppressLint("UnflaggedApi")
public static final int TETHER_ERROR_UNKNOWN_TYPE = 16;
+ @FlaggedApi(Flags.FLAG_TETHERING_WITH_SOFT_AP_CONFIG)
+ public static final int TETHER_ERROR_UNKNOWN_REQUEST = 17;
+ @FlaggedApi(Flags.FLAG_TETHERING_WITH_SOFT_AP_CONFIG)
+ public static final int TETHER_ERROR_DUPLICATE_REQUEST = 18;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -288,11 +343,23 @@
public @interface TetherOffloadStatus {
}
- /** Tethering offload status is stopped. */
+ /**
+ * Tethering offload status is stopped.
+ * @hide
+ */
+ @SystemApi
public static final int TETHER_HARDWARE_OFFLOAD_STOPPED = 0;
- /** Tethering offload status is started. */
+ /**
+ * Tethering offload status is started.
+ * @hide
+ */
+ @SystemApi
public static final int TETHER_HARDWARE_OFFLOAD_STARTED = 1;
- /** Fail to start tethering offload. */
+ /**
+ * Fail to start tethering offload.
+ * @hide
+ */
+ @SystemApi
public static final int TETHER_HARDWARE_OFFLOAD_FAILED = 2;
/**
@@ -686,11 +753,14 @@
* Indicates that this tethering connection will provide connectivity beyond this device (e.g.,
* global Internet access).
*/
+ @SuppressLint("UnflaggedApi")
public static final int CONNECTIVITY_SCOPE_GLOBAL = 1;
/**
* Indicates that this tethering connection will only provide local connectivity.
+ * @hide
*/
+ @SystemApi
public static final int CONNECTIVITY_SCOPE_LOCAL = 2;
/**
@@ -718,6 +788,7 @@
/**
* Use with {@link #startTethering} to specify additional parameters when starting tethering.
*/
+ @SuppressLint("UnflaggedApi")
public static final class TetheringRequest implements Parcelable {
/** A configuration set for TetheringRequest. */
private final TetheringRequestParcel mRequestParcel;
@@ -761,10 +832,12 @@
}
/** Builder used to create TetheringRequest. */
+ @SuppressLint({"UnflaggedApi", "StaticFinalBuilder"})
public static class Builder {
private final TetheringRequestParcel mBuilderParcel;
/** Default constructor of Builder. */
+ @SuppressLint("UnflaggedApi")
public Builder(@TetheringType final int type) {
mBuilderParcel = new TetheringRequestParcel();
mBuilderParcel.tetheringType = type;
@@ -775,6 +848,7 @@
mBuilderParcel.connectivityScope = getDefaultConnectivityScope(type);
mBuilderParcel.uid = Process.INVALID_UID;
mBuilderParcel.softApConfig = null;
+ mBuilderParcel.interfaceName = null;
}
/**
@@ -785,7 +859,9 @@
*
* @param localIPv4Address The preferred local IPv4 link address to use.
* @param clientAddress The static client address.
+ * @hide
*/
+ @SystemApi
@RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
@NonNull
public Builder setStaticIpv4Addresses(@NonNull final LinkAddress localIPv4Address,
@@ -801,7 +877,11 @@
return this;
}
- /** Start tethering without entitlement checks. */
+ /**
+ * Start tethering without entitlement checks.
+ * @hide
+ */
+ @SystemApi
@RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
@NonNull
public Builder setExemptFromEntitlementCheck(boolean exempt) {
@@ -812,7 +892,9 @@
/**
* If an entitlement check is needed, sets whether to show the entitlement UI or to
* perform a silent entitlement check. By default, the entitlement UI is shown.
+ * @hide
*/
+ @SystemApi
@RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
@NonNull
public Builder setShouldShowEntitlementUi(boolean showUi) {
@@ -821,8 +903,39 @@
}
/**
- * Sets the connectivity scope to be provided by this tethering downstream.
+ * Sets the name of the interface. Currently supported only for
+ * - {@link #TETHERING_VIRTUAL}.
+ * - {@link #TETHERING_WIFI} (for Local-only Hotspot)
+ * - {@link #TETHERING_WIFI_P2P}
+ * @hide
*/
+ @FlaggedApi(Flags.FLAG_TETHERING_WITH_SOFT_AP_CONFIG)
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_STACK,
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ })
+ @NonNull
+ @SystemApi(client = MODULE_LIBRARIES)
+ public Builder setInterfaceName(@Nullable final String interfaceName) {
+ switch (mBuilderParcel.tetheringType) {
+ case TETHERING_VIRTUAL:
+ case TETHERING_WIFI_P2P:
+ case TETHERING_WIFI:
+ break;
+ default:
+ throw new IllegalArgumentException("Interface name cannot be set for"
+ + " tethering type " + interfaceName);
+ }
+ mBuilderParcel.interfaceName = interfaceName;
+ return this;
+ }
+
+ /**
+ * Sets the connectivity scope to be provided by this tethering downstream.
+ * @hide
+ */
+ @SystemApi
@RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
@NonNull
public Builder setConnectivityScope(@ConnectivityScope int scope) {
@@ -842,6 +955,8 @@
* If TETHERING_WIFI is already enabled and a new request is made with a different
* SoftApConfiguration, the request will be accepted if the device can support an
* additional tethering Wi-Fi AP interface. Otherwise, the request will be rejected.
+ * </p>
+ * Non-system callers using TETHERING_WIFI must specify a SoftApConfiguration.
*
* @param softApConfig SoftApConfiguration to use.
* @throws IllegalArgumentException if the tethering type isn't TETHERING_WIFI.
@@ -860,6 +975,7 @@
/** Build {@link TetheringRequest} with the currently set configuration. */
@NonNull
+ @SuppressLint("UnflaggedApi")
public TetheringRequest build() {
return new TetheringRequest(mBuilderParcel);
}
@@ -868,7 +984,9 @@
/**
* Get the local IPv4 address, if one was configured with
* {@link Builder#setStaticIpv4Addresses}.
+ * @hide
*/
+ @SystemApi
@Nullable
public LinkAddress getLocalIpv4Address() {
return mRequestParcel.localIPv4Address;
@@ -877,35 +995,64 @@
/**
* Get the static IPv4 address of the client, if one was configured with
* {@link Builder#setStaticIpv4Addresses}.
+ * @hide
*/
+ @SystemApi
@Nullable
public LinkAddress getClientStaticIpv4Address() {
return mRequestParcel.staticClientAddress;
}
- /** Get tethering type. */
+ /**
+ * Get tethering type.
+ * @hide
+ */
+ @SystemApi
@TetheringType
public int getTetheringType() {
return mRequestParcel.tetheringType;
}
- /** Get connectivity type */
+ /**
+ * Get connectivity type
+ * @hide
+ */
+ @SystemApi
@ConnectivityScope
public int getConnectivityScope() {
return mRequestParcel.connectivityScope;
}
- /** Check if exempt from entitlement check. */
+ /**
+ * Check if exempt from entitlement check.
+ * @hide
+ */
+ @SystemApi
public boolean isExemptFromEntitlementCheck() {
return mRequestParcel.exemptFromEntitlementCheck;
}
- /** Check if show entitlement ui. */
+ /**
+ * Check if show entitlement ui.
+ * @hide
+ */
+ @SystemApi
public boolean getShouldShowEntitlementUi() {
return mRequestParcel.showProvisioningUi;
}
/**
+ * Get interface name.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_TETHERING_WITH_SOFT_AP_CONFIG)
+ @Nullable
+ @SystemApi(client = MODULE_LIBRARIES)
+ public String getInterfaceName() {
+ return mRequestParcel.interfaceName;
+ }
+
+ /**
* Check whether the two addresses are ipv4 and in the same prefix.
* @hide
*/
@@ -996,7 +1143,11 @@
return mRequestParcel;
}
- /** String of TetheringRequest detail. */
+ /**
+ * String of TetheringRequest detail.
+ * @hide
+ */
+ @SystemApi
public String toString() {
StringJoiner sj = new StringJoiner(", ", "TetheringRequest[ ", " ]");
sj.add(typeToString(mRequestParcel.tetheringType));
@@ -1022,9 +1173,16 @@
if (mRequestParcel.packageName != null) {
sj.add("packageName=" + mRequestParcel.packageName);
}
+ if (mRequestParcel.interfaceName != null) {
+ sj.add("interfaceName=" + mRequestParcel.interfaceName);
+ }
return sj.toString();
}
+ /**
+ * @hide
+ */
+ @SystemApi
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
@@ -1039,26 +1197,33 @@
&& parcel.connectivityScope == otherParcel.connectivityScope
&& Objects.equals(parcel.softApConfig, otherParcel.softApConfig)
&& parcel.uid == otherParcel.uid
- && Objects.equals(parcel.packageName, otherParcel.packageName);
+ && Objects.equals(parcel.packageName, otherParcel.packageName)
+ && Objects.equals(parcel.interfaceName, otherParcel.interfaceName);
}
+ /**
+ * @hide
+ */
+ @SystemApi
@Override
public int hashCode() {
TetheringRequestParcel parcel = getParcel();
return Objects.hash(parcel.tetheringType, parcel.localIPv4Address,
parcel.staticClientAddress, parcel.exemptFromEntitlementCheck,
parcel.showProvisioningUi, parcel.connectivityScope, parcel.softApConfig,
- parcel.uid, parcel.packageName);
+ parcel.uid, parcel.packageName, parcel.interfaceName);
}
}
/**
* Callback for use with {@link #startTethering} to find out whether tethering succeeded.
*/
+ @SuppressLint("UnflaggedApi")
public interface StartTetheringCallback {
/**
* Called when tethering has been successfully started.
*/
+ @SuppressLint("UnflaggedApi")
default void onTetheringStarted() {}
/**
@@ -1066,26 +1231,40 @@
*
* @param error The error that caused the failure.
*/
+ @SuppressLint("UnflaggedApi")
default void onTetheringFailed(@StartTetheringError final int error) {}
}
/**
+ * Callback for use with {@link #stopTethering} to find out whether stop tethering succeeded.
+ */
+ @FlaggedApi(Flags.FLAG_TETHERING_WITH_SOFT_AP_CONFIG)
+ public interface StopTetheringCallback {
+ /**
+ * Called when tethering has been successfully stopped.
+ */
+ default void onStopTetheringSucceeded() {}
+
+ /**
+ * Called when starting tethering failed.
+ *
+ * @param error The error that caused the failure.
+ */
+ default void onStopTetheringFailed(@StopTetheringError final int error) {}
+ }
+
+ /**
* Starts tethering and runs tether provisioning for the given type if needed. If provisioning
* fails, stopTethering will be called automatically.
*
- * <p>Without {@link android.Manifest.permission.TETHER_PRIVILEGED} permission, the call will
- * fail if a tethering entitlement check is required.
- *
* @param request a {@link TetheringRequest} which can specify the preferred configuration.
* @param executor {@link Executor} to specify the thread upon which the callback of
* TetheringRequest will be invoked.
* @param callback A callback that will be called to indicate the success status of the
* tethering start request.
*/
- @RequiresPermission(anyOf = {
- android.Manifest.permission.TETHER_PRIVILEGED,
- android.Manifest.permission.WRITE_SETTINGS
- })
+ @RequiresPermission(value = android.Manifest.permission.TETHER_PRIVILEGED, conditional = true)
+ @SuppressLint("UnflaggedApi")
public void startTethering(@NonNull final TetheringRequest request,
@NonNull final Executor executor, @NonNull final StartTetheringCallback callback) {
final String callerPkg = mContext.getOpPackageName();
@@ -1135,11 +1314,13 @@
*
* <p>Without {@link android.Manifest.permission.TETHER_PRIVILEGED} permission, the call will
* fail if a tethering entitlement check is required.
+ * @hide
*/
@RequiresPermission(anyOf = {
android.Manifest.permission.TETHER_PRIVILEGED,
android.Manifest.permission.WRITE_SETTINGS
})
+ @SystemApi
public void stopTethering(@TetheringType final int type) {
final String callerPkg = mContext.getOpPackageName();
Log.i(TAG, "stopTethering caller:" + callerPkg);
@@ -1157,9 +1338,22 @@
}
/**
+ * Stops tethering for the given request. Operation will fail with
+ * {@link #TETHER_ERROR_UNKNOWN_REQUEST} if there is no request that matches it.
+ */
+ @RequiresPermission(value = android.Manifest.permission.TETHER_PRIVILEGED, conditional = true)
+ @FlaggedApi(Flags.FLAG_TETHERING_WITH_SOFT_AP_CONFIG)
+ public void stopTethering(@NonNull TetheringRequest request,
+ @NonNull final Executor executor, @NonNull final StopTetheringCallback callback) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
* Callback for use with {@link #getLatestTetheringEntitlementResult} to find out whether
* entitlement succeeded.
+ * @hide
*/
+ @SystemApi
public interface OnTetheringEntitlementResultListener {
/**
* Called to notify entitlement result.
@@ -1190,7 +1384,9 @@
* @param listener an {@link OnTetheringEntitlementResultListener} which will be called to
* notify the caller of the result of entitlement check. The listener may be called zero
* or one time.
+ * @hide
*/
+ @SystemApi
@RequiresPermission(anyOf = {
android.Manifest.permission.TETHER_PRIVILEGED,
android.Manifest.permission.WRITE_SETTINGS
@@ -1238,6 +1434,7 @@
* Callback for use with {@link registerTetheringEventCallback} to find out tethering
* upstream status.
*/
+ @SuppressLint("UnflaggedApi")
public interface TetheringEventCallback {
/**
* Called when tethering supported status changed.
@@ -1249,7 +1446,9 @@
* policy restrictions.
*
* @param supported whether any tethering type is supported.
+ * @hide
*/
+ @SystemApi
default void onTetheringSupported(boolean supported) {}
/**
@@ -1274,7 +1473,9 @@
*
* @param network the {@link Network} of tethering upstream. Null means tethering doesn't
* have any upstream.
+ * @hide
*/
+ @SystemApi
default void onUpstreamChanged(@Nullable Network network) {}
/**
@@ -1301,7 +1502,9 @@
* <p>This will be called immediately after the callback is registered, and may be called
* multiple times later upon changes.
* @param interfaces The list of tetherable interface names.
+ * @hide
*/
+ @SystemApi
default void onTetherableInterfacesChanged(@NonNull List<String> interfaces) {}
/**
@@ -1311,7 +1514,9 @@
* <p>This will be called immediately after the callback is registered, and may be called
* multiple times later upon changes.
* @param interfaces The set of TetheringInterface of currently tetherable interface.
+ * @hide
*/
+ @SystemApi
default void onTetherableInterfacesChanged(@NonNull Set<TetheringInterface> interfaces) {
// By default, the new callback calls the old callback, so apps
// implementing the old callback just work.
@@ -1324,7 +1529,9 @@
* <p>This will be called immediately after the callback is registered, and may be called
* multiple times later upon changes.
* @param interfaces The lit of 0 or more String of currently tethered interface names.
+ * @hide
*/
+ @SystemApi
default void onTetheredInterfacesChanged(@NonNull List<String> interfaces) {}
/**
@@ -1335,6 +1542,7 @@
* @param interfaces The set of 0 or more TetheringInterface of currently tethered
* interface.
*/
+ @SuppressLint("UnflaggedApi")
default void onTetheredInterfacesChanged(@NonNull Set<TetheringInterface> interfaces) {
// By default, the new callback calls the old callback, so apps
// implementing the old callback just work.
@@ -1347,7 +1555,9 @@
* <p>This will be called immediately after the callback is registered, and may be called
* multiple times later upon changes.
* @param interfaces The list of 0 or more String of active local-only interface names.
+ * @hide
*/
+ @SystemApi
default void onLocalOnlyInterfacesChanged(@NonNull List<String> interfaces) {}
/**
@@ -1357,7 +1567,9 @@
* multiple times later upon changes.
* @param interfaces The set of 0 or more TetheringInterface of active local-only
* interface.
+ * @hide
*/
+ @SystemApi
default void onLocalOnlyInterfacesChanged(@NonNull Set<TetheringInterface> interfaces) {
// By default, the new callback calls the old callback, so apps
// implementing the old callback just work.
@@ -1371,7 +1583,9 @@
* on the interface is an error, and may be called multiple times later upon changes.
* @param ifName Name of the interface.
* @param error One of {@code TetheringManager#TETHER_ERROR_*}.
+ * @hide
*/
+ @SystemApi
default void onError(@NonNull String ifName, @TetheringIfaceError int error) {}
/**
@@ -1381,7 +1595,9 @@
* on the interface is an error, and may be called multiple times later upon changes.
* @param iface The interface that experienced the error.
* @param error One of {@code TetheringManager#TETHER_ERROR_*}.
+ * @hide
*/
+ @SystemApi
default void onError(@NonNull TetheringInterface iface, @TetheringIfaceError int error) {
// By default, the new callback calls the old callback, so apps
// implementing the old callback just work.
@@ -1398,7 +1614,9 @@
* clients may still be reported by this callback after disconnection as the system cannot
* determine if they are still connected.
* @param clients The new set of tethered clients; the collection is not ordered.
+ * @hide
*/
+ @SystemApi
default void onClientsChanged(@NonNull Collection<TetheredClient> clients) {}
/**
@@ -1406,7 +1624,9 @@
*
* <p>This will be called immediately after the callback is registered.
* @param status The offload status.
+ * @hide
*/
+ @SystemApi
default void onOffloadStatusChanged(@TetherOffloadStatus int status) {}
}
@@ -1500,6 +1720,7 @@
* @param callback the callback to be called when tethering has change events.
*/
@RequiresPermission(Manifest.permission.ACCESS_NETWORK_STATE)
+ @SuppressLint("UnflaggedApi")
public void registerTetheringEventCallback(@NonNull Executor executor,
@NonNull TetheringEventCallback callback) {
Objects.requireNonNull(executor);
@@ -1658,6 +1879,7 @@
Manifest.permission.TETHER_PRIVILEGED,
Manifest.permission.ACCESS_NETWORK_STATE
})
+ @SuppressLint("UnflaggedApi")
public void unregisterTetheringEventCallback(@NonNull final TetheringEventCallback callback) {
Objects.requireNonNull(callback);
@@ -1848,10 +2070,9 @@
/**
* Stop all active tethering.
- *
- * <p>Without {@link android.Manifest.permission.TETHER_PRIVILEGED} permission, the call will
- * fail if a tethering entitlement check is required.
+ * @hide
*/
+ @SystemApi
@RequiresPermission(anyOf = {
android.Manifest.permission.TETHER_PRIVILEGED,
android.Manifest.permission.WRITE_SETTINGS
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringRequestParcel.aidl b/Tethering/common/TetheringLib/src/android/net/TetheringRequestParcel.aidl
index 789d5bb..97c9b9a 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringRequestParcel.aidl
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringRequestParcel.aidl
@@ -33,4 +33,5 @@
SoftApConfiguration softApConfig;
int uid;
String packageName;
+ String interfaceName;
}
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index ebc9e4e..d6f4572 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -19,6 +19,12 @@
import static android.net.RouteInfo.RTN_UNICAST;
import static android.net.TetheringManager.CONNECTIVITY_SCOPE_GLOBAL;
import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
+import static android.net.TetheringManager.TETHERING_BLUETOOTH;
+import static android.net.TetheringManager.TETHERING_ETHERNET;
+import static android.net.TetheringManager.TETHERING_NCM;
+import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.net.TetheringManager.TETHERING_WIFI_P2P;
+import static android.net.TetheringManager.TETHERING_WIGIG;
import static android.net.TetheringManager.TETHER_ERROR_DHCPSERVER_ERROR;
import static android.net.TetheringManager.TETHER_ERROR_ENABLE_FORWARDING_ERROR;
import static android.net.TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR;
@@ -45,7 +51,6 @@
import android.net.MacAddress;
import android.net.RouteInfo;
import android.net.TetheredClient;
-import android.net.TetheringManager;
import android.net.TetheringManager.TetheringRequest;
import android.net.dhcp.DhcpLeaseParcelable;
import android.net.dhcp.DhcpServerCallbacks;
@@ -589,8 +594,8 @@
@NonNull final Inet4Address dnsServer, @NonNull LinkAddress serverAddr,
@Nullable Inet4Address clientAddr) {
final boolean changePrefixOnDecline =
- (mInterfaceType == TetheringManager.TETHERING_NCM && clientAddr == null);
- final int subnetPrefixLength = mInterfaceType == TetheringManager.TETHERING_WIFI_P2P
+ (mInterfaceType == TETHERING_NCM && clientAddr == null);
+ final int subnetPrefixLength = mInterfaceType == TETHERING_WIFI_P2P
? mP2pLeasesSubnetPrefixLength : 0 /* default value */;
return new DhcpServingParamsParcelExt()
@@ -690,10 +695,10 @@
final IpPrefix ipv4Prefix = asIpPrefix(mIpv4Address);
final Boolean setIfaceUp;
- if (mInterfaceType == TetheringManager.TETHERING_WIFI
- || mInterfaceType == TetheringManager.TETHERING_WIFI_P2P
- || mInterfaceType == TetheringManager.TETHERING_ETHERNET
- || mInterfaceType == TetheringManager.TETHERING_WIGIG) {
+ if (mInterfaceType == TETHERING_WIFI
+ || mInterfaceType == TETHERING_WIFI_P2P
+ || mInterfaceType == TETHERING_ETHERNET
+ || mInterfaceType == TETHERING_WIGIG) {
// The WiFi and Ethernet stack has ownership of the interface up/down state.
// It is unclear whether the Bluetooth or USB stacks will manage their own
// state.
@@ -719,12 +724,12 @@
private boolean shouldNotConfigureBluetoothInterface() {
// Before T, bluetooth tethering configures the interface elsewhere.
- return (mInterfaceType == TetheringManager.TETHERING_BLUETOOTH) && !SdkLevel.isAtLeastT();
+ return (mInterfaceType == TETHERING_BLUETOOTH) && !SdkLevel.isAtLeastT();
}
private boolean shouldUseWifiP2pDedicatedIp() {
return mIsWifiP2pDedicatedIpEnabled
- && mInterfaceType == TetheringManager.TETHERING_WIFI_P2P;
+ && mInterfaceType == TETHERING_WIFI_P2P;
}
private LinkAddress requestIpv4Address(final int scope, final boolean useLastAddress) {
@@ -844,12 +849,13 @@
}
}
- private void removeRoutesFromLocalNetwork(@NonNull final List<RouteInfo> toBeRemoved) {
- final int removalFailures = NetdUtils.removeRoutesFromLocalNetwork(
- mNetd, toBeRemoved);
+ private void removeRoutesFromNetworkAndLinkProperties(int netId,
+ @NonNull final List<RouteInfo> toBeRemoved) {
+ final int removalFailures = NetdUtils.removeRoutesFromNetwork(
+ mNetd, netId, toBeRemoved);
if (removalFailures > 0) {
- mLog.e(String.format("Failed to remove %d IPv6 routes from local table.",
- removalFailures));
+ mLog.e("Failed to remove " + removalFailures
+ + " IPv6 routes from network " + netId + ".");
}
for (RouteInfo route : toBeRemoved) mLinkProperties.removeRoute(route);
@@ -879,14 +885,15 @@
}
}
- private void addRoutesToLocalNetwork(@NonNull final List<RouteInfo> toBeAdded) {
+ private void addRoutesToNetworkAndLinkProperties(int netId,
+ @NonNull final List<RouteInfo> toBeAdded) {
// It's safe to call addInterfaceToNetwork() even if
- // the interface is already in the local_network.
- addInterfaceToNetwork(INetd.LOCAL_NET_ID, mIfaceName);
+ // the interface is already in the network.
+ addInterfaceToNetwork(netId, mIfaceName);
try {
// Add routes from local network. Note that adding routes that
// already exist does not cause an error (EEXIST is silently ignored).
- NetdUtils.addRoutesToLocalNetwork(mNetd, mIfaceName, toBeAdded);
+ NetdUtils.addRoutesToNetwork(mNetd, netId, mIfaceName, toBeAdded);
} catch (IllegalStateException e) {
mLog.e("Failed to add IPv4/v6 routes to local table: " + e);
return;
@@ -899,7 +906,8 @@
ArraySet<IpPrefix> deprecatedPrefixes, ArraySet<IpPrefix> newPrefixes) {
// [1] Remove the routes that are deprecated.
if (!deprecatedPrefixes.isEmpty()) {
- removeRoutesFromLocalNetwork(getLocalRoutesFor(mIfaceName, deprecatedPrefixes));
+ removeRoutesFromNetworkAndLinkProperties(INetd.LOCAL_NET_ID,
+ getLocalRoutesFor(mIfaceName, deprecatedPrefixes));
}
// [2] Add only the routes that have not previously been added.
@@ -910,7 +918,8 @@
}
if (!addedPrefixes.isEmpty()) {
- addRoutesToLocalNetwork(getLocalRoutesFor(mIfaceName, addedPrefixes));
+ addRoutesToNetworkAndLinkProperties(INetd.LOCAL_NET_ID,
+ getLocalRoutesFor(mIfaceName, addedPrefixes));
}
}
}
@@ -1114,7 +1123,8 @@
}
try {
- NetdUtils.tetherInterface(mNetd, mIfaceName, asIpPrefix(mIpv4Address));
+ NetdUtils.tetherInterface(mNetd, INetd.LOCAL_NET_ID, mIfaceName,
+ asIpPrefix(mIpv4Address));
} catch (RemoteException | ServiceSpecificException | IllegalStateException e) {
mLog.e("Error Tethering", e);
mLastError = TETHER_ERROR_TETHER_IFACE_ERROR;
@@ -1213,14 +1223,14 @@
return;
}
- // Remove deprecated routes from local network.
- removeRoutesFromLocalNetwork(
- Collections.singletonList(getDirectConnectedRoute(deprecatedLinkAddress)));
+ // Remove deprecated routes from downstream network.
+ removeRoutesFromNetworkAndLinkProperties(INetd.LOCAL_NET_ID,
+ List.of(getDirectConnectedRoute(deprecatedLinkAddress)));
mLinkProperties.removeLinkAddress(deprecatedLinkAddress);
- // Add new routes to local network.
- addRoutesToLocalNetwork(
- Collections.singletonList(getDirectConnectedRoute(mIpv4Address)));
+ // Add new routes to downstream network.
+ addRoutesToNetworkAndLinkProperties(INetd.LOCAL_NET_ID,
+ List.of(getDirectConnectedRoute(mIpv4Address)));
mLinkProperties.addLinkAddress(mIpv4Address);
// Update local DNS caching server with new IPv4 address, otherwise, dnsmasq doesn't
diff --git a/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java b/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
index fb16226..a942166 100644
--- a/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
+++ b/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
@@ -154,7 +154,9 @@
// Only launch entitlement UI for the current user if it is allowed to
// change tethering. This usually means the system user or the admin users in HSUM.
- if (SdkLevel.isAtLeastT()) {
+ // TODO (b/382624069): Figure out whether it is safe to call createContextAsUser
+ // from secondary user. And re-enable the check or remove the code accordingly.
+ if (false) {
// Create a user context for the current foreground user as UserManager#isAdmin()
// operates on the context user.
final int currentUserId = getCurrentUser();
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index f33ef37..254b60f 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -665,7 +665,8 @@
// If tethering is already enabled with a different request,
// disable before re-enabling.
if (unfinishedRequest != null && !unfinishedRequest.equals(request)) {
- enableTetheringInternal(type, false /* disabled */, null);
+ enableTetheringInternal(type, false /* disabled */,
+ unfinishedRequest.getInterfaceName(), null);
mEntitlementMgr.stopProvisioningIfNeeded(type);
}
mActiveTetheringRequests.put(type, request);
@@ -676,7 +677,7 @@
mEntitlementMgr.startProvisioningIfNeeded(type,
request.getShouldShowEntitlementUi());
}
- enableTetheringInternal(type, true /* enabled */, listener);
+ enableTetheringInternal(type, true /* enabled */, request.getInterfaceName(), listener);
mTetheringMetrics.createBuilder(type, callerPkg);
});
}
@@ -689,7 +690,7 @@
void stopTetheringInternal(int type) {
mActiveTetheringRequests.remove(type);
- enableTetheringInternal(type, false /* disabled */, null);
+ enableTetheringInternal(type, false /* disabled */, null, null);
mEntitlementMgr.stopProvisioningIfNeeded(type);
}
@@ -698,7 +699,7 @@
* schedule provisioning rechecks for the specified interface.
*/
private void enableTetheringInternal(int type, boolean enable,
- final IIntResultListener listener) {
+ String iface, final IIntResultListener listener) {
int result = TETHER_ERROR_NO_ERROR;
switch (type) {
case TETHERING_WIFI:
@@ -717,7 +718,7 @@
result = setEthernetTethering(enable);
break;
case TETHERING_VIRTUAL:
- result = setVirtualMachineTethering(enable);
+ result = setVirtualMachineTethering(enable, iface);
break;
default:
Log.w(TAG, "Invalid tether type.");
@@ -972,10 +973,13 @@
}
}
- private int setVirtualMachineTethering(final boolean enable) {
- // TODO(340377643): Use bridge ifname when it's introduced, not fixed TAP ifname.
+ private int setVirtualMachineTethering(final boolean enable, String iface) {
if (enable) {
- mConfiguredVirtualIface = "avf_tap_fixed";
+ if (TextUtils.isEmpty(iface)) {
+ mConfiguredVirtualIface = "avf_tap_fixed";
+ } else {
+ mConfiguredVirtualIface = iface;
+ }
enableIpServing(
TETHERING_VIRTUAL,
mConfiguredVirtualIface,
@@ -2205,7 +2209,7 @@
case EVENT_REQUEST_CHANGE_DOWNSTREAM: {
final boolean enabled = message.arg1 == 1;
final TetheringRequest request = (TetheringRequest) message.obj;
- enableTetheringInternal(request.getTetheringType(), enabled, null);
+ enableTetheringInternal(request.getTetheringType(), enabled, null, null);
break;
}
default:
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringService.java b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
index 3cb5f99..6485ffd 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringService.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
@@ -133,9 +133,11 @@
@Override
public void startTethering(TetheringRequestParcel request, String callerPkg,
String callingAttributionTag, IIntResultListener listener) {
+ boolean onlyAllowPrivileged = request.exemptFromEntitlementCheck
+ || request.interfaceName != null;
if (checkAndNotifyCommonError(callerPkg,
callingAttributionTag,
- request.exemptFromEntitlementCheck /* onlyAllowPrivileged */,
+ onlyAllowPrivileged,
listener)) {
return;
}
@@ -284,6 +286,10 @@
return false;
}
+ private boolean hasNetworkSettingsPermission() {
+ return checkCallingOrSelfPermission(NETWORK_SETTINGS);
+ }
+
private boolean hasNetworkStackPermission() {
return checkCallingOrSelfPermission(NETWORK_STACK)
|| checkCallingOrSelfPermission(PERMISSION_MAINLINE_NETWORK_STACK);
@@ -299,7 +305,8 @@
private boolean hasTetherChangePermission(final String callerPkg,
final String callingAttributionTag, final boolean onlyAllowPrivileged) {
- if (onlyAllowPrivileged && !hasNetworkStackPermission()) return false;
+ if (onlyAllowPrivileged && !hasNetworkStackPermission()
+ && !hasNetworkSettingsPermission()) return false;
if (hasTetherPrivilegedPermission()) return true;
diff --git a/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java b/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
index 01f3af9..1323f28 100644
--- a/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
+++ b/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
@@ -332,15 +332,16 @@
// seconds. See b/289881008.
private static final int EXPANDED_TIMEOUT_MS = 30000;
- MyTetheringEventCallback(String iface) {
- mIface = new TetheringInterface(TETHERING_ETHERNET, iface);
+ MyTetheringEventCallback(int tetheringType, String iface) {
+ mIface = new TetheringInterface(tetheringType, iface);
mExpectedUpstream = null;
mAcceptAnyUpstream = true;
}
- MyTetheringEventCallback(String iface, @NonNull Network expectedUpstream) {
+ MyTetheringEventCallback(
+ int tetheringType, String iface, @NonNull Network expectedUpstream) {
Objects.requireNonNull(expectedUpstream);
- mIface = new TetheringInterface(TETHERING_ETHERNET, iface);
+ mIface = new TetheringInterface(tetheringType, iface);
mExpectedUpstream = expectedUpstream;
mAcceptAnyUpstream = false;
}
@@ -392,12 +393,12 @@
}
public void awaitInterfaceTethered() throws Exception {
- assertTrue("Ethernet not tethered after " + EXPANDED_TIMEOUT_MS + "ms",
+ assertTrue("Interface is not tethered after " + EXPANDED_TIMEOUT_MS + "ms",
mTetheringStartedLatch.await(EXPANDED_TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
public void awaitInterfaceLocalOnly() throws Exception {
- assertTrue("Ethernet not local-only after " + EXPANDED_TIMEOUT_MS + "ms",
+ assertTrue("Interface is not local-only after " + EXPANDED_TIMEOUT_MS + "ms",
mLocalOnlyStartedLatch.await(EXPANDED_TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
@@ -501,15 +502,17 @@
sCallbackErrors.add(error);
}
- protected static MyTetheringEventCallback enableEthernetTethering(String iface,
+ protected static MyTetheringEventCallback enableTethering(String iface,
TetheringRequest request, Network expectedUpstream) throws Exception {
- // Enable ethernet tethering with null expectedUpstream means the test accept any upstream
- // after etherent tethering started.
+ // Enable tethering with null expectedUpstream means the test accept any upstream after
+ // tethering started.
final MyTetheringEventCallback callback;
if (expectedUpstream != null) {
- callback = new MyTetheringEventCallback(iface, expectedUpstream);
+ callback =
+ new MyTetheringEventCallback(
+ request.getTetheringType(), iface, expectedUpstream);
} else {
- callback = new MyTetheringEventCallback(iface);
+ callback = new MyTetheringEventCallback(request.getTetheringType(), iface);
}
runAsShell(NETWORK_SETTINGS, () -> {
sTm.registerTetheringEventCallback(c -> c.run() /* executor */, callback);
@@ -521,7 +524,7 @@
StartTetheringCallback startTetheringCallback = new StartTetheringCallback() {
@Override
public void onTetheringStarted() {
- Log.d(TAG, "Ethernet tethering started");
+ Log.d(TAG, "Tethering started");
tetheringStartedLatch.countDown();
}
@@ -530,8 +533,8 @@
addCallbackError("Unexpectedly got onTetheringFailed");
}
};
- Log.d(TAG, "Starting Ethernet tethering");
- runAsShell(TETHER_PRIVILEGED, () -> {
+ Log.d(TAG, "Starting tethering");
+ runAsShell(TETHER_PRIVILEGED, NETWORK_SETTINGS, () -> {
sTm.startTethering(request, c -> c.run() /* executor */, startTetheringCallback);
// Binder call is an async call. Need to hold the shell permission until tethering
// started. This helps to avoid the test become flaky.
@@ -557,7 +560,7 @@
protected static MyTetheringEventCallback enableEthernetTethering(String iface,
Network expectedUpstream) throws Exception {
- return enableEthernetTethering(iface,
+ return enableTethering(iface,
new TetheringRequest.Builder(TETHERING_ETHERNET)
.setShouldShowEntitlementUi(false).build(), expectedUpstream);
}
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index 1bbea94..5c8d347 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -19,7 +19,9 @@
import static android.Manifest.permission.DUMP;
import static android.net.InetAddresses.parseNumericAddress;
import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
+import static android.net.TetheringManager.CONNECTIVITY_SCOPE_GLOBAL;
import static android.net.TetheringManager.TETHERING_ETHERNET;
+import static android.net.TetheringManager.TETHERING_VIRTUAL;
import static android.net.TetheringTester.TestDnsPacket;
import static android.net.TetheringTester.buildIcmpEchoPacketV4;
import static android.net.TetheringTester.buildUdpPacket;
@@ -236,7 +238,8 @@
downstreamReader = makePacketReader(fd, mtu);
tetheringEventCallback = enableEthernetTethering(downstreamIface.getInterfaceName(),
null /* any upstream */);
- checkTetheredClientCallbacks(downstreamReader, tetheringEventCallback);
+ checkTetheredClientCallbacks(
+ downstreamReader, TETHERING_ETHERNET, tetheringEventCallback);
} finally {
maybeStopTapPacketReader(downstreamReader);
maybeCloseTestInterface(downstreamIface);
@@ -267,7 +270,8 @@
downstreamReader = makePacketReader(fd, getMTU(downstreamIface));
tetheringEventCallback = enableEthernetTethering(downstreamIface.getInterfaceName(),
null /* any upstream */);
- checkTetheredClientCallbacks(downstreamReader, tetheringEventCallback);
+ checkTetheredClientCallbacks(
+ downstreamReader, TETHERING_ETHERNET, tetheringEventCallback);
} finally {
maybeStopTapPacketReader(downstreamReader);
maybeCloseTestInterface(downstreamIface);
@@ -302,7 +306,7 @@
final String localAddr = "192.0.2.3/28";
final String clientAddr = "192.0.2.2/28";
- tetheringEventCallback = enableEthernetTethering(iface,
+ tetheringEventCallback = enableTethering(iface,
requestWithStaticIpv4(localAddr, clientAddr), null /* any upstream */);
tetheringEventCallback.awaitInterfaceTethered();
@@ -368,8 +372,7 @@
final TetheringRequest request = new TetheringRequest.Builder(TETHERING_ETHERNET)
.setConnectivityScope(CONNECTIVITY_SCOPE_LOCAL).build();
- tetheringEventCallback = enableEthernetTethering(iface, request,
- null /* any upstream */);
+ tetheringEventCallback = enableTethering(iface, request, null /* any upstream */);
tetheringEventCallback.awaitInterfaceLocalOnly();
// makePacketReader only works after tethering is started, because until then the
@@ -424,6 +427,7 @@
}
private void checkTetheredClientCallbacks(final PollPacketReader packetReader,
+ final int tetheringType,
final MyTetheringEventCallback tetheringEventCallback) throws Exception {
// Create a fake client.
byte[] clientMacAddr = new byte[6];
@@ -438,7 +442,7 @@
// Check the MAC address.
assertEquals(MacAddress.fromBytes(clientMacAddr), client.getMacAddress());
- assertEquals(TETHERING_ETHERNET, client.getTetheringType());
+ assertEquals(tetheringType, client.getTetheringType());
// Check the hostname.
assertEquals(1, client.getAddresses().size());
@@ -475,8 +479,7 @@
private void assertInvalidStaticIpv4Request(String iface, String local, String client)
throws Exception {
try {
- enableEthernetTethering(iface, requestWithStaticIpv4(local, client),
- null /* any upstream */);
+ enableTethering(iface, requestWithStaticIpv4(local, client), null /* any upstream */);
fail("Unexpectedly accepted invalid IPv4 configuration: " + local + ", " + client);
} catch (IllegalArgumentException | NullPointerException expected) { }
}
@@ -1180,4 +1183,32 @@
TX_UDP_PACKET_COUNT * (TX_UDP_PACKET_SIZE + IPV6_HEADER_LEN - IPV4_HEADER_MIN_LEN),
newEgress4.bytes - oldEgress4.bytes);
}
+
+ @Test
+ public void testTetheringVirtual() throws Exception {
+ assumeFalse(isInterfaceForTetheringAvailable());
+ setIncludeTestInterfaces(true);
+
+ TestNetworkInterface downstreamIface = null;
+ MyTetheringEventCallback tetheringEventCallback = null;
+ PollPacketReader downstreamReader = null;
+ try {
+ downstreamIface = createTestInterface();
+ String iface = downstreamIface.getInterfaceName();
+ final TetheringRequest request = new TetheringRequest.Builder(TETHERING_VIRTUAL)
+ .setConnectivityScope(CONNECTIVITY_SCOPE_GLOBAL)
+ .setInterfaceName(iface)
+ .build();
+ tetheringEventCallback = enableTethering(iface, request, null /* any upstream */);
+
+ FileDescriptor fd = downstreamIface.getFileDescriptor().getFileDescriptor();
+ downstreamReader = makePacketReader(fd, getMTU(downstreamIface));
+ checkTetheredClientCallbacks(
+ downstreamReader, TETHERING_VIRTUAL, tetheringEventCallback);
+ } finally {
+ maybeStopTapPacketReader(downstreamReader);
+ maybeCloseTestInterface(downstreamIface);
+ maybeUnregisterTetheringEventCallback(tetheringEventCallback);
+ }
+ }
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
index 51c2d56..16ebbbb 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
@@ -38,7 +38,6 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.networkstack.apishim.ConstantsShim.KEY_CARRIER_SUPPORTS_TETHERING_BOOL;
-import static com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
import static com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
@@ -592,16 +591,8 @@
.onTetherProvisioningFailed(TETHERING_WIFI, FAILED_TETHERING_REASON);
}
- @IgnoreUpTo(SC_V2)
@Test
- public void testUiProvisioningMultiUser_aboveT() {
- doTestUiProvisioningMultiUser(true, 1);
- doTestUiProvisioningMultiUser(false, 0);
- }
-
- @IgnoreAfter(SC_V2)
- @Test
- public void testUiProvisioningMultiUser_belowT() {
+ public void testUiProvisioningMultiUser() {
doTestUiProvisioningMultiUser(true, 1);
doTestUiProvisioningMultiUser(false, 1);
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
index 1608e1a..c329142 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
@@ -189,7 +189,6 @@
final LinkAddress hotspotAddress = requestDownstreamAddress(mHotspotIpServer);
final IpPrefix hotspotPrefix = asIpPrefix(hotspotAddress);
assertNotEquals(asIpPrefix(mBluetoothAddress), hotspotPrefix);
- releaseDownstream(mHotspotIpServer);
// - Test previous enabled hotspot prefix(cached prefix) is reserved.
when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(
@@ -208,6 +207,7 @@
assertNotEquals(asIpPrefix(mLegacyWifiP2pAddress), etherPrefix);
assertNotEquals(asIpPrefix(mBluetoothAddress), etherPrefix);
assertNotEquals(hotspotPrefix, etherPrefix);
+ releaseDownstream(mHotspotIpServer);
releaseDownstream(mEthernetIpServer);
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
index 0dbf772..d94852e 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
@@ -17,9 +17,11 @@
package com.android.networkstack.tethering;
import static android.Manifest.permission.ACCESS_NETWORK_STATE;
+import static android.Manifest.permission.NETWORK_SETTINGS;
import static android.Manifest.permission.TETHER_PRIVILEGED;
import static android.Manifest.permission.WRITE_SETTINGS;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.net.TetheringManager.TETHERING_VIRTUAL;
import static android.net.TetheringManager.TETHERING_WIFI;
import static android.net.TetheringManager.TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION;
import static android.net.TetheringManager.TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION;
@@ -171,6 +173,10 @@
runTetheringCall(test, false /* isTetheringAllowed */, TETHER_PRIVILEGED);
}
+ private void runAsNetworkSettings(final TestTetheringCall test) throws Exception {
+ runTetheringCall(test, true /* isTetheringAllowed */, NETWORK_SETTINGS, TETHER_PRIVILEGED);
+ }
+
private void runTetheringCall(final TestTetheringCall test, boolean isTetheringAllowed,
String... permissions) throws Exception {
// Allow the test to run even if ACCESS_NETWORK_STATE was granted at the APK level
@@ -370,6 +376,32 @@
});
}
+ @Test
+ public void testStartTetheringWithInterfaceSucceeds() throws Exception {
+ final TetheringRequestParcel request = new TetheringRequestParcel();
+ request.tetheringType = TETHERING_VIRTUAL;
+ request.interfaceName = "avf_tap_fixed";
+
+ runAsNetworkSettings((result) -> {
+ runStartTethering(result, request);
+ verifyNoMoreInteractionsForTethering();
+ });
+ }
+
+ @Test
+ public void testStartTetheringNoNetworkStackPermissionWithInterfaceFails() throws Exception {
+ final TetheringRequestParcel request = new TetheringRequestParcel();
+ request.tetheringType = TETHERING_VIRTUAL;
+ request.interfaceName = "avf_tap_fixed";
+
+ runAsTetherPrivileged((result) -> {
+ mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
+ result);
+ result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
+ verifyNoMoreInteractionsForTethering();
+ });
+ }
+
private void runStartTetheringAndVerifyNoPermission(final TestTetheringResult result)
throws Exception {
final TetheringRequestParcel request = new TetheringRequestParcel();
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 17f5081..0c6a95d 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -47,6 +47,7 @@
import static android.net.TetheringManager.TETHERING_ETHERNET;
import static android.net.TetheringManager.TETHERING_NCM;
import static android.net.TetheringManager.TETHERING_USB;
+import static android.net.TetheringManager.TETHERING_VIRTUAL;
import static android.net.TetheringManager.TETHERING_WIFI;
import static android.net.TetheringManager.TETHERING_WIFI_P2P;
import static android.net.TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR;
@@ -255,6 +256,7 @@
private static final String TEST_P2P_IFNAME = "test_p2p-p2p0-0";
private static final String TEST_NCM_IFNAME = "test_ncm0";
private static final String TEST_ETH_IFNAME = "test_eth0";
+ private static final String TEST_VIRT_IFNAME = "test_virt0";
private static final String TEST_BT_IFNAME = "test_pan0";
private static final String TETHERING_NAME = "Tethering";
private static final String[] PROVISIONING_APP_NAME = {"some", "app"};
@@ -417,11 +419,12 @@
|| ifName.equals(TEST_P2P_IFNAME)
|| ifName.equals(TEST_NCM_IFNAME)
|| ifName.equals(TEST_ETH_IFNAME)
- || ifName.equals(TEST_BT_IFNAME));
+ || ifName.equals(TEST_BT_IFNAME)
+ || ifName.equals(TEST_VIRT_IFNAME));
final String[] ifaces = new String[] {
TEST_RNDIS_IFNAME, TEST_WLAN_IFNAME, TEST_WLAN2_IFNAME, TEST_WIFI_IFNAME,
TEST_MOBILE_IFNAME, TEST_DUN_IFNAME, TEST_P2P_IFNAME, TEST_NCM_IFNAME,
- TEST_ETH_IFNAME};
+ TEST_ETH_IFNAME, TEST_VIRT_IFNAME};
return new InterfaceParams(ifName,
CollectionUtils.indexOf(ifaces, ifName) + IFINDEX_OFFSET,
MacAddress.ALL_ZEROS_ADDRESS);
@@ -674,7 +677,8 @@
when(mNetd.interfaceGetList())
.thenReturn(new String[] {
TEST_MOBILE_IFNAME, TEST_WLAN_IFNAME, TEST_WLAN2_IFNAME, TEST_RNDIS_IFNAME,
- TEST_P2P_IFNAME, TEST_NCM_IFNAME, TEST_ETH_IFNAME, TEST_BT_IFNAME});
+ TEST_P2P_IFNAME, TEST_NCM_IFNAME, TEST_ETH_IFNAME, TEST_BT_IFNAME,
+ TEST_VIRT_IFNAME});
when(mResources.getString(R.string.config_wifi_tether_enable)).thenReturn("");
mInterfaceConfiguration = new InterfaceConfigurationParcel();
mInterfaceConfiguration.flags = new String[0];
@@ -765,12 +769,12 @@
}
private TetheringRequest createTetheringRequest(final int type) {
- return createTetheringRequest(type, null, null, false, CONNECTIVITY_SCOPE_GLOBAL);
+ return createTetheringRequest(type, null, null, false, CONNECTIVITY_SCOPE_GLOBAL, null);
}
private TetheringRequest createTetheringRequest(final int type,
final LinkAddress localIPv4Address, final LinkAddress staticClientAddress,
- final boolean exempt, final int scope) {
+ final boolean exempt, final int scope, final String interfaceName) {
TetheringRequest.Builder builder = new TetheringRequest.Builder(type)
.setExemptFromEntitlementCheck(exempt)
.setConnectivityScope(scope)
@@ -778,6 +782,9 @@
if (localIPv4Address != null && staticClientAddress != null) {
builder.setStaticIpv4Addresses(localIPv4Address, staticClientAddress);
}
+ if (interfaceName != null) {
+ builder.setInterfaceName(interfaceName);
+ }
return builder.build();
}
@@ -2782,7 +2789,7 @@
// Enable USB tethering with a different request and expect that USB is stopped and
// started.
mTethering.startTethering(createTetheringRequest(TETHERING_USB,
- serverLinkAddr, clientLinkAddr, false, CONNECTIVITY_SCOPE_GLOBAL),
+ serverLinkAddr, clientLinkAddr, false, CONNECTIVITY_SCOPE_GLOBAL, null),
TEST_CALLER_PKG, thirdResult);
mLooper.dispatchAll();
thirdResult.assertHasResult();
@@ -2813,7 +2820,7 @@
final ArgumentCaptor<DhcpServingParamsParcel> dhcpParamsCaptor =
ArgumentCaptor.forClass(DhcpServingParamsParcel.class);
mTethering.startTethering(createTetheringRequest(TETHERING_USB,
- serverLinkAddr, clientLinkAddr, false, CONNECTIVITY_SCOPE_GLOBAL),
+ serverLinkAddr, clientLinkAddr, false, CONNECTIVITY_SCOPE_GLOBAL, null),
TEST_CALLER_PKG, null);
mLooper.dispatchAll();
verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_NCM);
@@ -2942,7 +2949,7 @@
setupForRequiredProvisioning();
final TetheringRequest wifiNotExemptRequest =
createTetheringRequest(TETHERING_WIFI, null, null, false,
- CONNECTIVITY_SCOPE_GLOBAL);
+ CONNECTIVITY_SCOPE_GLOBAL, null);
mTethering.startTethering(wifiNotExemptRequest, TEST_CALLER_PKG, null);
mLooper.dispatchAll();
verify(mEntitleMgr).startProvisioningIfNeeded(TETHERING_WIFI, false);
@@ -2956,7 +2963,7 @@
setupForRequiredProvisioning();
final TetheringRequest wifiExemptRequest =
createTetheringRequest(TETHERING_WIFI, null, null, true,
- CONNECTIVITY_SCOPE_GLOBAL);
+ CONNECTIVITY_SCOPE_GLOBAL, null);
mTethering.startTethering(wifiExemptRequest, TEST_CALLER_PKG, null);
mLooper.dispatchAll();
verify(mEntitleMgr, never()).startProvisioningIfNeeded(TETHERING_WIFI, false);
@@ -3779,4 +3786,18 @@
// TODO: Test that a request for hotspot mode doesn't interfere with an
// already operating tethering mode interface.
+
+ @Test
+ public void testVirtualTetheringWithInterfaceName() throws Exception {
+ initTetheringOnTestThread();
+ final TetheringRequest virtualTetheringRequest =
+ createTetheringRequest(TETHERING_VIRTUAL, null, null, false,
+ CONNECTIVITY_SCOPE_GLOBAL, TEST_VIRT_IFNAME);
+ assertEquals(TEST_VIRT_IFNAME, virtualTetheringRequest.getInterfaceName());
+ mTethering.startTethering(virtualTetheringRequest, TEST_CALLER_PKG, null);
+ mLooper.dispatchAll();
+ assertContains(Arrays.asList(mTethering.getTetheredIfaces()), TEST_VIRT_IFNAME);
+ mTethering.stopTethering(TETHERING_VIRTUAL);
+ mLooper.dispatchAll();
+ }
}
diff --git a/bpf/loader/NetBpfLoad.cpp b/bpf/loader/NetBpfLoad.cpp
index c2a1d6e..ce144a7 100644
--- a/bpf/loader/NetBpfLoad.cpp
+++ b/bpf/loader/NetBpfLoad.cpp
@@ -1514,6 +1514,7 @@
REQUIRE(5, 15, 136)
REQUIRE(6, 1, 57)
REQUIRE(6, 6, 0)
+ REQUIRE(6, 12, 0)
#undef REQUIRE
@@ -1544,15 +1545,14 @@
*
* Additionally the 32-bit kernel jit support is poor,
* and 32-bit userspace on 64-bit kernel bpf ringbuffer compatibility is broken.
+ * Note, however, that TV and Wear devices will continue to support 32-bit userspace
+ * on ARM64.
*/
if (isUserspace32bit() && isAtLeastKernelVersion(6, 2, 0)) {
// Stuff won't work reliably, but...
- if (isTV()) {
- // exempt TVs... they don't really need functional advanced networking
- ALOGW("[TV] 32-bit userspace unsupported on 6.2+ kernels.");
- } else if (isWear() && isArm()) {
- // exempt Arm Wear devices (arm32 ABI is far less problematic than x86-32)
- ALOGW("[Arm Wear] 32-bit userspace unsupported on 6.2+ kernels.");
+ if (isArm() && (isTV() || isWear())) {
+ // exempt Arm TV or Wear devices (arm32 ABI is far less problematic than x86-32)
+ ALOGW("[Arm TV/Wear] 32-bit userspace unsupported on 6.2+ kernels.");
} else if (first_api_level <= __ANDROID_API_T__ && isArm()) {
// also exempt Arm devices upgrading with major kernel rev from T-
// might possibly be better for them to run with a newer kernel...
@@ -1566,8 +1566,8 @@
}
}
- // Note: 6.6 is highest version supported by Android V (sdk=35), so this is for sdk=36+
- if (isUserspace32bit() && isAtLeastKernelVersion(6, 7, 0)) {
+ // On handheld, 6.6 is highest version supported by Android V (sdk=35), so this is for sdk=36+
+ if (!isArm() && isUserspace32bit() && isAtLeastKernelVersion(6, 7, 0)) {
ALOGE("64-bit userspace required on 6.7+ kernels.");
return 1;
}
@@ -1659,17 +1659,17 @@
}
// unreachable before U QPR3
- {
+ if (exists(uprobestatsBpfLoader)) {
ALOGI("done, transferring control to uprobestatsbpfload.");
const char *args[] = {
uprobestatsBpfLoader,
NULL,
};
execve(args[0], (char **)args, envp);
+ ALOGI("unable to execute uprobestatsbpfload, transferring control to "
+ "platform bpfloader.");
}
- ALOGI("unable to execute uprobestatsbpfload, transferring control to "
- "platform bpfloader.");
// platform BpfLoader *needs* to run as root
const char * args[] = { platformBpfLoader, NULL, };
execve(args[0], (char**)args, envp);
diff --git a/framework-t/Android.bp b/framework-t/Android.bp
index 26fc145..9d6d356 100644
--- a/framework-t/Android.bp
+++ b/framework-t/Android.bp
@@ -102,6 +102,7 @@
java_library {
name: "framework-connectivity-t-pre-jarjar",
defaults: ["framework-connectivity-t-defaults"],
+ installable: false,
libs: [
"framework-bluetooth.stubs.module_lib",
"framework-wifi.stubs.module_lib",
diff --git a/framework-t/src/android/net/TrafficStats.java b/framework-t/src/android/net/TrafficStats.java
index 81f2cf9..868033a 100644
--- a/framework-t/src/android/net/TrafficStats.java
+++ b/framework-t/src/android/net/TrafficStats.java
@@ -17,6 +17,7 @@
package android.net;
import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+import static android.net.NetworkStats.UID_ALL;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
@@ -33,21 +34,25 @@
import android.content.Context;
import android.media.MediaPlayer;
import android.net.netstats.StatsResult;
+import android.net.netstats.TrafficStatsRateLimitCacheConfig;
import android.os.Binder;
import android.os.Build;
import android.os.RemoteException;
import android.os.StrictMode;
+import android.os.SystemClock;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.BinderUtils;
+import com.android.net.module.util.LruCacheWithExpiry;
import java.io.FileDescriptor;
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.Socket;
import java.net.SocketException;
-
+import java.util.function.LongSupplier;
/**
* Class that provides network traffic statistics. These statistics include
@@ -182,13 +187,48 @@
/** @hide */
public static final int TAG_SYSTEM_PROBE = 0xFFFFFF42;
+ private static final StatsResult EMPTY_STATS = new StatsResult(0L, 0L, 0L, 0L);
+
+ private static final Object sRateLimitCacheLock = new Object();
+
@GuardedBy("TrafficStats.class")
+ @Nullable
private static INetworkStatsService sStatsService;
// The variable will only be accessed in the test, which is effectively
// single-threaded.
+ @Nullable
private static INetworkStatsService sStatsServiceForTest = null;
+ // This holds the configuration for the TrafficStats rate limit caches.
+ // It will be filled with the result of a query to the service the first time
+ // the caller invokes get*Stats APIs.
+ // This variable can be accessed from any thread with the lock held.
+ @GuardedBy("sRateLimitCacheLock")
+ @Nullable
+ private static TrafficStatsRateLimitCacheConfig sRateLimitCacheConfig;
+
+ // Cache for getIfaceStats and getTotalStats binder interfaces.
+ // This variable can be accessed from any thread with the lock held,
+ // while the cache itself is thread-safe and can be accessed outside
+ // the lock.
+ @GuardedBy("sRateLimitCacheLock")
+ @Nullable
+ private static LruCacheWithExpiry<String, StatsResult> sRateLimitIfaceCache;
+
+ // Cache for getUidStats binder interface.
+ // This variable can be accessed from any thread with the lock held,
+ // while the cache itself is thread-safe and can be accessed outside
+ // the lock.
+ @GuardedBy("sRateLimitCacheLock")
+ @Nullable
+ private static LruCacheWithExpiry<Integer, StatsResult> sRateLimitUidCache;
+
+ // The variable will only be accessed in the test, which is effectively
+ // single-threaded.
+ @Nullable
+ private static LongSupplier sTimeSupplierForTest = null;
+
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
private synchronized static INetworkStatsService getStatsService() {
if (sStatsServiceForTest != null) return sStatsServiceForTest;
@@ -215,6 +255,28 @@
}
/**
+ * Set time supplier for test, or null to reset.
+ *
+ * @hide
+ */
+ @VisibleForTesting(visibility = PRIVATE)
+ public static void setTimeSupplierForTest(LongSupplier timeSupplier) {
+ sTimeSupplierForTest = timeSupplier;
+ }
+
+ /**
+ * Trigger query rate-limit cache config and initializing the caches.
+ *
+ * This is for test purpose.
+ *
+ * @hide
+ */
+ @VisibleForTesting(visibility = PRIVATE)
+ public static void reinitRateLimitCacheForTest() {
+ maybeGetConfigAndInitRateLimitCache(true /* forceReinit */);
+ }
+
+ /**
* Snapshot of {@link NetworkStats} when the currently active profiling
* session started, or {@code null} if no session active.
*
@@ -254,6 +316,92 @@
sStatsService = statsManager.getBinder();
}
+ @Nullable
+ private static LruCacheWithExpiry<String, StatsResult> maybeGetRateLimitIfaceCache() {
+ if (!maybeGetConfigAndInitRateLimitCache(false /* forceReinit */)) return null;
+ synchronized (sRateLimitCacheLock) {
+ return sRateLimitIfaceCache;
+ }
+ }
+
+ @Nullable
+ private static LruCacheWithExpiry<Integer, StatsResult> maybeGetRateLimitUidCache() {
+ if (!maybeGetConfigAndInitRateLimitCache(false /* forceReinit */)) return null;
+ synchronized (sRateLimitCacheLock) {
+ return sRateLimitUidCache;
+ }
+ }
+
+ /**
+ * Gets the rate limit cache configuration and init caches if null.
+ *
+ * Gets the configuration from the service as the configuration
+ * is not expected to change dynamically. And use it to initialize
+ * rate-limit cache if not yet initialized.
+ *
+ * @return whether the rate-limit cache is enabled.
+ *
+ * @hide
+ */
+ private static boolean maybeGetConfigAndInitRateLimitCache(boolean forceReinit) {
+ // Access the service outside the lock to avoid potential deadlocks. This is
+ // especially important when the caller is a system component (e.g.,
+ // NetworkPolicyManagerService) that might hold other locks that the service
+ // also needs.
+ // Although this introduces a race condition where multiple threads might
+ // query the service concurrently, it's acceptable in this case because the
+ // configuration doesn't change dynamically. The configuration only needs to
+ // be fetched once before initializing the cache.
+ synchronized (sRateLimitCacheLock) {
+ if (sRateLimitCacheConfig != null && !forceReinit) {
+ return sRateLimitCacheConfig.isCacheEnabled;
+ }
+ }
+
+ final TrafficStatsRateLimitCacheConfig config;
+ try {
+ config = getStatsService().getRateLimitCacheConfig();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ synchronized (sRateLimitCacheLock) {
+ if (sRateLimitCacheConfig == null || forceReinit) {
+ sRateLimitCacheConfig = config;
+ initRateLimitCacheLocked();
+ }
+ }
+ return config.isCacheEnabled;
+ }
+
+ @GuardedBy("sRateLimitCacheLock")
+ private static void initRateLimitCacheLocked() {
+ // Set up rate limiting caches.
+ // Use uid cache with UID_ALL to cache total stats.
+ if (sRateLimitCacheConfig.isCacheEnabled) {
+ // A time supplier which is monotonic until device reboots, and counts
+ // time spent in sleep. This is needed to ensure the get*Stats caller
+ // won't get stale value after system time adjustment or waking up from sleep.
+ final LongSupplier realtimeSupplier = (sTimeSupplierForTest != null
+ ? sTimeSupplierForTest : () -> SystemClock.elapsedRealtime());
+ sRateLimitIfaceCache = new LruCacheWithExpiry<String, StatsResult>(
+ realtimeSupplier,
+ sRateLimitCacheConfig.expiryDurationMs,
+ sRateLimitCacheConfig.maxEntries,
+ (statsResult) -> !isEmpty(statsResult)
+ );
+ sRateLimitUidCache = new LruCacheWithExpiry<Integer, StatsResult>(
+ realtimeSupplier,
+ sRateLimitCacheConfig.expiryDurationMs,
+ sRateLimitCacheConfig.maxEntries,
+ (statsResult) -> !isEmpty(statsResult)
+ );
+ } else {
+ sRateLimitIfaceCache = null;
+ sRateLimitUidCache = null;
+ }
+ }
+
/**
* Attach the socket tagger implementation to the current process, to
* get notified when a socket's {@link FileDescriptor} is assigned to
@@ -736,6 +884,14 @@
android.Manifest.permission.NETWORK_STACK,
android.Manifest.permission.NETWORK_SETTINGS})
public static void clearRateLimitCaches() {
+ final LruCacheWithExpiry<String, StatsResult> ifaceCache = maybeGetRateLimitIfaceCache();
+ if (ifaceCache != null) {
+ ifaceCache.clear();
+ }
+ final LruCacheWithExpiry<Integer, StatsResult> uidCache = maybeGetRateLimitUidCache();
+ if (uidCache != null) {
+ uidCache.clear();
+ }
try {
getStatsService().clearTrafficStatsRateLimitCaches();
} catch (RemoteException e) {
@@ -985,35 +1141,76 @@
/** @hide */
public static long getUidStats(int uid, int type) {
- final StatsResult stats;
+ return fetchStats(maybeGetRateLimitUidCache(), uid,
+ () -> getStatsService().getUidStats(uid), type);
+ }
+
+ // Note: This method calls to the service, do not invoke this method with lock held.
+ private static <K> long fetchStats(
+ @Nullable LruCacheWithExpiry<K, StatsResult> cache, K key,
+ BinderUtils.ThrowingSupplier<StatsResult, RemoteException> statsFetcher, int type) {
try {
- stats = getStatsService().getUidStats(uid);
+ final StatsResult stats;
+ if (cache != null) {
+ stats = fetchStatsWithCache(cache, key, statsFetcher);
+ } else {
+ // Cache is not enabled, fetch directly from service.
+ stats = statsFetcher.get();
+ }
+ return getEntryValueForType(stats, type);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
- return getEntryValueForType(stats, type);
+ }
+
+ // Note: This method calls to the service, do not invoke this method with lock held.
+ @Nullable
+ private static <K> StatsResult fetchStatsWithCache(LruCacheWithExpiry<K, StatsResult> cache,
+ K key, BinderUtils.ThrowingSupplier<StatsResult, RemoteException> statsFetcher)
+ throws RemoteException {
+ // Attempt to retrieve from the cache first.
+ StatsResult stats = cache.get(key);
+
+ // Although the cache instance is thread-safe, this can still introduce a
+ // race condition between threads of the same process, potentially
+ // returning non-monotonic results. This is because there is no lock
+ // between get, fetch, and put operations. This is considered acceptable
+ // because varying thread execution speeds can also cause non-monotonic
+ // results, even with locking.
+ if (stats == null) {
+ // Cache miss, fetch from the service.
+ stats = statsFetcher.get();
+
+ // Update the cache with the fetched result if valid.
+ if (stats != null && !isEmpty(stats)) {
+ final StatsResult cachedValue = cache.putIfAbsent(key, stats);
+ if (cachedValue != null) {
+ // Some other thread cached a value after this thread
+ // originally got a cache miss. Return the cached value
+ // to ensure all returned values after caching are consistent.
+ return cachedValue;
+ }
+ }
+ }
+ return stats;
+ }
+
+ private static boolean isEmpty(StatsResult stats) {
+ return stats.equals(EMPTY_STATS);
}
/** @hide */
public static long getTotalStats(int type) {
- final StatsResult stats;
- try {
- stats = getStatsService().getTotalStats();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- return getEntryValueForType(stats, type);
+ // In practice, Bpf doesn't use UID_ALL for storing per-UID stats.
+ // Use uid cache with UID_ALL to cache total stats.
+ return fetchStats(maybeGetRateLimitUidCache(), UID_ALL,
+ () -> getStatsService().getTotalStats(), type);
}
/** @hide */
public static long getIfaceStats(String iface, int type) {
- final StatsResult stats;
- try {
- stats = getStatsService().getIfaceStats(iface);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- return getEntryValueForType(stats, type);
+ return fetchStats(maybeGetRateLimitIfaceCache(), iface,
+ () -> getStatsService().getIfaceStats(iface), type);
}
/**
diff --git a/framework/Android.bp b/framework/Android.bp
index 7351648..a1c6a15 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -144,6 +144,7 @@
java_library {
name: "framework-connectivity-pre-jarjar",
defaults: ["framework-module-defaults"],
+ installable: false,
min_sdk_version: "30",
static_libs: [
"framework-connectivity-pre-jarjar-without-cronet",
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index 1ebc4a3..009344d 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -1873,7 +1873,7 @@
public NetworkCapabilities[] getDefaultNetworkCapabilitiesForUser(int userId) {
try {
return mService.getDefaultNetworkCapabilitiesForUser(
- userId, mContext.getOpPackageName(), getAttributionTag());
+ userId, mContext.getOpPackageName(), mContext.getAttributionTag());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1967,7 +1967,7 @@
@NonNull String packageName) {
try {
return mService.getRedactedLinkPropertiesForPackage(
- lp, uid, packageName, getAttributionTag());
+ lp, uid, packageName, mContext.getAttributionTag());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1993,7 +1993,7 @@
public NetworkCapabilities getNetworkCapabilities(@Nullable Network network) {
try {
return mService.getNetworkCapabilities(
- network, mContext.getOpPackageName(), getAttributionTag());
+ network, mContext.getOpPackageName(), mContext.getAttributionTag());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2027,7 +2027,7 @@
int uid, @NonNull String packageName) {
try {
return mService.getRedactedNetworkCapabilitiesForPackage(nc, uid, packageName,
- getAttributionTag());
+ mContext.getAttributionTag());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2752,21 +2752,13 @@
checkLegacyRoutingApiAccess();
try {
return mService.requestRouteToHostAddress(networkType, hostAddress.getAddress(),
- mContext.getOpPackageName(), getAttributionTag());
+ mContext.getOpPackageName(), mContext.getAttributionTag());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
- * @return the context's attribution tag
- */
- // TODO: Remove method and replace with direct call once R code is pushed to AOSP
- private @Nullable String getAttributionTag() {
- return mContext.getAttributionTag();
- }
-
- /**
* Returns the value of the setting for background data usage. If false,
* applications should not use the network if the application is not in the
* foreground. Developers should respect this setting, and check the value
@@ -4705,12 +4697,12 @@
if (reqType == LISTEN) {
request = mService.listenForNetwork(
need, messenger, binder, callbackFlags, callingPackageName,
- getAttributionTag(), declaredMethodsFlag);
+ mContext.getAttributionTag(), declaredMethodsFlag);
} else {
request = mService.requestNetwork(
asUid, need, reqType.ordinal(), messenger, timeoutMs, binder,
- legacyType, callbackFlags, callingPackageName, getAttributionTag(),
- declaredMethodsFlag);
+ legacyType, callbackFlags, callingPackageName,
+ mContext.getAttributionTag(), declaredMethodsFlag);
}
if (request != null) {
sCallbacks.put(request, callback);
@@ -5127,7 +5119,7 @@
try {
mService.pendingRequestForNetwork(
request.networkCapabilities, operation, mContext.getOpPackageName(),
- getAttributionTag());
+ mContext.getAttributionTag());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (ServiceSpecificException e) {
@@ -5276,7 +5268,7 @@
try {
mService.pendingListenForNetwork(
request.networkCapabilities, operation, mContext.getOpPackageName(),
- getAttributionTag());
+ mContext.getAttributionTag());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (ServiceSpecificException e) {
diff --git a/framework/src/android/net/NetworkAgentConfig.java b/framework/src/android/net/NetworkAgentConfig.java
index da12a0a..deaa734 100644
--- a/framework/src/android/net/NetworkAgentConfig.java
+++ b/framework/src/android/net/NetworkAgentConfig.java
@@ -272,6 +272,27 @@
return mVpnRequiresValidation;
}
+ /**
+ * Whether the native network creation should be skipped.
+ *
+ * If set, the native network and routes should be maintained by the caller.
+ *
+ * @hide
+ */
+ private boolean mSkipNativeNetworkCreation = false;
+
+
+ /**
+ * @return Whether the native network creation should be skipped.
+ * @hide
+ */
+ // TODO: Expose API when ready.
+ // @FlaggedApi(Flags.FLAG_TETHERING_NETWORK_AGENT)
+ // @SystemApi(client = MODULE_LIBRARIES) when ready.
+ public boolean shouldSkipNativeNetworkCreation() {
+ return mSkipNativeNetworkCreation;
+ }
+
/** @hide */
public NetworkAgentConfig() {
}
@@ -293,6 +314,7 @@
mLegacyExtraInfo = nac.mLegacyExtraInfo;
excludeLocalRouteVpn = nac.excludeLocalRouteVpn;
mVpnRequiresValidation = nac.mVpnRequiresValidation;
+ mSkipNativeNetworkCreation = nac.mSkipNativeNetworkCreation;
}
}
@@ -484,6 +506,26 @@
}
/**
+ * Sets the native network creation should be skipped.
+ *
+ * @return this builder, to facilitate chaining.
+ * @hide
+ */
+ @NonNull
+ // TODO: Expose API when ready.
+ // @FlaggedApi(Flags.FLAG_TETHERING_NETWORK_AGENT)
+ // @SystemApi(client = MODULE_LIBRARIES) when ready.
+ public Builder setSkipNativeNetworkCreation(boolean skipNativeNetworkCreation) {
+ if (!SdkLevel.isAtLeastV()) {
+ // Local agents are supported starting on U on TVs and on V on everything else.
+ // Thus, only support this flag on V+.
+ throw new UnsupportedOperationException("Method is not supported");
+ }
+ mConfig.mSkipNativeNetworkCreation = skipNativeNetworkCreation;
+ return this;
+ }
+
+ /**
* Returns the constructed {@link NetworkAgentConfig} object.
*/
@NonNull
@@ -510,7 +552,8 @@
&& Objects.equals(legacySubTypeName, that.legacySubTypeName)
&& Objects.equals(mLegacyExtraInfo, that.mLegacyExtraInfo)
&& excludeLocalRouteVpn == that.excludeLocalRouteVpn
- && mVpnRequiresValidation == that.mVpnRequiresValidation;
+ && mVpnRequiresValidation == that.mVpnRequiresValidation
+ && mSkipNativeNetworkCreation == that.mSkipNativeNetworkCreation;
}
@Override
@@ -518,7 +561,8 @@
return Objects.hash(allowBypass, explicitlySelected, acceptUnvalidated,
acceptPartialConnectivity, provisioningNotificationDisabled, subscriberId,
skip464xlat, legacyType, legacySubType, legacyTypeName, legacySubTypeName,
- mLegacyExtraInfo, excludeLocalRouteVpn, mVpnRequiresValidation);
+ mLegacyExtraInfo, excludeLocalRouteVpn, mVpnRequiresValidation,
+ mSkipNativeNetworkCreation);
}
@Override
@@ -539,6 +583,7 @@
+ ", legacyExtraInfo = '" + mLegacyExtraInfo + '\''
+ ", excludeLocalRouteVpn = '" + excludeLocalRouteVpn + '\''
+ ", vpnRequiresValidation = '" + mVpnRequiresValidation + '\''
+ + ", skipNativeNetworkCreation = '" + mSkipNativeNetworkCreation + '\''
+ "}";
}
@@ -563,33 +608,35 @@
out.writeString(mLegacyExtraInfo);
out.writeInt(excludeLocalRouteVpn ? 1 : 0);
out.writeInt(mVpnRequiresValidation ? 1 : 0);
+ out.writeInt(mSkipNativeNetworkCreation ? 1 : 0);
}
public static final @NonNull Creator<NetworkAgentConfig> CREATOR =
new Creator<NetworkAgentConfig>() {
- @Override
- public NetworkAgentConfig createFromParcel(Parcel in) {
- NetworkAgentConfig networkAgentConfig = new NetworkAgentConfig();
- networkAgentConfig.allowBypass = in.readInt() != 0;
- networkAgentConfig.explicitlySelected = in.readInt() != 0;
- networkAgentConfig.acceptUnvalidated = in.readInt() != 0;
- networkAgentConfig.acceptPartialConnectivity = in.readInt() != 0;
- networkAgentConfig.subscriberId = in.readString();
- networkAgentConfig.provisioningNotificationDisabled = in.readInt() != 0;
- networkAgentConfig.skip464xlat = in.readInt() != 0;
- networkAgentConfig.legacyType = in.readInt();
- networkAgentConfig.legacyTypeName = in.readString();
- networkAgentConfig.legacySubType = in.readInt();
- networkAgentConfig.legacySubTypeName = in.readString();
- networkAgentConfig.mLegacyExtraInfo = in.readString();
- networkAgentConfig.excludeLocalRouteVpn = in.readInt() != 0;
- networkAgentConfig.mVpnRequiresValidation = in.readInt() != 0;
- return networkAgentConfig;
- }
+ @Override
+ public NetworkAgentConfig createFromParcel(Parcel in) {
+ NetworkAgentConfig networkAgentConfig = new NetworkAgentConfig();
+ networkAgentConfig.allowBypass = in.readInt() != 0;
+ networkAgentConfig.explicitlySelected = in.readInt() != 0;
+ networkAgentConfig.acceptUnvalidated = in.readInt() != 0;
+ networkAgentConfig.acceptPartialConnectivity = in.readInt() != 0;
+ networkAgentConfig.subscriberId = in.readString();
+ networkAgentConfig.provisioningNotificationDisabled = in.readInt() != 0;
+ networkAgentConfig.skip464xlat = in.readInt() != 0;
+ networkAgentConfig.legacyType = in.readInt();
+ networkAgentConfig.legacyTypeName = in.readString();
+ networkAgentConfig.legacySubType = in.readInt();
+ networkAgentConfig.legacySubTypeName = in.readString();
+ networkAgentConfig.mLegacyExtraInfo = in.readString();
+ networkAgentConfig.excludeLocalRouteVpn = in.readInt() != 0;
+ networkAgentConfig.mVpnRequiresValidation = in.readInt() != 0;
+ networkAgentConfig.mSkipNativeNetworkCreation = in.readInt() != 0;
+ return networkAgentConfig;
+ }
- @Override
- public NetworkAgentConfig[] newArray(int size) {
- return new NetworkAgentConfig[size];
- }
- };
+ @Override
+ public NetworkAgentConfig[] newArray(int size) {
+ return new NetworkAgentConfig[size];
+ }
+ };
}
diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java
index 92b2b09..eef867c 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package com.android.server.net.ct;
import android.annotation.RequiresApi;
@@ -36,7 +37,8 @@
*/
public static boolean enabled(Context context) {
return DeviceConfig.getBoolean(
- Config.NAMESPACE_NETWORK_SECURITY, Config.FLAG_SERVICE_ENABLED, false)
+ Config.NAMESPACE_NETWORK_SECURITY, Config.FLAG_SERVICE_ENABLED,
+ /* defaultValue= */ true)
&& Flags.certificateTransparencyService();
}
diff --git a/service-b/Android.bp b/service-b/Android.bp
new file mode 100644
index 0000000..47439ee
--- /dev/null
+++ b/service-b/Android.bp
@@ -0,0 +1,40 @@
+//
+// 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_team: "trendy_team_enigma",
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+// TODO: b/380331248 This lib is a non-jarjared version of "service-connectivity-b-platform"
+// It will only be included in the Tethering module when the build system flag
+// RELEASE_MOVE_VCN_TO_MAINLINE is enabled. Including "service-connectivity-b-platform"
+// in Tethering will break art branch check because that lib lives in framework/base.
+// Once VCN is moved to Connectivity/, "service-connectivity-b-platform" can be cleaned up.
+java_library {
+ name: "service-connectivity-b-pre-jarjar",
+ defaults: ["service-connectivity-b-pre-jarjar-defaults"],
+ libs: ["service-connectivity-pre-jarjar"],
+
+ sdk_version: "system_server_current",
+
+ // TODO(b/210962470): Bump this to B
+ min_sdk_version: "30",
+
+ apex_available: [
+ "com.android.tethering",
+ ],
+}
diff --git a/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java b/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
index f55db93..81ba530 100644
--- a/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
+++ b/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
@@ -139,11 +139,8 @@
// Base service type
questions.add(new MdnsPointerRecord(serviceTypeLabels, expectUnicastResponse));
for (String subtype : subtypes) {
- final String[] labels = new String[serviceTypeLabels.length + 2];
- labels[0] = MdnsConstants.SUBTYPE_PREFIX + subtype;
- labels[1] = MdnsConstants.SUBTYPE_LABEL;
- System.arraycopy(serviceTypeLabels, 0, labels, 2, serviceTypeLabels.length);
-
+ final String[] labels = MdnsUtils.constructFullSubtype(serviceTypeLabels,
+ MdnsConstants.SUBTYPE_PREFIX + subtype);
questions.add(new MdnsPointerRecord(labels, expectUnicastResponse));
}
}
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 c3cb776..bfef5d9 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
@@ -1482,22 +1482,14 @@
private static String[] splitFullyQualifiedName(
@NonNull NsdServiceInfo info, @NonNull String[] serviceType) {
- final String[] split = new String[serviceType.length + 1];
- split[0] = info.getServiceName();
- System.arraycopy(serviceType, 0, split, 1, serviceType.length);
-
- return split;
+ return CollectionUtils.prependArray(String.class, serviceType, info.getServiceName());
}
private static String[] splitServiceType(@NonNull NsdServiceInfo info) {
// String.split(pattern, 0) removes trailing empty strings, which would appear when
// splitting "domain.name." (with a dot a the end), so this is what is needed here.
final String[] split = info.getServiceType().split("\\.", 0);
- final String[] type = new String[split.length + 1];
- System.arraycopy(split, 0, type, 0, split.length);
- type[split.length] = LOCAL_TLD;
-
- return type;
+ return CollectionUtils.appendArray(String.class, split, LOCAL_TLD);
}
/** Returns whether there will be an SRV record when registering the {@code info}. */
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java
index b640c32..1212e29 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java
@@ -26,6 +26,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresApi;
+import android.annotation.SuppressLint;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -164,6 +165,7 @@
this(context, looper, new Dependencies(), sharedLog, socketRequestMonitor);
}
+ @SuppressLint("NewApi")
MdnsSocketProvider(@NonNull Context context, @NonNull Looper looper,
@NonNull Dependencies deps, @NonNull SharedLog sharedLog,
@NonNull SocketRequestMonitor socketRequestMonitor) {
@@ -335,6 +337,7 @@
}
/*** Start monitoring sockets by listening callbacks for sockets creation or removal */
+ @SuppressLint("NewApi")
public void startMonitoringSockets() {
ensureRunningOnHandlerThread(mHandler);
mRequestStop = false; // Reset stop request flag.
@@ -365,6 +368,7 @@
}
}
+ @SuppressLint("NewApi")
private void maybeStopMonitoringSockets() {
if (!mMonitoringSockets) return; // Already unregistered.
if (!mRequestStop) return; // No stop request.
diff --git a/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java b/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
index 41b15dd..282ca9a 100644
--- a/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
+++ b/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
@@ -28,6 +28,7 @@
import android.util.ArraySet;
import android.util.Pair;
+import com.android.net.module.util.CollectionUtils;
import com.android.server.connectivity.mdns.MdnsConstants;
import com.android.server.connectivity.mdns.MdnsInetAddressRecord;
import com.android.server.connectivity.mdns.MdnsPacket;
@@ -273,11 +274,8 @@
* of ["_printer", "_sub", "_http", "_tcp"].
*/
public static String[] constructFullSubtype(String[] serviceType, String subtype) {
- String[] fullSubtype = new String[serviceType.length + 2];
- fullSubtype[0] = subtype;
- fullSubtype[1] = MdnsConstants.SUBTYPE_LABEL;
- System.arraycopy(serviceType, 0, fullSubtype, 2, serviceType.length);
- return fullSubtype;
+ return CollectionUtils.prependArray(String.class, serviceType, subtype,
+ MdnsConstants.SUBTYPE_LABEL);
}
/** A wrapper class of {@link SystemClock} to be mocked in unit tests. */
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index fb712a1..a8e3203 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -493,7 +493,8 @@
@Nullable
private final TrafficStatsRateLimitCache mTrafficStatsUidCache;
// A feature flag to control whether the client-side rate limit cache should be enabled.
- static final String TRAFFICSTATS_CLIENT_RATE_LIMIT_CACHE_ENABLED_FLAG =
+ @VisibleForTesting
+ public static final String TRAFFICSTATS_CLIENT_RATE_LIMIT_CACHE_ENABLED_FLAG =
"trafficstats_client_rate_limit_cache_enabled_flag";
static final String TRAFFICSTATS_SERVICE_RATE_LIMIT_CACHE_ENABLED_FLAG =
"trafficstats_rate_limit_cache_enabled_flag";
diff --git a/service-t/src/com/android/server/net/TrafficStatsRateLimitCache.java b/service-t/src/com/android/server/net/TrafficStatsRateLimitCache.java
index 667aad1..4f99d1b 100644
--- a/service-t/src/com/android/server/net/TrafficStatsRateLimitCache.java
+++ b/service-t/src/com/android/server/net/TrafficStatsRateLimitCache.java
@@ -29,7 +29,10 @@
/**
* A thread-safe cache for storing and retrieving {@link NetworkStats.Entry} objects,
* with an adjustable expiry duration to manage data freshness.
+ *
+ * @deprecated Use {@link LruCacheWithExpiry} instead.
*/
+// TODO: Remove this when service side rate limit cache solution is removed.
class TrafficStatsRateLimitCache extends
LruCacheWithExpiry<TrafficStatsRateLimitCache.TrafficStatsCacheKey, NetworkStats.Entry> {
@@ -41,7 +44,7 @@
* @param maxSize Maximum number of entries.
*/
TrafficStatsRateLimitCache(@NonNull Clock clock, long expiryDurationMs, int maxSize) {
- super(clock, expiryDurationMs, maxSize, it -> !it.isEmpty());
+ super(()-> clock.millis(), expiryDurationMs, maxSize, it -> !it.isEmpty());
}
public static class TrafficStatsCacheKey {
diff --git a/service/Android.bp b/service/Android.bp
index fd3d4a3..2659ebf 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -207,6 +207,7 @@
},
visibility: [
"//packages/modules/Connectivity/service-t",
+ "//packages/modules/Connectivity/service-b",
"//packages/modules/Connectivity/networksecurity:__subpackages__",
"//packages/modules/Connectivity/tests:__subpackages__",
"//packages/modules/Connectivity/thread/service:__subpackages__",
@@ -252,7 +253,11 @@
"service-networksecurity-pre-jarjar",
service_remoteauth_pre_jarjar_lib,
"service-thread-pre-jarjar",
- ],
+ ] + select(release_flag("RELEASE_MOVE_VCN_TO_MAINLINE"), {
+ true: ["service-connectivity-b-pre-jarjar"],
+ default: [],
+ }),
+
// The below libraries are not actually needed to build since no source is compiled
// (only combining prebuilt static_libs), but they are necessary so that R8 has the right
// references to optimize the code. Without these, there will be missing class warnings and
@@ -338,6 +343,7 @@
name: "service-connectivity-jarjar-gen",
tool_files: [
":service-connectivity-pre-jarjar{.jar}",
+ ":service-connectivity-b-pre-jarjar{.jar}",
":service-connectivity-tiramisu-pre-jarjar{.jar}",
"jarjar-excludes.txt",
],
@@ -347,6 +353,7 @@
out: ["service_connectivity_jarjar_rules.txt"],
cmd: "$(location jarjar-rules-generator) " +
"$(location :service-connectivity-pre-jarjar{.jar}) " +
+ "$(location :service-connectivity-b-pre-jarjar{.jar}) " +
"$(location :service-connectivity-tiramisu-pre-jarjar{.jar}) " +
"--prefix android.net.connectivity " +
"--excludes $(location jarjar-excludes.txt) " +
diff --git a/service/jarjar-excludes.txt b/service/jarjar-excludes.txt
index 7bd3862..9076b53 100644
--- a/service/jarjar-excludes.txt
+++ b/service/jarjar-excludes.txt
@@ -1,3 +1,4 @@
# Classes loaded by SystemServer via their hardcoded name, so they can't be jarjared
com\.android\.server\.ConnectivityServiceInitializer(\$.+)?
+com\.android\.server\.ConnectivityServiceInitializerB(\$.+)?
com\.android\.server\.NetworkStatsServiceInitializer(\$.+)?
diff --git a/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java b/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java
index d99eedc..553a24b 100644
--- a/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java
+++ b/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java
@@ -149,17 +149,17 @@
}
/** Setup interface for tethering. */
- public static void tetherInterface(final INetd netd, final String iface, final IpPrefix dest)
- throws RemoteException, ServiceSpecificException {
- tetherInterface(netd, iface, dest, 20 /* maxAttempts */, 50 /* pollingIntervalMs */);
+ public static void tetherInterface(final INetd netd, int netId, final String iface,
+ final IpPrefix dest) throws RemoteException, ServiceSpecificException {
+ tetherInterface(netd, netId, iface, dest, 20 /* maxAttempts */, 50 /* pollingIntervalMs */);
}
/** Setup interface with configurable retries for tethering. */
- public static void tetherInterface(final INetd netd, final String iface, final IpPrefix dest,
- int maxAttempts, int pollingIntervalMs)
+ public static void tetherInterface(final INetd netd, int netId, final String iface,
+ final IpPrefix dest, int maxAttempts, int pollingIntervalMs)
throws RemoteException, ServiceSpecificException {
netd.tetherInterfaceAdd(iface);
- networkAddInterface(netd, iface, maxAttempts, pollingIntervalMs);
+ networkAddInterface(netd, netId, iface, maxAttempts, pollingIntervalMs);
// Activate a route to dest and IPv6 link local.
modifyRoute(netd, ModifyOperation.ADD, INetd.LOCAL_NET_ID,
new RouteInfo(dest, null, iface, RTN_UNICAST));
@@ -174,12 +174,12 @@
* in use in netd because the ConnectivityService thread hasn't processed the disconnect yet.
* See b/158269544 for detail.
*/
- private static void networkAddInterface(final INetd netd, final String iface,
+ private static void networkAddInterface(final INetd netd, int netId, final String iface,
int maxAttempts, int pollingIntervalMs)
throws ServiceSpecificException, RemoteException {
for (int i = 1; i <= maxAttempts; i++) {
try {
- netd.networkAddInterface(INetd.LOCAL_NET_ID, iface);
+ netd.networkAddInterface(netId, iface);
return;
} catch (ServiceSpecificException e) {
if (e.errorCode == EBUSY && i < maxAttempts) {
@@ -203,28 +203,29 @@
}
}
- /** Add |routes| to local network. */
- public static void addRoutesToLocalNetwork(final INetd netd, final String iface,
+ /** Add |routes| to the given network. */
+ public static void addRoutesToNetwork(final INetd netd, int netId, final String iface,
final List<RouteInfo> routes) {
for (RouteInfo route : routes) {
if (!route.isDefaultRoute()) {
- modifyRoute(netd, ModifyOperation.ADD, INetd.LOCAL_NET_ID, route);
+ modifyRoute(netd, ModifyOperation.ADD, netId, route);
}
}
// IPv6 link local should be activated always.
- modifyRoute(netd, ModifyOperation.ADD, INetd.LOCAL_NET_ID,
+ modifyRoute(netd, ModifyOperation.ADD, netId,
new RouteInfo(new IpPrefix("fe80::/64"), null, iface, RTN_UNICAST));
}
- /** Remove routes from local network. */
- public static int removeRoutesFromLocalNetwork(final INetd netd, final List<RouteInfo> routes) {
+ /** Remove routes from the given network. */
+ public static int removeRoutesFromNetwork(final INetd netd, int netId,
+ final List<RouteInfo> routes) {
int failures = 0;
for (RouteInfo route : routes) {
try {
- modifyRoute(netd, ModifyOperation.REMOVE, INetd.LOCAL_NET_ID, route);
+ modifyRoute(netd, ModifyOperation.REMOVE, netId, route);
} catch (IllegalStateException e) {
failures++;
}
diff --git a/staticlibs/client-libs/tests/unit/src/com/android/net/module/util/NetdUtilsTest.java b/staticlibs/client-libs/tests/unit/src/com/android/net/module/util/NetdUtilsTest.java
index 5069672..ab90a50 100644
--- a/staticlibs/client-libs/tests/unit/src/com/android/net/module/util/NetdUtilsTest.java
+++ b/staticlibs/client-libs/tests/unit/src/com/android/net/module/util/NetdUtilsTest.java
@@ -163,7 +163,7 @@
setNetworkAddInterfaceOutcome(new ServiceSpecificException(expectedCode), expectedTries);
try {
- NetdUtils.tetherInterface(mNetd, IFACE, TEST_IPPREFIX, 20, 0);
+ NetdUtils.tetherInterface(mNetd, LOCAL_NET_ID, IFACE, TEST_IPPREFIX, 20, 0);
fail("Expect throw ServiceSpecificException");
} catch (ServiceSpecificException e) {
assertEquals(e.errorCode, expectedCode);
@@ -177,7 +177,7 @@
setNetworkAddInterfaceOutcome(new RemoteException(), expectedTries);
try {
- NetdUtils.tetherInterface(mNetd, IFACE, TEST_IPPREFIX, 20, 0);
+ NetdUtils.tetherInterface(mNetd, LOCAL_NET_ID, IFACE, TEST_IPPREFIX, 20, 0);
fail("Expect throw RemoteException");
} catch (RemoteException e) { }
@@ -195,7 +195,7 @@
private void verifyTetherInterfaceSucceeds(int expectedTries) throws Exception {
setNetworkAddInterfaceOutcome(null, expectedTries);
- NetdUtils.tetherInterface(mNetd, IFACE, TEST_IPPREFIX);
+ NetdUtils.tetherInterface(mNetd, LOCAL_NET_ID, IFACE, TEST_IPPREFIX);
verify(mNetd).tetherInterfaceAdd(IFACE);
verify(mNetd, times(expectedTries)).networkAddInterface(LOCAL_NET_ID, IFACE);
verify(mNetd, times(2)).networkAddRoute(eq(LOCAL_NET_ID), eq(IFACE), any(), any());
diff --git a/staticlibs/device/com/android/net/module/util/TimerFdUtils.java b/staticlibs/device/com/android/net/module/util/TimerFdUtils.java
index 310dbc9..c7ed911 100644
--- a/staticlibs/device/com/android/net/module/util/TimerFdUtils.java
+++ b/staticlibs/device/com/android/net/module/util/TimerFdUtils.java
@@ -26,12 +26,13 @@
*/
public class TimerFdUtils {
static {
- if (Process.myUid() == Process.SYSTEM_UID) {
+ final String jniLibName = JniUtil.getJniLibraryName(TimerFdUtils.class.getPackage());
+ if (jniLibName.equals("android_net_connectivity_com_android_net_module_util_jni")) {
// This library is part of service-connectivity.jar when in the system server,
// so libservice-connectivity.so is the library to load.
System.loadLibrary("service-connectivity");
} else {
- System.loadLibrary(JniUtil.getJniLibraryName(TimerFdUtils.class.getPackage()));
+ System.loadLibrary(jniLibName);
}
}
diff --git a/staticlibs/device/com/android/net/module/util/TimerFileDescriptor.java b/staticlibs/device/com/android/net/module/util/TimerFileDescriptor.java
new file mode 100644
index 0000000..dbbccc5
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/TimerFileDescriptor.java
@@ -0,0 +1,254 @@
+/*
+ * 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.net.module.util;
+
+import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
+import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.MessageQueue;
+import android.os.ParcelFileDescriptor;
+import android.util.CloseGuard;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.io.IOException;
+
+/**
+ * Represents a Timer file descriptor object used for scheduling tasks with precise delays.
+ * Compared to {@link Handler#postDelayed}, this class offers enhanced accuracy for delayed
+ * callbacks by accounting for periods when the device is in deep sleep.
+ *
+ * <p> This class is designed for use exclusively from the handler thread.
+ *
+ * **Usage Examples:**
+ *
+ * ** Scheduling recurring tasks with the same TimerFileDescriptor **
+ *
+ * ```java
+ * // Create a TimerFileDescriptor
+ * final TimerFileDescriptor timerFd = new TimerFileDescriptor(handler);
+ *
+ * // Schedule a new task with a delay.
+ * timerFd.setDelayedTask(() -> taskToExecute(), delayTime);
+ *
+ * // Once the delay has elapsed, and the task is running, schedule another task.
+ * timerFd.setDelayedTask(() -> anotherTaskToExecute(), anotherDelayTime);
+ *
+ * // Remember to close the TimerFileDescriptor after all tasks have finished running.
+ * timerFd.close();
+ * ```
+ */
+public class TimerFileDescriptor {
+ private static final String TAG = TimerFileDescriptor.class.getSimpleName();
+ // EVENT_ERROR may be generated even if not specified, as per its javadoc.
+ private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR;
+ private final CloseGuard mGuard = new CloseGuard();
+ @NonNull
+ private final Handler mHandler;
+ @NonNull
+ private final MessageQueue mQueue;
+ @NonNull
+ private final ParcelFileDescriptor mParcelFileDescriptor;
+ private final int mFdInt;
+ @Nullable
+ private ITask mTask;
+
+ /**
+ * An interface for defining tasks that can be executed using a {@link Handler}.
+ */
+ public interface ITask {
+ /**
+ * Executes the task using the provided {@link Handler}.
+ *
+ * @param handler The {@link Handler} to use for executing the task.
+ */
+ void post(Handler handler);
+ }
+
+ /**
+ * A task that sends a {@link Message} using a {@link Handler}.
+ */
+ public static class MessageTask implements ITask {
+ private final Message mMessage;
+
+ public MessageTask(Message message) {
+ mMessage = message;
+ }
+
+ /**
+ * Sends the {@link Message} using the provided {@link Handler}.
+ *
+ * @param handler The {@link Handler} to use for sending the message.
+ */
+ @Override
+ public void post(Handler handler) {
+ handler.sendMessage(mMessage);
+ }
+ }
+
+ /**
+ * A task that posts a {@link Runnable} to a {@link Handler}.
+ */
+ public static class RunnableTask implements ITask {
+ private final Runnable mRunnable;
+
+ public RunnableTask(Runnable runnable) {
+ mRunnable = runnable;
+ }
+
+ /**
+ * Posts the {@link Runnable} to the provided {@link Handler}.
+ *
+ * @param handler The {@link Handler} to use for posting the runnable.
+ */
+ @Override
+ public void post(Handler handler) {
+ handler.post(mRunnable);
+ }
+ }
+
+ /**
+ * TimerFileDescriptor constructor
+ *
+ * Note: The constructor is currently safe to call on another thread because it only sets final
+ * members and registers the event to be called on the handler.
+ */
+ public TimerFileDescriptor(@NonNull Handler handler) {
+ mFdInt = TimerFdUtils.createTimerFileDescriptor();
+ mParcelFileDescriptor = ParcelFileDescriptor.adoptFd(mFdInt);
+ mHandler = handler;
+ mQueue = handler.getLooper().getQueue();
+ registerFdEventListener();
+
+ mGuard.open("close");
+ }
+
+ /**
+ * Set a task to be executed after a specified delay.
+ *
+ * <p> A task can only be scheduled once at a time. Cancel previous scheduled task before the
+ * new task is scheduled.
+ *
+ * @param task the task to be executed
+ * @param delayMs the delay time in milliseconds
+ * @throws IllegalArgumentException if try to replace the current scheduled task
+ * @throws IllegalArgumentException if the delay time is less than 0
+ */
+ public void setDelayedTask(@NonNull ITask task, long delayMs) {
+ ensureRunningOnCorrectThread();
+ if (mTask != null) {
+ throw new IllegalArgumentException("task is already scheduled");
+ }
+ if (delayMs <= 0L) {
+ task.post(mHandler);
+ return;
+ }
+
+ if (TimerFdUtils.setExpirationTime(mFdInt, delayMs)) {
+ mTask = task;
+ }
+ }
+
+ /**
+ * Cancel the scheduled task.
+ */
+ public void cancelTask() {
+ ensureRunningOnCorrectThread();
+ if (mTask == null) return;
+
+ TimerFdUtils.setExpirationTime(mFdInt, 0 /* delayMs */);
+ mTask = null;
+ }
+
+ /**
+ * Check if there is a scheduled task.
+ */
+ public boolean hasDelayedTask() {
+ ensureRunningOnCorrectThread();
+ return mTask != null;
+ }
+
+ /**
+ * Close the TimerFileDescriptor. This implementation closes the underlying
+ * OS resources allocated to represent this stream.
+ */
+ public void close() {
+ ensureRunningOnCorrectThread();
+ unregisterAndDestroyFd();
+ }
+
+ private void registerFdEventListener() {
+ mQueue.addOnFileDescriptorEventListener(
+ mParcelFileDescriptor.getFileDescriptor(),
+ FD_EVENTS,
+ (fd, events) -> {
+ if (!isRunning()) {
+ return 0;
+ }
+ if ((events & EVENT_INPUT) != 0) {
+ handleExpiration();
+ }
+ return FD_EVENTS;
+ });
+ }
+
+ private boolean isRunning() {
+ return mParcelFileDescriptor.getFileDescriptor().valid();
+ }
+
+ private void handleExpiration() {
+ // Execute the task
+ if (mTask != null) {
+ mTask.post(mHandler);
+ mTask = null;
+ }
+ }
+
+ private void unregisterAndDestroyFd() {
+ if (mGuard != null) {
+ mGuard.close();
+ }
+
+ mQueue.removeOnFileDescriptorEventListener(mParcelFileDescriptor.getFileDescriptor());
+ try {
+ mParcelFileDescriptor.close();
+ } catch (IOException exception) {
+ Log.e(TAG, "close ParcelFileDescriptor failed. ", exception);
+ }
+ }
+
+ private void ensureRunningOnCorrectThread() {
+ if (mHandler.getLooper() != Looper.myLooper()) {
+ throw new IllegalStateException(
+ "Not running on Handler thread: " + Thread.currentThread().getName());
+ }
+ }
+
+ @SuppressWarnings("Finalize")
+ @Override
+ protected void finalize() throws Throwable {
+ if (mGuard != null) {
+ mGuard.warnIfOpen();
+ }
+ super.finalize();
+ }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/CollectionUtils.java b/staticlibs/framework/com/android/net/module/util/CollectionUtils.java
index f3d8c4a..760d849 100644
--- a/staticlibs/framework/com/android/net/module/util/CollectionUtils.java
+++ b/staticlibs/framework/com/android/net/module/util/CollectionUtils.java
@@ -22,6 +22,7 @@
import android.util.Pair;
import android.util.SparseArray;
+import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -413,4 +414,68 @@
}
return -1;
}
+
+ /**
+ * Concatenates multiple arrays of the same type into a single new array.
+ */
+ public static byte[] concatArrays(@NonNull byte[]... arr) {
+ int size = 0;
+ for (byte[] a : arr) {
+ size += a.length;
+ }
+ final byte[] result = new byte[size];
+ int offset = 0;
+ for (byte[] a : arr) {
+ System.arraycopy(a, 0, result, offset, a.length);
+ offset += a.length;
+ }
+ return result;
+ }
+
+ /**
+ * Concatenates multiple arrays of the same type into a single new array.
+ */
+ public static <T> T[] concatArrays(@NonNull Class<T> clazz, @NonNull T[]... arr) {
+ int size = 0;
+ for (T[] a : arr) {
+ size += a.length;
+ }
+ final T[] result = (T[]) Array.newInstance(clazz, size);
+ int offset = 0;
+ for (T[] a : arr) {
+ System.arraycopy(a, 0, result, offset, a.length);
+ offset += a.length;
+ }
+ return result;
+ }
+
+ /**
+ * Prepends the elements of a variable number of prefixes to an existing array (suffix).
+ */
+ public static byte[] prependArray(@NonNull byte[] suffix, @NonNull byte... prefixes) {
+ return concatArrays(prefixes, suffix);
+ }
+
+ /**
+ * Prepends the elements of a variable number of prefixes to an existing array (suffix).
+ */
+ public static <T> T[] prependArray(@NonNull Class<T> clazz, @NonNull T[] suffix,
+ @NonNull T... prefixes) {
+ return concatArrays(clazz, prefixes, suffix);
+ }
+
+ /**
+ * Appends the elements of a variable number of suffixes to an existing array (prefix).
+ */
+ public static byte[] appendArray(@NonNull byte[] prefix, @NonNull byte... suffixes) {
+ return concatArrays(prefix, suffixes);
+ }
+
+ /**
+ * Appends the elements of a variable number of suffixes to an existing array (prefix).
+ */
+ public static <T> T[] appendArray(@NonNull Class<T> clazz, @NonNull T[] prefix,
+ @NonNull T... suffixes) {
+ return concatArrays(clazz, prefix, suffixes);
+ }
}
diff --git a/staticlibs/framework/com/android/net/module/util/HexDump.java b/staticlibs/framework/com/android/net/module/util/HexDump.java
index a22c258..409f611 100644
--- a/staticlibs/framework/com/android/net/module/util/HexDump.java
+++ b/staticlibs/framework/com/android/net/module/util/HexDump.java
@@ -202,7 +202,7 @@
if (c >= 'A' && c <= 'F') return (c - 'A' + 10);
if (c >= 'a' && c <= 'f') return (c - 'a' + 10);
- throw new RuntimeException("Invalid hex char '" + c + "'");
+ throw new IllegalArgumentException("Invalid hex char '" + c + "'");
}
/**
diff --git a/staticlibs/framework/com/android/net/module/util/LruCacheWithExpiry.java b/staticlibs/framework/com/android/net/module/util/LruCacheWithExpiry.java
index 80088b9..96d995a 100644
--- a/staticlibs/framework/com/android/net/module/util/LruCacheWithExpiry.java
+++ b/staticlibs/framework/com/android/net/module/util/LruCacheWithExpiry.java
@@ -22,13 +22,13 @@
import com.android.internal.annotations.GuardedBy;
-import java.time.Clock;
import java.util.Objects;
+import java.util.function.LongSupplier;
import java.util.function.Predicate;
import java.util.function.Supplier;
/**
- * An LRU cache that stores key-value pairs with an expiry time.
+ * A thread-safe LRU cache that stores key-value pairs with an expiry time.
*
* <p>This cache uses an {@link LruCache} to store entries and evicts the least
* recently used entries when the cache reaches its maximum capacity. It also
@@ -41,7 +41,7 @@
* @hide
*/
public class LruCacheWithExpiry<K, V> {
- private final Clock mClock;
+ private final LongSupplier mTimeSupplier;
private final long mExpiryDurationMs;
@GuardedBy("mMap")
private final LruCache<K, CacheValue<V>> mMap;
@@ -50,16 +50,17 @@
/**
* Constructs a new {@link LruCacheWithExpiry} with the specified parameters.
*
- * @param clock The {@link Clock} to use for determining timestamps.
+ * @param timeSupplier The {@link java.util.function.LongSupplier} to use for
+ * determining timestamps.
* @param expiryDurationMs The expiry duration for cached entries in milliseconds.
* @param maxSize The maximum number of entries to hold in the cache.
* @param shouldCacheValue A {@link Predicate} that determines whether a given value should be
* cached. This can be used to filter out certain values from being
* stored in the cache.
*/
- public LruCacheWithExpiry(@NonNull Clock clock, long expiryDurationMs, int maxSize,
- Predicate<V> shouldCacheValue) {
- mClock = clock;
+ public LruCacheWithExpiry(@NonNull LongSupplier timeSupplier, long expiryDurationMs,
+ int maxSize, Predicate<V> shouldCacheValue) {
+ mTimeSupplier = timeSupplier;
mExpiryDurationMs = expiryDurationMs;
mMap = new LruCache<>(maxSize);
mShouldCacheValue = shouldCacheValue;
@@ -119,7 +120,26 @@
public void put(@NonNull K key, @NonNull V value) {
Objects.requireNonNull(value);
synchronized (mMap) {
- mMap.put(key, new CacheValue<>(mClock.millis(), value));
+ mMap.put(key, new CacheValue<>(mTimeSupplier.getAsLong(), value));
+ }
+ }
+
+ /**
+ * Stores a value in the cache if absent, associated with the given key.
+ *
+ * @param key The key to associate with the value.
+ * @param value The value to store in the cache.
+ * @return The existing value associated with the key, if present; otherwise, null.
+ */
+ @Nullable
+ public V putIfAbsent(@NonNull K key, @NonNull V value) {
+ Objects.requireNonNull(value);
+ synchronized (mMap) {
+ final V existingValue = get(key);
+ if (existingValue == null) {
+ put(key, value);
+ }
+ return existingValue;
}
}
@@ -133,7 +153,7 @@
}
private boolean isExpired(long timestamp) {
- return mClock.millis() > timestamp + mExpiryDurationMs;
+ return mTimeSupplier.getAsLong() > timestamp + mExpiryDurationMs;
}
private static class CacheValue<V> {
diff --git a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
index 5d588cc..4878334 100644
--- a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
+++ b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
@@ -120,6 +120,8 @@
(byte) 0, (byte) 0, (byte) 0, (byte) 0,
(byte) 0, (byte) 0, (byte) 0, (byte) 0,
(byte) 0, (byte) 0, (byte) 0, (byte) 0 });
+ public static final Inet4Address IPV4_ADDR_ALL_HOST_MULTICAST =
+ (Inet4Address) InetAddresses.parseNumericAddress("224.0.0.1");
/**
* CLAT constants
diff --git a/staticlibs/tests/unit/Android.bp b/staticlibs/tests/unit/Android.bp
index 8c54e6a..9d1d291 100644
--- a/staticlibs/tests/unit/Android.bp
+++ b/staticlibs/tests/unit/Android.bp
@@ -55,6 +55,7 @@
// For mockito extended
"libdexmakerjvmtiagent",
"libstaticjvmtiagent",
+ "libcom_android_net_moduletests_util_jni",
],
jarjar_rules: "jarjar-rules.txt",
test_suites: ["device-tests"],
diff --git a/staticlibs/tests/unit/jni/Android.bp b/staticlibs/tests/unit/jni/Android.bp
new file mode 100644
index 0000000..e456471
--- /dev/null
+++ b/staticlibs/tests/unit/jni/Android.bp
@@ -0,0 +1,39 @@
+// 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_team: "trendy_team_fwk_core_networking",
+ // See: http://go/android-license-faq
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_shared {
+ name: "libcom_android_net_moduletests_util_jni",
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-unused-parameter",
+ "-Wthread-safety",
+ ],
+ srcs: [
+ "com_android_net_moduletests_util/onload.cpp",
+ ],
+ static_libs: [
+ "libnet_utils_device_common_timerfdjni",
+ ],
+ shared_libs: [
+ "liblog",
+ "libnativehelper",
+ ],
+}
diff --git a/staticlibs/tests/unit/jni/com_android_net_moduletests_util/onload.cpp b/staticlibs/tests/unit/jni/com_android_net_moduletests_util/onload.cpp
new file mode 100644
index 0000000..a035540
--- /dev/null
+++ b/staticlibs/tests/unit/jni/com_android_net_moduletests_util/onload.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "jni.h"
+#include <nativehelper/JNIHelp.h>
+
+#define LOG_TAG "NetworkStaticLibTestsJni"
+#include <android/log.h>
+
+namespace android {
+
+int register_com_android_net_module_util_TimerFdUtils(JNIEnv *env,
+ char const *class_name);
+
+extern "C" jint JNI_OnLoad(JavaVM *vm, void *) {
+ JNIEnv *env;
+ if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
+ __android_log_print(ANDROID_LOG_FATAL, LOG_TAG, "ERROR: GetEnv failed");
+ return JNI_ERR;
+ }
+
+ if (register_com_android_net_module_util_TimerFdUtils(
+ env, "com/android/net/moduletests/util/TimerFdUtils") < 0)
+ return JNI_ERR;
+
+ return JNI_VERSION_1_6;
+}
+
+}; // namespace android
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt
index 7244803..1aa943e 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt
@@ -19,7 +19,7 @@
import android.util.SparseArray
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
-import com.android.testutils.assertThrows
+import kotlin.test.assertContentEquals
import org.junit.Test
import org.junit.runner.RunWith
import kotlin.test.assertEquals
@@ -196,4 +196,88 @@
assertEquals(1, CollectionUtils.getIndexForValue(sparseArray, value1Copy))
assertEquals(2, CollectionUtils.getIndexForValue(sparseArray, value2))
}
+
+ @Test
+ fun testConcatEmptyByteArrays() {
+ assertContentEquals(
+ byteArrayOf(),
+ CollectionUtils.concatArrays(byteArrayOf(), byteArrayOf())
+ )
+ }
+
+ @Test
+ fun testConcatEmptyStringArrays() {
+ assertContentEquals(
+ arrayOf<String>(),
+ CollectionUtils.concatArrays(
+ String::class.java,
+ arrayOf<String>(),
+ arrayOf<String>()
+ )
+ )
+ }
+
+ @Test
+ fun testConcatByteArrays() {
+ val byteArr1 = byteArrayOf(1, 2, 3)
+ val byteArr2 = byteArrayOf(4, 5, 6)
+ val byteArr3 = byteArrayOf()
+ val byteArrExpected = byteArrayOf(1, 2, 3, 4, 5, 6)
+ assertContentEquals(
+ byteArrExpected,
+ CollectionUtils.concatArrays(byteArr1, byteArr2, byteArr3)
+ )
+ }
+
+ @Test
+ fun testConcatStringArrays() {
+ val stringArr1 = arrayOf("1", "2", "3")
+ val stringArr2 = arrayOf("4", "5", "6")
+ val strinvArr3 = arrayOf<String>()
+ val stringArrExpected = arrayOf("1", "2", "3", "4", "5", "6")
+ assertContentEquals(
+ stringArrExpected,
+ CollectionUtils.concatArrays(String::class.java, stringArr1, stringArr2, strinvArr3)
+ )
+ }
+
+ @Test
+ fun testPrependByteArrays() {
+ val byteArr2 = byteArrayOf(4, 5, 6)
+ val byteArrExpected = byteArrayOf(1, 2, 3, 4, 5, 6)
+ assertContentEquals(
+ byteArrExpected,
+ CollectionUtils.prependArray(byteArr2, 1, 2, 3)
+ )
+ }
+
+ @Test
+ fun testPrependStringArrays() {
+ val stringArr2 = arrayOf("4", "5", "6")
+ val stringArrExpected = arrayOf("1", "2", "3", "4", "5", "6")
+ assertContentEquals(
+ stringArrExpected,
+ CollectionUtils.prependArray(String::class.java, stringArr2, "1", "2", "3")
+ )
+ }
+
+ @Test
+ fun testAppendByteArrays() {
+ val byteArr1 = byteArrayOf(1, 2, 3)
+ val byteArrExpected = byteArrayOf(1, 2, 3, 4, 5, 6)
+ assertContentEquals(
+ byteArrExpected,
+ CollectionUtils.appendArray(byteArr1, 4, 5, 6)
+ )
+ }
+
+ @Test
+ fun testAppendStringArrays() {
+ val stringArr1 = arrayOf("1", "2", "3")
+ val stringArrExpected = arrayOf("1", "2", "3", "4", "5", "6")
+ assertContentEquals(
+ stringArrExpected,
+ CollectionUtils.appendArray(String::class.java, stringArr1, "4", "5", "6")
+ )
+ }
}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/HexDumpTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/HexDumpTest.java
index 5a15585..f81978a 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/HexDumpTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/HexDumpTest.java
@@ -18,6 +18,7 @@
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -50,6 +51,11 @@
}
@Test
+ public void testInvalidHexStringToByteArray() {
+ assertThrows(IllegalArgumentException.class, () -> HexDump.hexStringToByteArray("abxX"));
+ }
+
+ @Test
public void testIntegerToByteArray() {
assertArrayEquals(new byte[]{(byte) 0xff, (byte) 0x00, (byte) 0x00, (byte) 0x04},
HexDump.toByteArray((int) 0xff000004));
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/LruCacheWithExpiryTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/LruCacheWithExpiryTest.kt
new file mode 100644
index 0000000..b6af892
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/LruCacheWithExpiryTest.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.net.module.util
+
+import com.android.testutils.DevSdkIgnoreRunner
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.function.LongSupplier
+
+@RunWith(DevSdkIgnoreRunner::class)
+class LruCacheWithExpiryTest {
+
+ companion object {
+ private const val CACHE_SIZE = 2
+ private const val EXPIRY_DURATION_MS = 1000L
+ }
+
+ private val timeSupplier = object : LongSupplier {
+ private var currentTimeMillis = 0L
+ override fun getAsLong(): Long = currentTimeMillis
+ fun advanceTime(millis: Long) { currentTimeMillis += millis }
+ }
+
+ private val cache = LruCacheWithExpiry<Int, String>(
+ timeSupplier, EXPIRY_DURATION_MS, CACHE_SIZE) { true }
+
+ @Test
+ fun testPutIfAbsent_keyNotPresent() {
+ val value = cache.putIfAbsent(1, "value1")
+ assertNull(value)
+ assertEquals("value1", cache.get(1))
+ }
+
+ @Test
+ fun testPutIfAbsent_keyPresent() {
+ cache.put(1, "value1")
+ val value = cache.putIfAbsent(1, "value2")
+ assertEquals("value1", value)
+ assertEquals("value1", cache.get(1))
+ }
+
+ @Test
+ fun testPutIfAbsent_keyPresentButExpired() {
+ cache.put(1, "value1")
+ // Advance time to expire the entry
+ timeSupplier.advanceTime(EXPIRY_DURATION_MS + 1)
+ val value = cache.putIfAbsent(1, "value2")
+ assertNull(value)
+ assertEquals("value2", cache.get(1))
+ }
+
+ @Test
+ fun testPutIfAbsent_maxSizeReached() {
+ cache.put(1, "value1")
+ cache.put(2, "value2")
+ cache.putIfAbsent(3, "value3") // This should evict the least recently used entry (1)
+ assertNull(cache.get(1))
+ assertEquals("value2", cache.get(2))
+ assertEquals("value3", cache.get(3))
+ }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/TimerFileDescriptorTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/TimerFileDescriptorTest.kt
new file mode 100644
index 0000000..f5e47c9
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/TimerFileDescriptorTest.kt
@@ -0,0 +1,114 @@
+/*
+ * 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.net.module.util
+
+import android.os.Build
+import android.os.ConditionVariable
+import android.os.Handler
+import android.os.HandlerThread
+import android.os.Looper
+import android.os.Message
+import androidx.test.filters.SmallTest
+import com.android.net.module.util.TimerFileDescriptor.ITask
+import com.android.net.module.util.TimerFileDescriptor.MessageTask
+import com.android.net.module.util.TimerFileDescriptor.RunnableTask
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.tryTest
+import com.android.testutils.visibleOnHandlerThread
+import org.junit.After
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.time.Duration
+import java.time.Instant
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+private const val MSG_TEST = 1
+
+@DevSdkIgnoreRunner.MonitorThreadLeak
+@RunWith(DevSdkIgnoreRunner::class)
+@SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+class TimerFileDescriptorTest {
+ private class TestHandler(looper: Looper) : Handler(looper) {
+ override fun handleMessage(msg: Message) {
+ val cv = msg.obj as ConditionVariable
+ cv.open()
+ }
+ }
+ private val thread = HandlerThread(TimerFileDescriptorTest::class.simpleName).apply { start() }
+ private val handler by lazy { TestHandler(thread.looper) }
+
+ @After
+ fun tearDown() {
+ thread.quitSafely()
+ thread.join()
+ }
+
+ private fun assertDelayedTaskPost(
+ timerFd: TimerFileDescriptor,
+ task: ITask,
+ cv: ConditionVariable
+ ) {
+ val delayTime = 10L
+ val startTime1 = Instant.now()
+ handler.post { timerFd.setDelayedTask(task, delayTime) }
+ assertTrue(cv.block(100L /* timeoutMs*/))
+ assertTrue(Duration.between(startTime1, Instant.now()).toMillis() >= delayTime)
+ }
+
+ @Test
+ fun testSetDelayedTask() {
+ val timerFd = TimerFileDescriptor(handler)
+ tryTest {
+ // Verify the delayed task is executed with the self-implemented ITask
+ val cv1 = ConditionVariable()
+ assertDelayedTaskPost(timerFd, { cv1.open() }, cv1)
+
+ // Verify the delayed task is executed with the RunnableTask
+ val cv2 = ConditionVariable()
+ assertDelayedTaskPost(timerFd, RunnableTask{ cv2.open() }, cv2)
+
+ // Verify the delayed task is executed with the MessageTask
+ val cv3 = ConditionVariable()
+ assertDelayedTaskPost(timerFd, MessageTask(handler.obtainMessage(MSG_TEST, cv3)), cv3)
+ } cleanup {
+ visibleOnHandlerThread(handler) { timerFd.close() }
+ }
+ }
+
+ @Test
+ fun testCancelTask() {
+ // The task is posted and canceled within the same handler loop, so the short delay used
+ // here won't cause flakes.
+ val delayTime = 10L
+ val timerFd = TimerFileDescriptor(handler)
+ val cv = ConditionVariable()
+ tryTest {
+ handler.post {
+ timerFd.setDelayedTask({ cv.open() }, delayTime)
+ assertTrue(timerFd.hasDelayedTask())
+ timerFd.cancelTask()
+ assertFalse(timerFd.hasDelayedTask())
+ }
+ assertFalse(cv.block(20L /* timeoutMs*/))
+ } cleanup {
+ visibleOnHandlerThread(handler) { timerFd.close() }
+ }
+ }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/ConnectivityDiagnosticsCollector.kt b/staticlibs/testutils/devicetests/com/android/testutils/ConnectivityDiagnosticsCollector.kt
index e5b8471..0624e5f 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/ConnectivityDiagnosticsCollector.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/ConnectivityDiagnosticsCollector.kt
@@ -43,9 +43,14 @@
import androidx.test.platform.app.InstrumentationRegistry
import com.android.modules.utils.build.SdkLevel.isAtLeastS
import java.io.ByteArrayOutputStream
+import java.io.CharArrayWriter
import java.io.File
import java.io.FileOutputStream
+import java.io.FileReader
+import java.io.OutputStream
+import java.io.OutputStreamWriter
import java.io.PrintWriter
+import java.io.Reader
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.util.concurrent.CompletableFuture
@@ -80,7 +85,38 @@
var instance: ConnectivityDiagnosticsCollector? = null
}
+ /**
+ * Indicates tcpdump should be started and written to the diagnostics file on test case failure.
+ */
+ annotation class CollectTcpdumpOnFailure
+
+ private class DumpThread(
+ // Keep a reference to the ParcelFileDescriptor otherwise GC would close it
+ private val fd: ParcelFileDescriptor,
+ private val reader: Reader
+ ) : Thread() {
+ private val writer = CharArrayWriter()
+ override fun run() {
+ reader.copyTo(writer)
+ }
+
+ fun closeAndWriteTo(output: OutputStream?) {
+ join()
+ fd.close()
+ if (output != null) {
+ val outputWriter = OutputStreamWriter(output)
+ outputWriter.write("--- tcpdump stopped at ${ZonedDateTime.now()} ---\n")
+ writer.writeTo(outputWriter)
+ }
+ }
+ }
+
+ private data class TcpdumpRun(val pid: Int, val reader: DumpThread)
+
private var failureHeader: String? = null
+
+ // Accessed from the test listener methods which are synchronized by junit (see TestListener)
+ private var tcpdumpRun: TcpdumpRun? = null
private val buffer = ByteArrayOutputStream()
private val failureHeaderExtras = mutableMapOf<String, Any>()
private val collectorDir: File by lazy {
@@ -157,7 +193,57 @@
flushBufferToFileMetric(testData, baseFilename)
}
+ override fun onTestStart(testData: DataRecord, description: Description) {
+ val tcpdumpAnn = description.annotations.firstOrNull { it is CollectTcpdumpOnFailure }
+ as? CollectTcpdumpOnFailure
+ if (tcpdumpAnn != null) {
+ startTcpdumpForTestcaseIfSupported()
+ }
+ }
+
+ private fun startTcpdumpForTestcaseIfSupported() {
+ if (!DeviceInfoUtils.isDebuggable()) {
+ Log.d(TAG, "Cannot start tcpdump, build is not debuggable")
+ return
+ }
+ if (tcpdumpRun != null) {
+ Log.e(TAG, "Cannot start tcpdump: it is already running")
+ return
+ }
+ // executeShellCommand won't tokenize quoted arguments containing spaces (like pcap filters)
+ // properly, so pass in the command in stdin instead of using sh -c 'command'
+ val fds = instrumentation.uiAutomation.executeShellCommandRw("sh")
+
+ val stdout = fds[0]
+ val stdin = fds[1]
+ ParcelFileDescriptor.AutoCloseOutputStream(stdin).use { writer ->
+ // Echo the current pid, and replace it (with exec) with the tcpdump process, so the
+ // tcpdump pid is known.
+ writer.write(
+ "echo $$; exec su 0 tcpdump -n -i any -U -xx".encodeToByteArray()
+ )
+ }
+ val reader = FileReader(stdout.fileDescriptor).buffered()
+ val tcpdumpPid = Integer.parseInt(reader.readLine())
+ val dumpThread = DumpThread(stdout, reader)
+ dumpThread.start()
+ tcpdumpRun = TcpdumpRun(tcpdumpPid, dumpThread)
+ }
+
+ private fun stopTcpdumpIfRunning(output: OutputStream?) {
+ val run = tcpdumpRun ?: return
+ // Send SIGTERM for graceful shutdown of tcpdump so that it can flush its output
+ executeCommandBlocking("su 0 kill ${run.pid}")
+ run.reader.closeAndWriteTo(output)
+ tcpdumpRun = null
+ }
+
override fun onTestEnd(testData: DataRecord, description: Description) {
+ // onTestFail is called before onTestEnd, so if the test failed tcpdump would already have
+ // been stopped and output dumped. Here this stops tcpdump if the test succeeded, throwing
+ // away its output.
+ stopTcpdumpIfRunning(output = null)
+
// Tests may call methods like collectDumpsysConnectivity to collect diagnostics at any time
// during the run, for example to observe state at various points to investigate a flake
// and compare passing/failing cases.
@@ -196,6 +282,7 @@
fos.write("\n".toByteArray())
}
fos.write(buffer.toByteArray())
+ stopTcpdumpIfRunning(fos)
}
failureHeader = null
buffer.reset()
diff --git a/staticlibs/testutils/host/java/com/android/testutils/ConnectivityTestTargetPreparer.kt b/staticlibs/testutils/host/java/com/android/testutils/ConnectivityTestTargetPreparer.kt
index f6168af..a99359b 100644
--- a/staticlibs/testutils/host/java/com/android/testutils/ConnectivityTestTargetPreparer.kt
+++ b/staticlibs/testutils/host/java/com/android/testutils/ConnectivityTestTargetPreparer.kt
@@ -18,12 +18,15 @@
import com.android.ddmlib.testrunner.TestResult
import com.android.tradefed.config.Option
+import com.android.tradefed.invoker.ExecutionFiles.FilesKey
import com.android.tradefed.invoker.TestInformation
+import com.android.tradefed.log.LogUtil.CLog
import com.android.tradefed.result.CollectingTestListener
import com.android.tradefed.result.ddmlib.DefaultRemoteAndroidTestRunner
import com.android.tradefed.targetprep.BaseTargetPreparer
import com.android.tradefed.targetprep.TargetSetupError
import com.android.tradefed.targetprep.suite.SuiteApkInstaller
+import java.io.File
private const val CONNECTIVITY_CHECKER_APK = "ConnectivityTestPreparer.apk"
private const val CONNECTIVITY_PKG_NAME = "com.android.testutils.connectivitypreparer"
@@ -48,7 +51,41 @@
* --test-arg com.android.testutils.ConnectivityTestTargetPreparer:ignore-wifi-check:true".
*/
open class ConnectivityTestTargetPreparer : BaseTargetPreparer() {
- private val installer = SuiteApkInstaller()
+ private val installer = ApkInstaller()
+
+ private class ApkInstaller : SuiteApkInstaller() {
+ override fun getLocalPathForFilename(
+ testInfo: TestInformation,
+ apkFileName: String
+ ): File {
+ if (apkFileName == CONNECTIVITY_CHECKER_APK) {
+ // For the connectivity checker APK, explicitly look for it in the directory of the
+ // host-side preparer.
+ // This preparer is part of the net-tests-utils-host-common library, which includes
+ // the checker APK via device_common_data in its build rule. Both need to be at the
+ // same version so that the preparer calls the right test methods in the checker
+ // APK.
+ // The default strategy for finding test files is to do a recursive search in test
+ // directories, which may find wrong files in wrong directories. In particular,
+ // if some MTS test includes the checker APK, and that test is linked to a module
+ // that boards the train at a version different from this target preparer, there
+ // could be a version difference between the APK and the host-side preparer.
+ // Explicitly looking for the APK in the host-side preparer directory ensures that
+ // it uses the version that was packaged together with the host-side preparer.
+ val testsDir = testInfo.executionFiles().get(FilesKey.TESTS_DIRECTORY)
+ val f = File(testsDir, "net-tests-utils-host-common/$CONNECTIVITY_CHECKER_APK")
+ if (f.isFile) {
+ return f
+ }
+ // When running locally via atest, device_common_data does cause the APK to be put
+ // into the test temp directory, so recursive search is still used to find it in the
+ // directory of the test module that is being run. This is fine because atest runs
+ // are on local trees that do not have versioning problems.
+ CLog.i("APK not found at $f, falling back to recursive search")
+ }
+ return super.getLocalPathForFilename(testInfo, apkFileName)
+ }
+ }
@Option(
name = IGNORE_WIFI_CHECK,
@@ -179,7 +216,7 @@
.contains(":deny")
}
- private fun refreshTime(testInfo: TestInformation,) {
+ private fun refreshTime(testInfo: TestInformation) {
// Forces a synchronous time refresh using the network. Time is fetched synchronously but
// this does not guarantee that system time is updated when it returns.
// This avoids flakes where the system clock rolls back, for example when using test
diff --git a/staticlibs/testutils/host/python/tether_utils.py b/staticlibs/testutils/host/python/tether_utils.py
index 702b596..c63de1f 100644
--- a/staticlibs/testutils/host/python/tether_utils.py
+++ b/staticlibs/testutils/host/python/tether_utils.py
@@ -108,3 +108,6 @@
server.unregisterAll()
# Teardown the hotspot.
server.stopAllTethering()
+ # Some test cases would disable wifi, e.g. cellular upstream tests.
+ # Reconnect to it if feasible.
+ server.reconnectWifiIfSupported()
diff --git a/tests/common/java/android/net/NetworkAgentConfigTest.kt b/tests/common/java/android/net/NetworkAgentConfigTest.kt
index d640a73..fe869f8 100644
--- a/tests/common/java/android/net/NetworkAgentConfigTest.kt
+++ b/tests/common/java/android/net/NetworkAgentConfigTest.kt
@@ -20,6 +20,7 @@
import androidx.test.runner.AndroidJUnit4
import com.android.modules.utils.build.SdkLevel.isAtLeastS
import com.android.modules.utils.build.SdkLevel.isAtLeastT
+import com.android.modules.utils.build.SdkLevel.isAtLeastV
import com.android.testutils.ConnectivityModuleTest
import com.android.testutils.assertParcelingIsLossless
import org.junit.Assert.assertEquals
@@ -47,6 +48,9 @@
setLocalRoutesExcludedForVpn(true)
setVpnRequiresValidation(true)
}
+ if (isAtLeastV()) {
+ setSkipNativeNetworkCreation(true)
+ }
}.build()
assertParcelingIsLossless(config)
}
@@ -71,6 +75,9 @@
setLocalRoutesExcludedForVpn(true)
setVpnRequiresValidation(true)
}
+ if (isAtLeastV()) {
+ setSkipNativeNetworkCreation(true)
+ }
}.build()
assertTrue(config.isExplicitlySelected())
@@ -79,6 +86,9 @@
assertFalse(config.isPartialConnectivityAcceptable())
assertTrue(config.isUnvalidatedConnectivityAcceptable())
assertEquals("TEST_NETWORK", config.getLegacyTypeName())
+ if (isAtLeastV()) {
+ assertTrue(config.shouldSkipNativeNetworkCreation())
+ }
if (isAtLeastT()) {
assertTrue(config.areLocalRoutesExcludedForVpn())
assertTrue(config.isVpnValidationRequired())
diff --git a/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt b/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt
index 49688cc..6da7e9a 100644
--- a/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt
+++ b/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt
@@ -89,6 +89,11 @@
ctsNetUtils.expectNetworkIsSystemDefault(network)
}
+ @Rpc(description = "Reconnect to wifi if supported.")
+ fun reconnectWifiIfSupported() {
+ ctsNetUtils.reconnectWifiIfSupported()
+ }
+
@Rpc(description = "Unregister all connections.")
fun unregisterAll() {
cbHelper.unregisterAll()
diff --git a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
index ff10e1a..cdca8dc 100644
--- a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
+++ b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
@@ -130,7 +130,7 @@
verifyNoCapabilities(builder.build());
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ @Test
public void testTemporarilyNotMeteredCapability() {
assertTrue(new NetworkRequest.Builder()
.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED).build()
@@ -157,7 +157,6 @@
}
@Test
- @IgnoreUpTo(Build.VERSION_CODES.Q)
public void testSpecifier() {
assertNull(new NetworkRequest.Builder().build().getNetworkSpecifier());
final WifiNetworkSpecifier specifier = new WifiNetworkSpecifier.Builder()
@@ -192,7 +191,6 @@
}
@Test
- @IgnoreUpTo(Build.VERSION_CODES.Q)
public void testRequestorPackageName() {
assertNull(new NetworkRequest.Builder().build().getRequestorPackageName());
final String pkgName = "android.net.test";
@@ -216,7 +214,6 @@
}
@Test
- @IgnoreUpTo(Build.VERSION_CODES.Q)
public void testCanBeSatisfiedBy() {
final LocalNetworkSpecifier specifier1 = new LocalNetworkSpecifier(1234 /* id */);
final LocalNetworkSpecifier specifier2 = new LocalNetworkSpecifier(5678 /* id */);
@@ -284,7 +281,6 @@
}
@Test
- @IgnoreUpTo(Build.VERSION_CODES.Q)
public void testInvariantInCanBeSatisfiedBy() {
// Test invariant that result of NetworkRequest.canBeSatisfiedBy() should be the same with
// NetworkCapabilities.satisfiedByNetworkCapabilities().
@@ -388,7 +384,7 @@
otherUidsRequest.canBeSatisfiedBy(ncWithOtherUid));
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ @Test
public void testRequestorUid() {
final NetworkCapabilities nc = new NetworkCapabilities();
// Verify default value is INVALID_UID
diff --git a/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java b/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
index e3d7240..005f6ad 100644
--- a/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
@@ -75,6 +75,7 @@
import com.android.compatibility.common.util.SystemUtil;
import com.android.modules.utils.build.SdkLevel;
import com.android.testutils.AutoReleaseNetworkCallbackRule;
+import com.android.testutils.ConnectivityDiagnosticsCollector;
import com.android.testutils.ConnectivityModuleTest;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
@@ -759,6 +760,7 @@
bucket.getRxBytes(), bucket.getTxBytes()));
}
+ @ConnectivityDiagnosticsCollector.CollectTcpdumpOnFailure
@Test
public void testUidTagStateDetails() throws Exception {
for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerDownstreamTetheringTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerDownstreamTetheringTest.kt
index 24af42b..1973899 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerDownstreamTetheringTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerDownstreamTetheringTest.kt
@@ -92,7 +92,7 @@
assertEquals(downstreamIface.name, iface)
val request = TetheringRequest.Builder(TETHERING_ETHERNET)
.setConnectivityScope(CONNECTIVITY_SCOPE_LOCAL).build()
- tetheringEventCallback = enableEthernetTethering(
+ tetheringEventCallback = enableTethering(
iface, request,
null /* any upstream */
).apply {
@@ -125,7 +125,7 @@
val request = TetheringRequest.Builder(TETHERING_ETHERNET)
.setStaticIpv4Addresses(localAddr, clientAddr)
.setShouldShowEntitlementUi(false).build()
- tetheringEventCallback = enableEthernetTethering(
+ tetheringEventCallback = enableTethering(
iface, request,
null /* any upstream */
).apply {
diff --git a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
index b294d63..e0e22e5 100644
--- a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
+++ b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
@@ -28,6 +28,7 @@
import static android.net.TetheringManager.TETHERING_ETHERNET;
import static android.net.TetheringManager.TETHERING_NCM;
import static android.net.TetheringManager.TETHERING_USB;
+import static android.net.TetheringManager.TETHERING_VIRTUAL;
import static android.net.TetheringManager.TETHERING_WIFI;
import static android.net.TetheringManager.TETHERING_WIFI_P2P;
import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN;
@@ -94,6 +95,7 @@
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
@@ -303,6 +305,13 @@
tr3.setUid(uid);
tr3.setPackageName(packageName);
assertEquals(tr2, tr3);
+
+ final String interfaceName = "test_iface";
+ final TetheringRequest tr4 = new TetheringRequest.Builder(TETHERING_VIRTUAL)
+ .setInterfaceName(interfaceName)
+ .setConnectivityScope(CONNECTIVITY_SCOPE_GLOBAL).build();
+ assertEquals(interfaceName, tr4.getInterfaceName());
+ assertEquals(CONNECTIVITY_SCOPE_GLOBAL, tr4.getConnectivityScope());
}
@Test
@@ -320,23 +329,40 @@
}
@Test
+ public void testTetheringRequestSetInterfaceNameFailsExceptTetheringVirtual() {
+ for (int type : List.of(TETHERING_USB, TETHERING_BLUETOOTH, TETHERING_NCM,
+ TETHERING_ETHERNET)) {
+ try {
+ new TetheringRequest.Builder(type).setInterfaceName("test_iface");
+ fail("Was able to set interface name for tethering type " + type);
+ } catch (IllegalArgumentException e) {
+ // Success
+ }
+ }
+ }
+
+ @Test
public void testTetheringRequestParcelable() {
final SoftApConfiguration softApConfiguration = createSoftApConfiguration("SSID");
final LinkAddress localAddr = new LinkAddress("192.168.24.5/24");
final LinkAddress clientAddr = new LinkAddress("192.168.24.100/24");
- final TetheringRequest withConfig = new TetheringRequest.Builder(TETHERING_WIFI)
+ final TetheringRequest withApConfig = new TetheringRequest.Builder(TETHERING_WIFI)
.setSoftApConfiguration(softApConfiguration)
.setStaticIpv4Addresses(localAddr, clientAddr)
.setExemptFromEntitlementCheck(true)
.setShouldShowEntitlementUi(false).build();
- final TetheringRequest withoutConfig = new TetheringRequest.Builder(TETHERING_WIFI)
+ final TetheringRequest withoutApConfig = new TetheringRequest.Builder(TETHERING_WIFI)
.setStaticIpv4Addresses(localAddr, clientAddr)
.setExemptFromEntitlementCheck(true)
.setShouldShowEntitlementUi(false).build();
- assertEquals(withConfig, ParcelUtils.parcelingRoundTrip(withConfig));
- assertEquals(withoutConfig, ParcelUtils.parcelingRoundTrip(withoutConfig));
- assertNotEquals(withConfig, ParcelUtils.parcelingRoundTrip(withoutConfig));
- assertNotEquals(withoutConfig, ParcelUtils.parcelingRoundTrip(withConfig));
+ assertEquals(withApConfig, ParcelUtils.parcelingRoundTrip(withApConfig));
+ assertEquals(withoutApConfig, ParcelUtils.parcelingRoundTrip(withoutApConfig));
+ assertNotEquals(withApConfig, ParcelUtils.parcelingRoundTrip(withoutApConfig));
+ assertNotEquals(withoutApConfig, ParcelUtils.parcelingRoundTrip(withApConfig));
+
+ final TetheringRequest withIfaceName = new TetheringRequest.Builder(TETHERING_VIRTUAL)
+ .setInterfaceName("test_iface").build();
+ assertEquals(withIfaceName, ParcelUtils.parcelingRoundTrip(withIfaceName));
}
@Test
@@ -420,6 +446,18 @@
}
@Test
+ public void testStopTetheringRequest() throws Exception {
+ TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI).build();
+ Executor executor = Runnable::run;
+ TetheringManager.StopTetheringCallback callback =
+ new TetheringManager.StopTetheringCallback() {};
+ try {
+ mTM.stopTethering(request, executor, callback);
+ fail("stopTethering should throw UnsupportedOperationException");
+ } catch (UnsupportedOperationException expect) { }
+ }
+
+ @Test
public void testEnableTetheringPermission() throws Exception {
final StartTetheringCallback startTetheringCallback = new StartTetheringCallback();
mTM.startTethering(new TetheringRequest.Builder(TETHERING_WIFI).build(),
diff --git a/tests/unit/java/android/net/TrafficStatsTest.kt b/tests/unit/java/android/net/TrafficStatsTest.kt
new file mode 100644
index 0000000..0f85daf
--- /dev/null
+++ b/tests/unit/java/android/net/TrafficStatsTest.kt
@@ -0,0 +1,251 @@
+/*
+ * 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
+
+import android.net.TrafficStats.UNSUPPORTED
+import android.net.netstats.StatsResult
+import android.net.netstats.TrafficStatsRateLimitCacheConfig
+import android.os.Build
+import com.android.server.net.NetworkStatsService.TRAFFICSTATS_CLIENT_RATE_LIMIT_CACHE_ENABLED_FLAG
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.com.android.testutils.SetFeatureFlagsRule
+import com.android.testutils.com.android.testutils.SetFeatureFlagsRule.FeatureFlag
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import java.util.HashMap
+import java.util.function.LongSupplier
+
+const val TEST_EXPIRY_DURATION_MS = 1000
+const val TEST_IFACE = "wlan0"
+
+@RunWith(DevSdkIgnoreRunner::class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
+class TrafficStatsTest {
+ private val binder = mock(INetworkStatsService::class.java)
+ private val myUid = android.os.Process.myUid()
+ private val mockMyUidStatsResult = StatsResult(5L, 6L, 7L, 8L)
+ private val mockIfaceStatsResult = StatsResult(7L, 3L, 10L, 21L)
+ private val mockTotalStatsResult = StatsResult(8L, 1L, 5L, 2L)
+ private val secondUidStatsResult = StatsResult(3L, 7L, 10L, 5L)
+ private val secondIfaceStatsResult = StatsResult(9L, 8L, 7L, 6L)
+ private val secondTotalStatsResult = StatsResult(4L, 3L, 2L, 1L)
+ private val emptyStatsResult = StatsResult(0L, 0L, 0L, 0L)
+ private val unsupportedStatsResult =
+ StatsResult(UNSUPPORTED.toLong(), UNSUPPORTED.toLong(),
+ UNSUPPORTED.toLong(), UNSUPPORTED.toLong())
+
+ private val cacheDisabledConfig = TrafficStatsRateLimitCacheConfig.Builder()
+ .setIsCacheEnabled(false)
+ .setExpiryDurationMs(0)
+ .setMaxEntries(0)
+ .build()
+ private val cacheEnabledConfig = TrafficStatsRateLimitCacheConfig.Builder()
+ .setIsCacheEnabled(true)
+ .setExpiryDurationMs(TEST_EXPIRY_DURATION_MS)
+ .setMaxEntries(100)
+ .build()
+ private val mTestTimeSupplier = TestTimeSupplier()
+
+ private val featureFlags = HashMap<String, Boolean>()
+
+ // This will set feature flags from @FeatureFlag annotations
+ // into the map before setUp() runs.
+ @get:Rule
+ val setFeatureFlagsRule = SetFeatureFlagsRule(
+ { name, enabled -> featureFlags.put(name, enabled == true) },
+ { name -> featureFlags.getOrDefault(name, false) }
+ )
+
+ class TestTimeSupplier : LongSupplier {
+ private var currentTimeMillis = 0L
+
+ override fun getAsLong() = currentTimeMillis
+
+ fun advanceTime(millis: Int) {
+ currentTimeMillis += millis
+ }
+ }
+
+ @Before
+ fun setUp() {
+ TrafficStats.setServiceForTest(binder)
+ TrafficStats.setTimeSupplierForTest(mTestTimeSupplier)
+ mockStats(mockMyUidStatsResult, mockIfaceStatsResult, mockTotalStatsResult)
+ if (featureFlags.getOrDefault(TRAFFICSTATS_CLIENT_RATE_LIMIT_CACHE_ENABLED_FLAG, false)) {
+ doReturn(cacheEnabledConfig).`when`(binder).getRateLimitCacheConfig()
+ } else {
+ doReturn(cacheDisabledConfig).`when`(binder).getRateLimitCacheConfig()
+ }
+ TrafficStats.reinitRateLimitCacheForTest()
+ }
+
+ @After
+ fun tearDown() {
+ TrafficStats.setServiceForTest(null)
+ TrafficStats.setTimeSupplierForTest(null)
+ TrafficStats.reinitRateLimitCacheForTest()
+ }
+
+ private fun assertUidStats(uid: Int, stats: StatsResult) {
+ assertEquals(stats.rxBytes, TrafficStats.getUidRxBytes(uid))
+ assertEquals(stats.rxPackets, TrafficStats.getUidRxPackets(uid))
+ assertEquals(stats.txBytes, TrafficStats.getUidTxBytes(uid))
+ assertEquals(stats.txPackets, TrafficStats.getUidTxPackets(uid))
+ }
+
+ private fun assertIfaceStats(iface: String, stats: StatsResult) {
+ assertEquals(stats.rxBytes, TrafficStats.getRxBytes(iface))
+ assertEquals(stats.rxPackets, TrafficStats.getRxPackets(iface))
+ assertEquals(stats.txBytes, TrafficStats.getTxBytes(iface))
+ assertEquals(stats.txPackets, TrafficStats.getTxPackets(iface))
+ }
+
+ private fun assertTotalStats(stats: StatsResult) {
+ assertEquals(stats.rxBytes, TrafficStats.getTotalRxBytes())
+ assertEquals(stats.rxPackets, TrafficStats.getTotalRxPackets())
+ assertEquals(stats.txBytes, TrafficStats.getTotalTxBytes())
+ assertEquals(stats.txPackets, TrafficStats.getTotalTxPackets())
+ }
+
+ private fun mockStats(uidStats: StatsResult?, ifaceStats: StatsResult?,
+ totalStats: StatsResult?) {
+ doReturn(uidStats).`when`(binder).getUidStats(myUid)
+ doReturn(ifaceStats).`when`(binder).getIfaceStats(TEST_IFACE)
+ doReturn(totalStats).`when`(binder).getTotalStats()
+ }
+
+ private fun assertStats(uidStats: StatsResult, ifaceStats: StatsResult,
+ totalStats: StatsResult) {
+ assertUidStats(myUid, uidStats)
+ assertIfaceStats(TEST_IFACE, ifaceStats)
+ assertTotalStats(totalStats)
+ }
+
+ private fun assertStatsFetchInvocations(wantedInvocations: Int) {
+ verify(binder, times(wantedInvocations)).getUidStats(myUid)
+ verify(binder, times(wantedInvocations)).getIfaceStats(TEST_IFACE)
+ verify(binder, times(wantedInvocations)).getTotalStats()
+ }
+
+ @FeatureFlag(name = TRAFFICSTATS_CLIENT_RATE_LIMIT_CACHE_ENABLED_FLAG)
+ @Test
+ fun testRateLimitCacheExpiry_cacheEnabled() {
+ // Initial fetch, verify binder calls.
+ assertStats(mockMyUidStatsResult, mockIfaceStatsResult, mockTotalStatsResult)
+ assertStatsFetchInvocations(1)
+
+ // Advance time within expiry, verify cached values used.
+ clearInvocations(binder)
+ mockStats(secondUidStatsResult, secondIfaceStatsResult, secondTotalStatsResult)
+ mTestTimeSupplier.advanceTime(1)
+ assertStats(mockMyUidStatsResult, mockIfaceStatsResult, mockTotalStatsResult)
+ assertStatsFetchInvocations(0)
+
+ // Advance time to expire cache, verify new values fetched.
+ clearInvocations(binder)
+ mTestTimeSupplier.advanceTime(TEST_EXPIRY_DURATION_MS)
+ assertStats(secondUidStatsResult, secondIfaceStatsResult, secondTotalStatsResult)
+ assertStatsFetchInvocations(1)
+ }
+
+ @FeatureFlag(name = TRAFFICSTATS_CLIENT_RATE_LIMIT_CACHE_ENABLED_FLAG, enabled = false)
+ @Test
+ fun testRateLimitCacheExpiry_cacheDisabled() {
+ // Initial fetch, verify binder calls.
+ assertStats(mockMyUidStatsResult, mockIfaceStatsResult, mockTotalStatsResult)
+ assertStatsFetchInvocations(4)
+
+ // Advance time within expiry, verify new values fetched.
+ clearInvocations(binder)
+ mockStats(secondUidStatsResult, secondIfaceStatsResult, secondTotalStatsResult)
+ mTestTimeSupplier.advanceTime(1)
+ assertStats(secondUidStatsResult, secondIfaceStatsResult, secondTotalStatsResult)
+ assertStatsFetchInvocations(4)
+ }
+
+ @FeatureFlag(name = TRAFFICSTATS_CLIENT_RATE_LIMIT_CACHE_ENABLED_FLAG)
+ @Test
+ fun testInvalidStatsNotCached_cacheEnabled() {
+ doTestInvalidStatsNotCached()
+ }
+
+ @FeatureFlag(name = TRAFFICSTATS_CLIENT_RATE_LIMIT_CACHE_ENABLED_FLAG, enabled = false)
+ @Test
+ fun testInvalidStatsNotCached_cacheDisabled() {
+ doTestInvalidStatsNotCached()
+ }
+
+ private fun doTestInvalidStatsNotCached() {
+ // Mock null stats, this usually happens when the query is not valid,
+ // e.g. query uid stats of other application.
+ mockStats(null, null, null)
+ assertStats(unsupportedStatsResult, unsupportedStatsResult, unsupportedStatsResult)
+ assertStatsFetchInvocations(4)
+
+ // Verify null stats is not cached, and mock empty stats. This usually
+ // happens when queries with non-existent interface names.
+ clearInvocations(binder)
+ mockStats(emptyStatsResult, emptyStatsResult, emptyStatsResult)
+ assertStats(emptyStatsResult, emptyStatsResult, emptyStatsResult)
+ assertStatsFetchInvocations(4)
+
+ // Verify empty result is also not cached.
+ clearInvocations(binder)
+ assertStats(emptyStatsResult, emptyStatsResult, emptyStatsResult)
+ assertStatsFetchInvocations(4)
+ }
+
+ @FeatureFlag(name = TRAFFICSTATS_CLIENT_RATE_LIMIT_CACHE_ENABLED_FLAG)
+ @Test
+ fun testClearRateLimitCaches_cacheEnabled() {
+ doTestClearRateLimitCaches(true)
+ }
+
+ @FeatureFlag(name = TRAFFICSTATS_CLIENT_RATE_LIMIT_CACHE_ENABLED_FLAG, enabled = false)
+ @Test
+ fun testClearRateLimitCaches_cacheDisabled() {
+ doTestClearRateLimitCaches(false)
+ }
+
+ private fun doTestClearRateLimitCaches(cacheEnabled: Boolean) {
+ // Initial fetch, verify binder calls.
+ assertStats(mockMyUidStatsResult, mockIfaceStatsResult, mockTotalStatsResult)
+ assertStatsFetchInvocations(if (cacheEnabled) 1 else 4)
+
+ // Verify cached values are used.
+ clearInvocations(binder)
+ assertStats(mockMyUidStatsResult, mockIfaceStatsResult, mockTotalStatsResult)
+ assertStatsFetchInvocations(if (cacheEnabled) 0 else 4)
+
+ // Clear caches, verify fetching from the service.
+ clearInvocations(binder)
+ TrafficStats.clearRateLimitCaches()
+ mockStats(secondUidStatsResult, secondIfaceStatsResult, secondTotalStatsResult)
+ assertStats(secondUidStatsResult, secondIfaceStatsResult, secondTotalStatsResult)
+ assertStatsFetchInvocations(if (cacheEnabled) 1 else 4)
+ }
+}
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
index 30d5a02..c55096b 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
@@ -351,14 +351,14 @@
}
otDaemon.initialize(
- mTunIfController.getTunFd(),
shouldEnableThread(),
newOtDaemonConfig(mPersistentSettings.getConfiguration()),
+ mTunIfController.getTunFd(),
mNsdPublisher,
getMeshcopTxtAttributes(mResources.get()),
- mOtDaemonCallbackProxy,
mCountryCodeSupplier.get(),
- FeatureFlags.isTrelEnabled());
+ FeatureFlags.isTrelEnabled(),
+ mOtDaemonCallbackProxy);
otDaemon.asBinder().linkToDeath(() -> mHandler.post(this::onOtDaemonDied), 0);
mOtDaemon = otDaemon;
mHandler.post(mNat64CidrController::maybeUpdateNat64Cidr);
diff --git a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
index aeeed65..875a4ad 100644
--- a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
+++ b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
@@ -16,7 +16,6 @@
package android.net.thread;
-import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
import static android.net.InetAddresses.parseNumericAddress;
import static android.net.thread.utils.IntegrationTestUtils.DEFAULT_DATASET;
import static android.net.thread.utils.IntegrationTestUtils.buildIcmpv4EchoReply;
@@ -38,7 +37,6 @@
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REPLY_TYPE;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE;
-import static com.android.testutils.TestPermissionUtil.runAsShell;
import static com.google.common.truth.Truth.assertThat;
@@ -56,7 +54,6 @@
import android.net.RouteInfo;
import android.net.thread.utils.FullThreadDevice;
import android.net.thread.utils.InfraNetworkDevice;
-import android.net.thread.utils.IntegrationTestUtils;
import android.net.thread.utils.OtDaemonController;
import android.net.thread.utils.TestTunNetworkUtils;
import android.net.thread.utils.ThreadFeatureCheckerRule;
diff --git a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.kt b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.kt
index 07d0390..316f570 100644
--- a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.kt
+++ b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.kt
@@ -388,7 +388,12 @@
raMsg ?: return pioList
val buf = ByteBuffer.wrap(raMsg)
- val ipv6Header = Struct.parse(Ipv6Header::class.java, buf)
+ val ipv6Header = try {
+ Struct.parse(Ipv6Header::class.java, buf)
+ } catch (e: IllegalArgumentException) {
+ // the packet is not IPv6
+ return pioList
+ }
if (ipv6Header.nextHeader != OsConstants.IPPROTO_ICMPV6.toByte()) {
return pioList
}