Merge "Add adb_utils for multidevice test" into main
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index 2963f87..8b3102a 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -698,7 +698,11 @@
/** A configuration set for TetheringRequest. */
private final TetheringRequestParcel mRequestParcel;
- private TetheringRequest(@NonNull final TetheringRequestParcel request) {
+ /**
+ * @hide
+ */
+ @FlaggedApi(Flags.TETHERING_REQUEST_WITH_SOFT_AP_CONFIG)
+ public TetheringRequest(@NonNull final TetheringRequestParcel request) {
mRequestParcel = request;
}
@@ -901,6 +905,28 @@
+ mRequestParcel.exemptFromEntitlementCheck + ", showProvisioningUi= "
+ mRequestParcel.showProvisioningUi + " ]";
}
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (!(obj instanceof TetheringRequest otherRequest)) return false;
+ TetheringRequestParcel parcel = getParcel();
+ TetheringRequestParcel otherParcel = otherRequest.getParcel();
+ return parcel.tetheringType == otherParcel.tetheringType
+ && Objects.equals(parcel.localIPv4Address, otherParcel.localIPv4Address)
+ && Objects.equals(parcel.staticClientAddress, otherParcel.staticClientAddress)
+ && parcel.exemptFromEntitlementCheck == otherParcel.exemptFromEntitlementCheck
+ && parcel.showProvisioningUi == otherParcel.showProvisioningUi
+ && parcel.connectivityScope == otherParcel.connectivityScope;
+ }
+
+ @Override
+ public int hashCode() {
+ TetheringRequestParcel parcel = getParcel();
+ return Objects.hash(parcel.tetheringType, parcel.localIPv4Address,
+ parcel.staticClientAddress, parcel.exemptFromEntitlementCheck,
+ parcel.showProvisioningUi, parcel.connectivityScope);
+ }
}
/**
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index fe5a0c6..a213ac4 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -47,7 +47,7 @@
import android.net.RoutingCoordinatorManager;
import android.net.TetheredClient;
import android.net.TetheringManager;
-import android.net.TetheringRequestParcel;
+import android.net.TetheringManager.TetheringRequest;
import android.net.dhcp.DhcpLeaseParcelable;
import android.net.dhcp.DhcpServerCallbacks;
import android.net.dhcp.DhcpServingParamsParcel;
@@ -404,7 +404,7 @@
}
/** Enable this IpServer. IpServer state machine will be tethered or localHotspot state. */
- public void enable(final int requestedState, final TetheringRequestParcel request) {
+ public void enable(final int requestedState, final TetheringRequest request) {
sendMessage(CMD_TETHER_REQUESTED, requestedState, 0, request);
}
@@ -1006,18 +1006,18 @@
mLinkProperties.setInterfaceName(mIfaceName);
}
- private void maybeConfigureStaticIp(final TetheringRequestParcel request) {
+ private void maybeConfigureStaticIp(final TetheringRequest request) {
// Ignore static address configuration if they are invalid or null. In theory, static
// addresses should not be invalid here because TetheringManager do not allow caller to
// specify invalid static address configuration.
- if (request == null || request.localIPv4Address == null
- || request.staticClientAddress == null || !checkStaticAddressConfiguration(
- request.localIPv4Address, request.staticClientAddress)) {
+ if (request == null || request.getLocalIpv4Address() == null
+ || request.getClientStaticIpv4Address() == null || !checkStaticAddressConfiguration(
+ request.getLocalIpv4Address(), request.getClientStaticIpv4Address())) {
return;
}
- mStaticIpv4ServerAddr = request.localIPv4Address;
- mStaticIpv4ClientAddr = request.staticClientAddress;
+ mStaticIpv4ServerAddr = request.getLocalIpv4Address();
+ mStaticIpv4ClientAddr = request.getClientStaticIpv4Address();
}
class InitialState extends State {
@@ -1034,11 +1034,11 @@
mLastError = TETHER_ERROR_NO_ERROR;
switch (message.arg1) {
case STATE_LOCAL_ONLY:
- maybeConfigureStaticIp((TetheringRequestParcel) message.obj);
+ maybeConfigureStaticIp((TetheringRequest) message.obj);
transitionTo(mLocalHotspotState);
break;
case STATE_TETHERED:
- maybeConfigureStaticIp((TetheringRequestParcel) message.obj);
+ maybeConfigureStaticIp((TetheringRequest) message.obj);
transitionTo(mTetheredState);
break;
default:
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 0ff89d3..c310f16 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -98,7 +98,6 @@
import android.net.TetheringConfigurationParcel;
import android.net.TetheringInterface;
import android.net.TetheringManager.TetheringRequest;
-import android.net.TetheringRequestParcel;
import android.net.Uri;
import android.net.ip.IpServer;
import android.net.wifi.WifiClient;
@@ -148,7 +147,6 @@
import com.android.networkstack.tethering.metrics.TetheringMetrics;
import com.android.networkstack.tethering.util.InterfaceSet;
import com.android.networkstack.tethering.util.PrefixUtils;
-import com.android.networkstack.tethering.util.TetheringUtils;
import com.android.networkstack.tethering.util.VersionedBroadcastListener;
import com.android.networkstack.tethering.wear.WearableConnectionManager;
@@ -232,7 +230,7 @@
// Currently active tethering requests per tethering type. Only one of each type can be
// requested at a time. After a tethering type is requested, the map keeps tethering parameters
// to be used after the interface comes up asynchronously.
- private final SparseArray<TetheringRequestParcel> mActiveTetheringRequests =
+ private final SparseArray<TetheringRequest> mActiveTetheringRequests =
new SparseArray<>();
private final Context mContext;
@@ -661,28 +659,27 @@
processInterfaceStateChange(iface, false /* enabled */);
}
- void startTethering(final TetheringRequestParcel request, final String callerPkg,
+ void startTethering(final TetheringRequest request, final String callerPkg,
final IIntResultListener listener) {
mHandler.post(() -> {
- final TetheringRequestParcel unfinishedRequest = mActiveTetheringRequests.get(
- request.tetheringType);
+ final int type = request.getTetheringType();
+ final TetheringRequest unfinishedRequest = mActiveTetheringRequests.get(type);
// If tethering is already enabled with a different request,
// disable before re-enabling.
- if (unfinishedRequest != null
- && !TetheringUtils.isTetheringRequestEquals(unfinishedRequest, request)) {
- enableTetheringInternal(request.tetheringType, false /* disabled */, null);
- mEntitlementMgr.stopProvisioningIfNeeded(request.tetheringType);
+ if (unfinishedRequest != null && !unfinishedRequest.equals(request)) {
+ enableTetheringInternal(type, false /* disabled */, null);
+ mEntitlementMgr.stopProvisioningIfNeeded(type);
}
- mActiveTetheringRequests.put(request.tetheringType, request);
+ mActiveTetheringRequests.put(type, request);
- if (request.exemptFromEntitlementCheck) {
- mEntitlementMgr.setExemptedDownstreamType(request.tetheringType);
+ if (request.isExemptFromEntitlementCheck()) {
+ mEntitlementMgr.setExemptedDownstreamType(type);
} else {
- mEntitlementMgr.startProvisioningIfNeeded(request.tetheringType,
- request.showProvisioningUi);
+ mEntitlementMgr.startProvisioningIfNeeded(type,
+ request.getShouldShowEntitlementUi());
}
- enableTetheringInternal(request.tetheringType, true /* enabled */, listener);
- mTetheringMetrics.createBuilder(request.tetheringType, callerPkg);
+ enableTetheringInternal(type, true /* enabled */, listener);
+ mTetheringMetrics.createBuilder(type, callerPkg);
});
}
@@ -1018,7 +1015,7 @@
//
// This code cannot race with untether() because they both run on the handler thread.
final int type = tetherState.ipServer.interfaceType();
- final TetheringRequestParcel request = mActiveTetheringRequests.get(type, null);
+ final TetheringRequest request = mActiveTetheringRequests.get(type, null);
if (request != null) {
mActiveTetheringRequests.delete(type);
}
@@ -1075,14 +1072,14 @@
}
private int getRequestedState(int type) {
- final TetheringRequestParcel request = mActiveTetheringRequests.get(type);
+ final TetheringRequest request = mActiveTetheringRequests.get(type);
// The request could have been deleted before we had a chance to complete it.
// If so, assume that the scope is the default scope for this tethering type.
// This likely doesn't matter - if the request has been deleted, then tethering is
// likely going to be stopped soon anyway.
final int connectivityScope = (request != null)
- ? request.connectivityScope
+ ? request.getConnectivityScope()
: TetheringRequest.getDefaultConnectivityScope(type);
return connectivityScope == CONNECTIVITY_SCOPE_LOCAL
@@ -1381,7 +1378,7 @@
}
@VisibleForTesting
- SparseArray<TetheringRequestParcel> getActiveTetheringRequests() {
+ SparseArray<TetheringRequest> getActiveTetheringRequests() {
return mActiveTetheringRequests;
}
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringService.java b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
index 623f502..a147a4a 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringService.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
@@ -38,6 +38,7 @@
import android.net.ITetheringConnector;
import android.net.ITetheringEventCallback;
import android.net.NetworkStack;
+import android.net.TetheringManager.TetheringRequest;
import android.net.TetheringRequestParcel;
import android.net.dhcp.DhcpServerCallbacks;
import android.net.dhcp.DhcpServingParamsParcel;
@@ -137,8 +138,8 @@
listener)) {
return;
}
-
- mTethering.startTethering(request, callerPkg, listener);
+ // TODO(b/216524590): Add UID/packageName of caller to TetheringRequest here
+ mTethering.startTethering(new TetheringRequest(request), callerPkg, listener);
}
@Override
diff --git a/Tethering/src/com/android/networkstack/tethering/util/TetheringUtils.java b/Tethering/src/com/android/networkstack/tethering/util/TetheringUtils.java
index e6236df..76c2f0d 100644
--- a/Tethering/src/com/android/networkstack/tethering/util/TetheringUtils.java
+++ b/Tethering/src/com/android/networkstack/tethering/util/TetheringUtils.java
@@ -16,7 +16,6 @@
package com.android.networkstack.tethering.util;
import android.net.TetherStatsParcel;
-import android.net.TetheringRequestParcel;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -29,7 +28,6 @@
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Arrays;
-import java.util.Objects;
/**
* The classes and the methods for tethering utilization.
@@ -158,20 +156,6 @@
return s & 0xffff;
}
- /** Check whether two TetheringRequestParcels are the same. */
- public static boolean isTetheringRequestEquals(final TetheringRequestParcel request,
- final TetheringRequestParcel otherRequest) {
- if (request == otherRequest) return true;
-
- return request != null && otherRequest != null
- && request.tetheringType == otherRequest.tetheringType
- && Objects.equals(request.localIPv4Address, otherRequest.localIPv4Address)
- && Objects.equals(request.staticClientAddress, otherRequest.staticClientAddress)
- && request.exemptFromEntitlementCheck == otherRequest.exemptFromEntitlementCheck
- && request.showProvisioningUi == otherRequest.showProvisioningUi
- && request.connectivityScope == otherRequest.connectivityScope;
- }
-
/** Get inet6 address for all nodes given scope ID. */
public static Inet6Address getAllNodesForScopeId(int scopeId) {
try {
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 da81bda..c0d7ad4 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
@@ -45,6 +45,7 @@
import android.net.ITetheringConnector;
import android.net.ITetheringEventCallback;
import android.net.TetheringManager;
+import android.net.TetheringManager.TetheringRequest;
import android.net.TetheringRequestParcel;
import android.net.ip.IpServer;
import android.os.Bundle;
@@ -311,7 +312,8 @@
result);
verify(mTethering).isTetheringSupported();
verify(mTethering).isTetheringAllowed();
- verify(mTethering).startTethering(eq(request), eq(TEST_CALLER_PKG), eq(result));
+ verify(mTethering).startTethering(
+ eq(new TetheringRequest(request)), eq(TEST_CALLER_PKG), eq(result));
}
@Test
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 9f430af..e9cde28 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -150,7 +150,7 @@
import android.net.TetheringConfigurationParcel;
import android.net.TetheringInterface;
import android.net.TetheringManager;
-import android.net.TetheringRequestParcel;
+import android.net.TetheringManager.TetheringRequest;
import android.net.dhcp.DhcpLeaseParcelable;
import android.net.dhcp.DhcpServerCallbacks;
import android.net.dhcp.DhcpServingParamsParcel;
@@ -743,22 +743,21 @@
doReturn(upstreamState).when(mUpstreamNetworkMonitor).selectPreferredUpstreamType(any());
}
- private TetheringRequestParcel createTetheringRequestParcel(final int type) {
- return createTetheringRequestParcel(type, null, null, false, CONNECTIVITY_SCOPE_GLOBAL);
+ private TetheringRequest createTetheringRequest(final int type) {
+ return createTetheringRequest(type, null, null, false, CONNECTIVITY_SCOPE_GLOBAL);
}
- private TetheringRequestParcel createTetheringRequestParcel(final int type,
- final LinkAddress serverAddr, final LinkAddress clientAddr, final boolean exempt,
- final int scope) {
- final TetheringRequestParcel request = new TetheringRequestParcel();
- request.tetheringType = type;
- request.localIPv4Address = serverAddr;
- request.staticClientAddress = clientAddr;
- request.exemptFromEntitlementCheck = exempt;
- request.showProvisioningUi = false;
- request.connectivityScope = scope;
-
- return request;
+ private TetheringRequest createTetheringRequest(final int type,
+ final LinkAddress localIPv4Address, final LinkAddress staticClientAddress,
+ final boolean exempt, final int scope) {
+ TetheringRequest.Builder builder = new TetheringRequest.Builder(type)
+ .setExemptFromEntitlementCheck(exempt)
+ .setConnectivityScope(scope)
+ .setShouldShowEntitlementUi(false);
+ if (localIPv4Address != null && staticClientAddress != null) {
+ builder.setStaticIpv4Addresses(localIPv4Address, staticClientAddress);
+ }
+ return builder.build();
}
@NonNull
@@ -911,7 +910,7 @@
private void prepareNcmTethering() {
// Emulate startTethering(TETHERING_NCM) called
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_NCM), TEST_CALLER_PKG,
+ mTethering.startTethering(createTetheringRequest(TETHERING_NCM), TEST_CALLER_PKG,
null);
mLooper.dispatchAll();
verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_NCM);
@@ -919,7 +918,7 @@
private void prepareUsbTethering() {
// Emulate pressing the USB tethering button in Settings UI.
- final TetheringRequestParcel request = createTetheringRequestParcel(TETHERING_USB);
+ final TetheringRequest request = createTetheringRequest(TETHERING_USB);
mTethering.startTethering(request, TEST_CALLER_PKG, null);
mLooper.dispatchAll();
@@ -1909,7 +1908,7 @@
when(mWifiManager.startTetheredHotspot(any(SoftApConfiguration.class))).thenReturn(true);
// Emulate pressing the WiFi tethering button.
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), TEST_CALLER_PKG,
+ mTethering.startTethering(createTetheringRequest(TETHERING_WIFI), TEST_CALLER_PKG,
null);
mLooper.dispatchAll();
verify(mWifiManager, times(1)).startTetheredHotspot(null);
@@ -1938,7 +1937,7 @@
when(mWifiManager.startTetheredHotspot(any(SoftApConfiguration.class))).thenReturn(true);
// Emulate pressing the WiFi tethering button.
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), TEST_CALLER_PKG,
+ mTethering.startTethering(createTetheringRequest(TETHERING_WIFI), TEST_CALLER_PKG,
null);
mLooper.dispatchAll();
verify(mWifiManager, times(1)).startTetheredHotspot(null);
@@ -1988,7 +1987,7 @@
doThrow(new RemoteException()).when(mNetd).ipfwdEnableForwarding(TETHERING_NAME);
// Emulate pressing the WiFi tethering button.
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), TEST_CALLER_PKG,
+ mTethering.startTethering(createTetheringRequest(TETHERING_WIFI), TEST_CALLER_PKG,
null);
mLooper.dispatchAll();
verify(mWifiManager, times(1)).startTetheredHotspot(null);
@@ -2334,7 +2333,7 @@
tetherState = callback.pollTetherStatesChanged();
assertArrayEquals(tetherState.availableList, new TetheringInterface[] {wifiIface});
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), TEST_CALLER_PKG,
+ mTethering.startTethering(createTetheringRequest(TETHERING_WIFI), TEST_CALLER_PKG,
null);
sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
tetherState = callback.pollTetherStatesChanged();
@@ -2430,11 +2429,11 @@
initTetheringOnTestThread();
final TetheredInterfaceRequest mockRequest = mock(TetheredInterfaceRequest.class);
when(mEm.requestTetheredInterface(any(), any())).thenReturn(mockRequest);
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_ETHERNET), TEST_CALLER_PKG,
+ mTethering.startTethering(createTetheringRequest(TETHERING_ETHERNET), TEST_CALLER_PKG,
null);
mLooper.dispatchAll();
verify(mEm, times(1)).requestTetheredInterface(any(), any());
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_ETHERNET), TEST_CALLER_PKG,
+ mTethering.startTethering(createTetheringRequest(TETHERING_ETHERNET), TEST_CALLER_PKG,
null);
mLooper.dispatchAll();
verifyNoMoreInteractions(mEm);
@@ -2644,7 +2643,7 @@
final ResultListener thirdResult = new ResultListener(TETHER_ERROR_NO_ERROR);
// Enable USB tethering and check that Tethering starts USB.
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB), TEST_CALLER_PKG,
+ mTethering.startTethering(createTetheringRequest(TETHERING_USB), TEST_CALLER_PKG,
firstResult);
mLooper.dispatchAll();
firstResult.assertHasResult();
@@ -2652,7 +2651,7 @@
verifyNoMoreInteractions(mUsbManager);
// Enable USB tethering again with the same request and expect no change to USB.
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB), TEST_CALLER_PKG,
+ mTethering.startTethering(createTetheringRequest(TETHERING_USB), TEST_CALLER_PKG,
secondResult);
mLooper.dispatchAll();
secondResult.assertHasResult();
@@ -2661,7 +2660,7 @@
// Enable USB tethering with a different request and expect that USB is stopped and
// started.
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB,
+ mTethering.startTethering(createTetheringRequest(TETHERING_USB,
serverLinkAddr, clientLinkAddr, false, CONNECTIVITY_SCOPE_GLOBAL),
TEST_CALLER_PKG, thirdResult);
mLooper.dispatchAll();
@@ -2692,7 +2691,7 @@
final int clientAddrParceled = 0xc0a8002a;
final ArgumentCaptor<DhcpServingParamsParcel> dhcpParamsCaptor =
ArgumentCaptor.forClass(DhcpServingParamsParcel.class);
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB,
+ mTethering.startTethering(createTetheringRequest(TETHERING_USB,
serverLinkAddr, clientLinkAddr, false, CONNECTIVITY_SCOPE_GLOBAL),
TEST_CALLER_PKG, null);
mLooper.dispatchAll();
@@ -2820,8 +2819,8 @@
public void testExemptFromEntitlementCheck() throws Exception {
initTetheringOnTestThread();
setupForRequiredProvisioning();
- final TetheringRequestParcel wifiNotExemptRequest =
- createTetheringRequestParcel(TETHERING_WIFI, null, null, false,
+ final TetheringRequest wifiNotExemptRequest =
+ createTetheringRequest(TETHERING_WIFI, null, null, false,
CONNECTIVITY_SCOPE_GLOBAL);
mTethering.startTethering(wifiNotExemptRequest, TEST_CALLER_PKG, null);
mLooper.dispatchAll();
@@ -2834,8 +2833,8 @@
reset(mEntitleMgr);
setupForRequiredProvisioning();
- final TetheringRequestParcel wifiExemptRequest =
- createTetheringRequestParcel(TETHERING_WIFI, null, null, true,
+ final TetheringRequest wifiExemptRequest =
+ createTetheringRequest(TETHERING_WIFI, null, null, true,
CONNECTIVITY_SCOPE_GLOBAL);
mTethering.startTethering(wifiExemptRequest, TEST_CALLER_PKG, null);
mLooper.dispatchAll();
@@ -2954,7 +2953,7 @@
when(mEm.requestTetheredInterface(any(), any())).thenReturn(mockRequest);
final ArgumentCaptor<TetheredInterfaceCallback> callbackCaptor =
ArgumentCaptor.forClass(TetheredInterfaceCallback.class);
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_ETHERNET),
+ mTethering.startTethering(createTetheringRequest(TETHERING_ETHERNET),
TEST_CALLER_PKG, null);
mLooper.dispatchAll();
verify(mEm).requestTetheredInterface(any(), callbackCaptor.capture());
@@ -3235,7 +3234,7 @@
final ResultListener result = new ResultListener(TETHER_ERROR_NO_ERROR);
mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */);
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH),
+ mTethering.startTethering(createTetheringRequest(TETHERING_BLUETOOTH),
TEST_CALLER_PKG, result);
mLooper.dispatchAll();
verifySetBluetoothTethering(true /* enable */, true /* bindToPanService */);
@@ -3272,7 +3271,7 @@
final ResultListener result = new ResultListener(TETHER_ERROR_NO_ERROR);
mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */);
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH),
+ mTethering.startTethering(createTetheringRequest(TETHERING_BLUETOOTH),
TEST_CALLER_PKG, result);
mLooper.dispatchAll();
verifySetBluetoothTethering(true /* enable */, true /* bindToPanService */);
@@ -3294,7 +3293,7 @@
// already bound.
mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */);
final ResultListener secondResult = new ResultListener(TETHER_ERROR_NO_ERROR);
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH),
+ mTethering.startTethering(createTetheringRequest(TETHERING_BLUETOOTH),
TEST_CALLER_PKG, secondResult);
mLooper.dispatchAll();
verifySetBluetoothTethering(true /* enable */, false /* bindToPanService */);
@@ -3317,7 +3316,7 @@
initTetheringOnTestThread();
final ResultListener result = new ResultListener(TETHER_ERROR_NO_ERROR);
mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */);
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH),
+ mTethering.startTethering(createTetheringRequest(TETHERING_BLUETOOTH),
TEST_CALLER_PKG, result);
mLooper.dispatchAll();
ServiceListener panListener = verifySetBluetoothTethering(true /* enable */,
@@ -3487,7 +3486,7 @@
// If TETHERING_USB is forced to use ncm function, TETHERING_NCM would no longer be
// available.
final ResultListener ncmResult = new ResultListener(TETHER_ERROR_SERVICE_UNAVAIL);
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_NCM), TEST_CALLER_PKG,
+ mTethering.startTethering(createTetheringRequest(TETHERING_NCM), TEST_CALLER_PKG,
ncmResult);
mLooper.dispatchAll();
ncmResult.assertHasResult();
@@ -3638,7 +3637,7 @@
when(mWifiManager.startTetheredHotspot(any())).thenReturn(true);
// Emulate pressing the WiFi tethering button.
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), TEST_CALLER_PKG,
+ mTethering.startTethering(createTetheringRequest(TETHERING_WIFI), TEST_CALLER_PKG,
null);
mLooper.dispatchAll();
verify(mWifiManager).startTetheredHotspot(null);
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/util/TetheringUtilsTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/util/TetheringUtilsTest.java
index 94ce2b6..f0770f9 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/util/TetheringUtilsTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/util/TetheringUtilsTest.java
@@ -15,8 +15,6 @@
*/
package com.android.networkstack.tethering.util;
-import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
-import static android.net.TetheringManager.TETHERING_USB;
import static android.net.TetheringManager.TETHERING_WIFI;
import static android.system.OsConstants.AF_UNIX;
import static android.system.OsConstants.EAGAIN;
@@ -25,8 +23,6 @@
import static android.system.OsConstants.SOCK_NONBLOCK;
import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
import android.net.LinkAddress;
import android.net.MacAddress;
@@ -43,9 +39,7 @@
import com.android.net.module.util.structs.EthernetHeader;
import com.android.net.module.util.structs.Icmpv6Header;
import com.android.net.module.util.structs.Ipv6Header;
-import com.android.testutils.MiscAsserts;
-import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -61,13 +55,6 @@
private static final LinkAddress TEST_CLIENT_ADDR = new LinkAddress("192.168.43.5/24");
private static final int PACKET_SIZE = 1500;
- private TetheringRequestParcel mTetheringRequest;
-
- @Before
- public void setUp() {
- mTetheringRequest = makeTetheringRequestParcel();
- }
-
public TetheringRequestParcel makeTetheringRequestParcel() {
final TetheringRequestParcel request = new TetheringRequestParcel();
request.tetheringType = TETHERING_WIFI;
@@ -78,40 +65,6 @@
return request;
}
- @Test
- public void testIsTetheringRequestEquals() {
- TetheringRequestParcel request = makeTetheringRequestParcel();
-
- assertTrue(TetheringUtils.isTetheringRequestEquals(mTetheringRequest, mTetheringRequest));
- assertTrue(TetheringUtils.isTetheringRequestEquals(mTetheringRequest, request));
- assertTrue(TetheringUtils.isTetheringRequestEquals(null, null));
- assertFalse(TetheringUtils.isTetheringRequestEquals(mTetheringRequest, null));
- assertFalse(TetheringUtils.isTetheringRequestEquals(null, mTetheringRequest));
-
- request = makeTetheringRequestParcel();
- request.tetheringType = TETHERING_USB;
- assertFalse(TetheringUtils.isTetheringRequestEquals(mTetheringRequest, request));
-
- request = makeTetheringRequestParcel();
- request.localIPv4Address = null;
- request.staticClientAddress = null;
- assertFalse(TetheringUtils.isTetheringRequestEquals(mTetheringRequest, request));
-
- request = makeTetheringRequestParcel();
- request.exemptFromEntitlementCheck = true;
- assertFalse(TetheringUtils.isTetheringRequestEquals(mTetheringRequest, request));
-
- request = makeTetheringRequestParcel();
- request.showProvisioningUi = false;
- assertFalse(TetheringUtils.isTetheringRequestEquals(mTetheringRequest, request));
-
- request = makeTetheringRequestParcel();
- request.connectivityScope = CONNECTIVITY_SCOPE_LOCAL;
- assertFalse(TetheringUtils.isTetheringRequestEquals(mTetheringRequest, request));
-
- MiscAsserts.assertFieldCountEquals(6, TetheringRequestParcel.class);
- }
-
// Writes the specified packet to a filedescriptor, skipping the Ethernet header.
// Needed because the Ipv6Utils methods for building packets always include the Ethernet header,
// but socket filters applied by TetheringUtils expect the packet to start from the IP header.
diff --git a/bpf_progs/netd.h b/bpf_progs/netd.h
index 332979b..4877a4b 100644
--- a/bpf_progs/netd.h
+++ b/bpf_progs/netd.h
@@ -270,5 +270,5 @@
static inline bool is_system_uid(uint32_t uid) {
// MIN_SYSTEM_UID is AID_ROOT == 0, so uint32_t is *always* >= 0
// MAX_SYSTEM_UID is AID_NOBODY == 9999, while AID_APP_START == 10000
- return (uid < AID_APP_START);
+ return ((uid % AID_USER_OFFSET) < AID_APP_START);
}
diff --git a/framework/src/android/net/BpfNetMapsUtils.java b/framework/src/android/net/BpfNetMapsUtils.java
index 4099e2a..282a11e 100644
--- a/framework/src/android/net/BpfNetMapsUtils.java
+++ b/framework/src/android/net/BpfNetMapsUtils.java
@@ -68,6 +68,7 @@
import android.os.Build;
import android.os.Process;
import android.os.ServiceSpecificException;
+import android.os.UserHandle;
import android.system.ErrnoException;
import android.system.Os;
import android.util.Pair;
@@ -330,9 +331,9 @@
) {
throwIfPreT("isUidBlockedByFirewallChains is not available on pre-T devices");
- // System uid is not blocked by firewall chains, see bpf_progs/netd.c
- // TODO: use UserHandle.isCore() once it is accessible
- if (uid < Process.FIRST_APPLICATION_UID) {
+ // System uids are not blocked by firewall chains, see bpf_progs/netd.c
+ // TODO: b/348513058 - use UserHandle.isCore() once it is accessible
+ if (UserHandle.getAppId(uid) < Process.FIRST_APPLICATION_UID) {
return false;
}
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index 4eaf973..ffaf41f 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -71,23 +71,29 @@
import android.telephony.TelephonyManager;
import android.util.ArrayMap;
import android.util.Log;
+import android.util.LruCache;
import android.util.Range;
import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.utils.build.SdkLevel;
import libcore.net.event.NetworkEventDispatcher;
import java.io.IOException;
import java.io.UncheckedIOException;
+import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.Method;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
@@ -1231,6 +1237,19 @@
@GuardedBy("mTetheringEventCallbacks")
private TetheringManager mTetheringManager;
+ // Cache of the most recently used NetworkCallback classes (not instances) -> method flags.
+ // 100 is chosen kind arbitrarily as an unlikely number of different types of NetworkCallback
+ // overrides that a process may have, and should generally not be reached (for example, the
+ // system server services.jar has been observed with dexdump to have only 16 when this was
+ // added, and a very large system services app only had 18).
+ // If this number is exceeded, the code will still function correctly, but re-registering
+ // using a network callback class that was used before, but 100+ other classes have been used in
+ // the meantime, will be a bit slower (as slow as the first registration) because
+ // getDeclaredMethodsFlag must re-examine the callback class to determine what methods it
+ // overrides.
+ private static final LruCache<Class<? extends NetworkCallback>, Integer> sMethodFlagsCache =
+ new LruCache<>(100);
+
private final Object mEnabledConnectivityManagerFeaturesLock = new Object();
// mEnabledConnectivityManagerFeatures is lazy-loaded in this ConnectivityManager instance, but
// fetched from ConnectivityService, where it is loaded in ConnectivityService startup, so it
@@ -3996,6 +4015,55 @@
*/
public static class NetworkCallback {
/**
+ * Bitmask of method flags with all flags set.
+ * @hide
+ */
+ public static final int DECLARED_METHODS_ALL = ~0;
+
+ /**
+ * Bitmask of method flags with no flag set.
+ * @hide
+ */
+ public static final int DECLARED_METHODS_NONE = 0;
+
+ // Tracks whether an instance was created via reflection without calling the constructor.
+ private final boolean mConstructorWasCalled;
+
+ /**
+ * Annotation for NetworkCallback methods to verify filtering is configured properly.
+ *
+ * This is only used in tests to ensure that tests fail when a new callback is added, or
+ * callbacks are modified, without updating
+ * {@link NetworkCallbackMethodsHolder#NETWORK_CB_METHODS} properly.
+ * @hide
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.METHOD)
+ @VisibleForTesting
+ public @interface FilteredCallback {
+ /**
+ * The NetworkCallback.METHOD_* ID of this method.
+ */
+ int methodId();
+
+ /**
+ * The ConnectivityManager.CALLBACK_* message that this method is directly called by.
+ *
+ * If this method is not called by any message, this should be
+ * {@link #CALLBACK_TRANSITIVE_CALLS_ONLY}.
+ */
+ int calledByCallbackId();
+
+ /**
+ * If this method may call other NetworkCallback methods, an array of methods it calls.
+ *
+ * Only direct calls (not transitive calls) should be included. The IDs must be
+ * NetworkCallback.METHOD_* IDs.
+ */
+ int[] mayCall() default {};
+ }
+
+ /**
* No flags associated with this callback.
* @hide
*/
@@ -4058,6 +4126,7 @@
throw new IllegalArgumentException("Invalid flags");
}
mFlags = flags;
+ mConstructorWasCalled = true;
}
/**
@@ -4075,7 +4144,9 @@
*
* @hide
*/
+ @FilteredCallback(methodId = METHOD_ONPRECHECK, calledByCallbackId = CALLBACK_PRECHECK)
public void onPreCheck(@NonNull Network network) {}
+ private static final int METHOD_ONPRECHECK = 1;
/**
* Called when the framework connects and has declared a new network ready for use.
@@ -4090,6 +4161,11 @@
* @param blocked Whether access to the {@link Network} is blocked due to system policy.
* @hide
*/
+ @FilteredCallback(methodId = METHOD_ONAVAILABLE_5ARGS,
+ calledByCallbackId = CALLBACK_AVAILABLE,
+ mayCall = { METHOD_ONAVAILABLE_4ARGS,
+ METHOD_ONLOCALNETWORKINFOCHANGED,
+ METHOD_ONBLOCKEDSTATUSCHANGED_INT })
public final void onAvailable(@NonNull Network network,
@NonNull NetworkCapabilities networkCapabilities,
@NonNull LinkProperties linkProperties,
@@ -4102,6 +4178,7 @@
if (null != localInfo) onLocalNetworkInfoChanged(network, localInfo);
onBlockedStatusChanged(network, blocked);
}
+ private static final int METHOD_ONAVAILABLE_5ARGS = 2;
/**
* Legacy variant of onAvailable that takes a boolean blocked reason.
@@ -4114,6 +4191,13 @@
*
* @hide
*/
+ @FilteredCallback(methodId = METHOD_ONAVAILABLE_4ARGS,
+ calledByCallbackId = CALLBACK_TRANSITIVE_CALLS_ONLY,
+ mayCall = { METHOD_ONAVAILABLE_1ARG,
+ METHOD_ONNETWORKSUSPENDED,
+ METHOD_ONCAPABILITIESCHANGED,
+ METHOD_ONLINKPROPERTIESCHANGED
+ })
public void onAvailable(@NonNull Network network,
@NonNull NetworkCapabilities networkCapabilities,
@NonNull LinkProperties linkProperties,
@@ -4127,6 +4211,7 @@
onLinkPropertiesChanged(network, linkProperties);
// No call to onBlockedStatusChanged here. That is done by the caller.
}
+ private static final int METHOD_ONAVAILABLE_4ARGS = 3;
/**
* Called when the framework connects and has declared a new network ready for use.
@@ -4157,7 +4242,10 @@
*
* @param network The {@link Network} of the satisfying network.
*/
+ @FilteredCallback(methodId = METHOD_ONAVAILABLE_1ARG,
+ calledByCallbackId = CALLBACK_TRANSITIVE_CALLS_ONLY)
public void onAvailable(@NonNull Network network) {}
+ private static final int METHOD_ONAVAILABLE_1ARG = 4;
/**
* Called when the network is about to be lost, typically because there are no outstanding
@@ -4176,7 +4264,9 @@
* connected for graceful handover; note that the network may still
* suffer a hard loss at any time.
*/
+ @FilteredCallback(methodId = METHOD_ONLOSING, calledByCallbackId = CALLBACK_LOSING)
public void onLosing(@NonNull Network network, int maxMsToLive) {}
+ private static final int METHOD_ONLOSING = 5;
/**
* Called when a network disconnects or otherwise no longer satisfies this request or
@@ -4197,7 +4287,9 @@
*
* @param network The {@link Network} lost.
*/
+ @FilteredCallback(methodId = METHOD_ONLOST, calledByCallbackId = CALLBACK_LOST)
public void onLost(@NonNull Network network) {}
+ private static final int METHOD_ONLOST = 6;
/**
* Called if no network is found within the timeout time specified in
@@ -4207,7 +4299,9 @@
* {@link NetworkRequest} will have already been removed and released, as if
* {@link #unregisterNetworkCallback(NetworkCallback)} had been called.
*/
+ @FilteredCallback(methodId = METHOD_ONUNAVAILABLE, calledByCallbackId = CALLBACK_UNAVAIL)
public void onUnavailable() {}
+ private static final int METHOD_ONUNAVAILABLE = 7;
/**
* Called when the network corresponding to this request changes capabilities but still
@@ -4224,8 +4318,11 @@
* @param networkCapabilities The new {@link NetworkCapabilities} for this
* network.
*/
+ @FilteredCallback(methodId = METHOD_ONCAPABILITIESCHANGED,
+ calledByCallbackId = CALLBACK_CAP_CHANGED)
public void onCapabilitiesChanged(@NonNull Network network,
@NonNull NetworkCapabilities networkCapabilities) {}
+ private static final int METHOD_ONCAPABILITIESCHANGED = 8;
/**
* Called when the network corresponding to this request changes {@link LinkProperties}.
@@ -4240,8 +4337,11 @@
* @param network The {@link Network} whose link properties have changed.
* @param linkProperties The new {@link LinkProperties} for this network.
*/
+ @FilteredCallback(methodId = METHOD_ONLINKPROPERTIESCHANGED,
+ calledByCallbackId = CALLBACK_IP_CHANGED)
public void onLinkPropertiesChanged(@NonNull Network network,
@NonNull LinkProperties linkProperties) {}
+ private static final int METHOD_ONLINKPROPERTIESCHANGED = 9;
/**
* Called when there is a change in the {@link LocalNetworkInfo} for this network.
@@ -4253,8 +4353,11 @@
* @param localNetworkInfo the new {@link LocalNetworkInfo} for this network.
* @hide
*/
+ @FilteredCallback(methodId = METHOD_ONLOCALNETWORKINFOCHANGED,
+ calledByCallbackId = CALLBACK_LOCAL_NETWORK_INFO_CHANGED)
public void onLocalNetworkInfoChanged(@NonNull Network network,
@NonNull LocalNetworkInfo localNetworkInfo) {}
+ private static final int METHOD_ONLOCALNETWORKINFOCHANGED = 10;
/**
* Called when the network the framework connected to for this request suspends data
@@ -4273,7 +4376,10 @@
*
* @hide
*/
+ @FilteredCallback(methodId = METHOD_ONNETWORKSUSPENDED,
+ calledByCallbackId = CALLBACK_SUSPENDED)
public void onNetworkSuspended(@NonNull Network network) {}
+ private static final int METHOD_ONNETWORKSUSPENDED = 11;
/**
* Called when the network the framework connected to for this request
@@ -4287,7 +4393,9 @@
*
* @hide
*/
+ @FilteredCallback(methodId = METHOD_ONNETWORKRESUMED, calledByCallbackId = CALLBACK_RESUMED)
public void onNetworkResumed(@NonNull Network network) {}
+ private static final int METHOD_ONNETWORKRESUMED = 12;
/**
* Called when access to the specified network is blocked or unblocked.
@@ -4300,7 +4408,10 @@
* @param network The {@link Network} whose blocked status has changed.
* @param blocked The blocked status of this {@link Network}.
*/
+ @FilteredCallback(methodId = METHOD_ONBLOCKEDSTATUSCHANGED_BOOL,
+ calledByCallbackId = CALLBACK_TRANSITIVE_CALLS_ONLY)
public void onBlockedStatusChanged(@NonNull Network network, boolean blocked) {}
+ private static final int METHOD_ONBLOCKEDSTATUSCHANGED_BOOL = 13;
/**
* Called when access to the specified network is blocked or unblocked, or the reason for
@@ -4318,10 +4429,14 @@
* @param blocked The blocked status of this {@link Network}.
* @hide
*/
+ @FilteredCallback(methodId = METHOD_ONBLOCKEDSTATUSCHANGED_INT,
+ calledByCallbackId = CALLBACK_BLK_CHANGED,
+ mayCall = { METHOD_ONBLOCKEDSTATUSCHANGED_BOOL })
@SystemApi(client = MODULE_LIBRARIES)
public void onBlockedStatusChanged(@NonNull Network network, @BlockedReason int blocked) {
onBlockedStatusChanged(network, blocked != 0);
}
+ private static final int METHOD_ONBLOCKEDSTATUSCHANGED_INT = 14;
private NetworkRequest networkRequest;
private final int mFlags;
@@ -4349,6 +4464,7 @@
}
}
+ private static final int CALLBACK_TRANSITIVE_CALLS_ONLY = 0;
/** @hide */
public static final int CALLBACK_PRECHECK = 1;
/** @hide */
@@ -4374,9 +4490,11 @@
/** @hide */
public static final int CALLBACK_LOCAL_NETWORK_INFO_CHANGED = 12;
+
/** @hide */
public static String getCallbackName(int whichCallback) {
switch (whichCallback) {
+ case CALLBACK_TRANSITIVE_CALLS_ONLY: return "CALLBACK_TRANSITIVE_CALLS_ONLY";
case CALLBACK_PRECHECK: return "CALLBACK_PRECHECK";
case CALLBACK_AVAILABLE: return "CALLBACK_AVAILABLE";
case CALLBACK_LOSING: return "CALLBACK_LOSING";
@@ -4394,6 +4512,68 @@
}
}
+ /** @hide */
+ @VisibleForTesting
+ public static class NetworkCallbackMethod {
+ @NonNull
+ public final String mName;
+ @NonNull
+ public final Class<?>[] mParameterTypes;
+ // Bitmask of CALLBACK_* that may transitively call this method.
+ public final int mCallbacksCallingThisMethod;
+
+ public NetworkCallbackMethod(@NonNull String name, @NonNull Class<?>[] parameterTypes,
+ int callbacksCallingThisMethod) {
+ mName = name;
+ mParameterTypes = parameterTypes;
+ mCallbacksCallingThisMethod = callbacksCallingThisMethod;
+ }
+ }
+
+ // Holder class for the list of NetworkCallbackMethod. This ensures the list is only created
+ // once on first usage, and not just on ConnectivityManager class initialization.
+ /** @hide */
+ @VisibleForTesting
+ public static class NetworkCallbackMethodsHolder {
+ public static final NetworkCallbackMethod[] NETWORK_CB_METHODS =
+ new NetworkCallbackMethod[] {
+ method("onPreCheck", 1 << CALLBACK_PRECHECK, Network.class),
+ // Note the final overload of onAvailable is not included, since it cannot
+ // match any overridden method.
+ method("onAvailable", 1 << CALLBACK_AVAILABLE, Network.class),
+ method("onAvailable", 1 << CALLBACK_AVAILABLE,
+ Network.class, NetworkCapabilities.class,
+ LinkProperties.class, boolean.class),
+ method("onLosing", 1 << CALLBACK_LOSING, Network.class, int.class),
+ method("onLost", 1 << CALLBACK_LOST, Network.class),
+ method("onUnavailable", 1 << CALLBACK_UNAVAIL),
+ method("onCapabilitiesChanged",
+ 1 << CALLBACK_CAP_CHANGED | 1 << CALLBACK_AVAILABLE,
+ Network.class, NetworkCapabilities.class),
+ method("onLinkPropertiesChanged",
+ 1 << CALLBACK_IP_CHANGED | 1 << CALLBACK_AVAILABLE,
+ Network.class, LinkProperties.class),
+ method("onLocalNetworkInfoChanged",
+ 1 << CALLBACK_LOCAL_NETWORK_INFO_CHANGED | 1 << CALLBACK_AVAILABLE,
+ Network.class, LocalNetworkInfo.class),
+ method("onNetworkSuspended",
+ 1 << CALLBACK_SUSPENDED | 1 << CALLBACK_AVAILABLE, Network.class),
+ method("onNetworkResumed",
+ 1 << CALLBACK_RESUMED, Network.class),
+ method("onBlockedStatusChanged",
+ 1 << CALLBACK_BLK_CHANGED | 1 << CALLBACK_AVAILABLE,
+ Network.class, boolean.class),
+ method("onBlockedStatusChanged",
+ 1 << CALLBACK_BLK_CHANGED | 1 << CALLBACK_AVAILABLE,
+ Network.class, int.class),
+ };
+
+ private static NetworkCallbackMethod method(
+ String name, int callbacksCallingThisMethod, Class<?>... args) {
+ return new NetworkCallbackMethod(name, args, callbacksCallingThisMethod);
+ }
+ }
+
private static class CallbackHandler extends Handler {
private static final String TAG = "ConnectivityManager.CallbackHandler";
private static final boolean DBG = false;
@@ -4513,6 +4693,14 @@
if (reqType != TRACK_DEFAULT && reqType != TRACK_SYSTEM_DEFAULT && need == null) {
throw new IllegalArgumentException("null NetworkCapabilities");
}
+
+
+ final boolean useDeclaredMethods = isFeatureEnabled(
+ FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS);
+ // Set all bits if the feature is disabled
+ int declaredMethodsFlag = useDeclaredMethods
+ ? tryGetDeclaredMethodsFlag(callback)
+ : NetworkCallback.DECLARED_METHODS_ALL;
final NetworkRequest request;
final String callingPackageName = mContext.getOpPackageName();
try {
@@ -4529,11 +4717,12 @@
if (reqType == LISTEN) {
request = mService.listenForNetwork(
need, messenger, binder, callbackFlags, callingPackageName,
- getAttributionTag());
+ getAttributionTag(), declaredMethodsFlag);
} else {
request = mService.requestNetwork(
asUid, need, reqType.ordinal(), messenger, timeoutMs, binder,
- legacyType, callbackFlags, callingPackageName, getAttributionTag());
+ legacyType, callbackFlags, callingPackageName, getAttributionTag(),
+ declaredMethodsFlag);
}
if (request != null) {
sCallbacks.put(request, callback);
@@ -4548,6 +4737,108 @@
return request;
}
+ private int tryGetDeclaredMethodsFlag(@NonNull NetworkCallback cb) {
+ if (!cb.mConstructorWasCalled) {
+ // Do not use the optimization if the callback was created via reflection or mocking,
+ // as for example with dexmaker-mockito-inline methods will be instrumented without
+ // using subclasses. This does not catch all cases as it is still possible to call the
+ // constructor when creating mocks, but by default constructors are not called in that
+ // case.
+ return NetworkCallback.DECLARED_METHODS_ALL;
+ }
+ try {
+ return getDeclaredMethodsFlag(cb.getClass());
+ } catch (LinkageError e) {
+ // This may happen if some methods reference inaccessible classes in their arguments
+ // (for example b/261807130).
+ Log.w(TAG, "Could not get methods from NetworkCallback class", e);
+ // Fall through
+ } catch (Throwable e) {
+ // Log.wtf would be best but this is in app process, so the TerribleFailureHandler may
+ // have unknown effects, possibly crashing the app (default behavior on eng builds or
+ // if the WTF_IS_FATAL setting is set).
+ Log.e(TAG, "Unexpected error while getting methods from NetworkCallback class", e);
+ // Fall through
+ }
+ return NetworkCallback.DECLARED_METHODS_ALL;
+ }
+
+ private static int getDeclaredMethodsFlag(@NonNull Class<? extends NetworkCallback> clazz) {
+ final Integer cachedFlags = sMethodFlagsCache.get(clazz);
+ // As this is not synchronized, it is possible that this method will calculate the
+ // flags for a given class multiple times, but that is fine. LruCache itself is thread-safe.
+ if (cachedFlags != null) {
+ return cachedFlags;
+ }
+
+ int flag = 0;
+ // This uses getMethods instead of getDeclaredMethods, to make sure that if A overrides B
+ // that overrides NetworkCallback, A.getMethods also returns methods declared by B.
+ for (Method classMethod : clazz.getMethods()) {
+ final Class<?> declaringClass = classMethod.getDeclaringClass();
+ if (declaringClass == NetworkCallback.class) {
+ // The callback is as defined by NetworkCallback and not overridden
+ continue;
+ }
+ if (declaringClass == Object.class) {
+ // Optimization: no need to try to match callbacks for methods declared by Object
+ continue;
+ }
+ flag |= getCallbackIdsCallingThisMethod(classMethod);
+ }
+
+ if (flag == 0) {
+ // dexmaker-mockito-inline (InlineDexmakerMockMaker), for example for mockito-extended,
+ // modifies bytecode of classes in-place to add hooks instead of creating subclasses,
+ // which would not be detected. When no method is found, fall back to enabling callbacks
+ // for all methods.
+ // This will not catch the case where both NetworkCallback bytecode is modified and a
+ // subclass of NetworkCallback that has some overridden methods are used. But this kind
+ // of bytecode injection is only possible in debuggable processes, with a JVMTI debug
+ // agent attached, so it should not cause real issues.
+ // There may be legitimate cases where an empty callback is filed with no method
+ // overridden, for example requestNetwork(requestForCell, new NetworkCallback()) which
+ // would ensure that one cell network stays up. But there is no way to differentiate
+ // such NetworkCallbacks from a mock that called the constructor, so this code will
+ // register the callback with DECLARED_METHODS_ALL and turn off the optimization in that
+ // case. Apps are not expected to do this often anyway since the usefulness is very
+ // limited.
+ flag = NetworkCallback.DECLARED_METHODS_ALL;
+ }
+ sMethodFlagsCache.put(clazz, flag);
+ return flag;
+ }
+
+ /**
+ * Find out which of the base methods in NetworkCallback will call this method.
+ *
+ * For example, in the case of onLinkPropertiesChanged, this will be
+ * (1 << CALLBACK_IP_CHANGED) | (1 << CALLBACK_AVAILABLE).
+ */
+ private static int getCallbackIdsCallingThisMethod(@NonNull Method method) {
+ for (NetworkCallbackMethod baseMethod : NetworkCallbackMethodsHolder.NETWORK_CB_METHODS) {
+ if (!baseMethod.mName.equals(method.getName())) {
+ continue;
+ }
+ Class<?>[] methodParams = method.getParameterTypes();
+
+ // As per JLS 8.4.8.1., a method m1 must have a subsignature of method m2 to override
+ // it. And as per JLS 8.4.2, this means the erasure of the signature of m2 must be the
+ // same as the signature of m1. Since type erasure is done at compile time, with
+ // reflection the erased types are already observed, so the (erased) parameter types
+ // must be equal.
+ // So for example a method that is identical to a NetworkCallback method, except with
+ // one parameter being a subclass of the parameter in the original method, will never
+ // be called since it is not an override (the erasure of the arguments are not the same)
+ // Therefore, the method is an override only if methodParams is exactly equal to
+ // the base method's parameter types.
+ if (Arrays.equals(baseMethod.mParameterTypes, methodParams)) {
+ return baseMethod.mCallbacksCallingThisMethod;
+ }
+ }
+ return 0;
+ }
+
private boolean isFeatureEnabled(@ConnectivityManagerFeature long connectivityManagerFeature) {
synchronized (mEnabledConnectivityManagerFeaturesLock) {
if (mEnabledConnectivityManagerFeatures == null) {
diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl
index f9de8ed..988cc92 100644
--- a/framework/src/android/net/IConnectivityManager.aidl
+++ b/framework/src/android/net/IConnectivityManager.aidl
@@ -153,7 +153,8 @@
NetworkRequest requestNetwork(int uid, in NetworkCapabilities networkCapabilities, int reqType,
in Messenger messenger, int timeoutSec, in IBinder binder, int legacy,
- int callbackFlags, String callingPackageName, String callingAttributionTag);
+ int callbackFlags, String callingPackageName, String callingAttributionTag,
+ int declaredMethodsFlag);
NetworkRequest pendingRequestForNetwork(in NetworkCapabilities networkCapabilities,
in PendingIntent operation, String callingPackageName, String callingAttributionTag);
@@ -162,7 +163,7 @@
NetworkRequest listenForNetwork(in NetworkCapabilities networkCapabilities,
in Messenger messenger, in IBinder binder, int callbackFlags, String callingPackageName,
- String callingAttributionTag);
+ String callingAttributionTag, int declaredMethodsFlag);
void pendingListenForNetwork(in NetworkCapabilities networkCapabilities,
in PendingIntent operation, String callingPackageName,
diff --git a/netbpfload/NetBpfLoad.cpp b/netbpfload/NetBpfLoad.cpp
index e9c6d8a..0d4a5c4 100644
--- a/netbpfload/NetBpfLoad.cpp
+++ b/netbpfload/NetBpfLoad.cpp
@@ -57,6 +57,7 @@
using base::StartsWith;
using base::EndsWith;
using std::string;
+using std::vector;
static bool exists(const char* const path) {
int v = access(path, F_OK);
@@ -248,6 +249,22 @@
return tv;
}
+static bool isWear() {
+ static string wearSdkStr = base::GetProperty("ro.cw_build.wear_sdk.version", "");
+ static int wearSdkInt = base::GetIntProperty("ro.cw_build.wear_sdk.version", 0);
+ static string buildChars = base::GetProperty("ro.build.characteristics", "");
+ static vector<string> v = base::Tokenize(buildChars, ",");
+ static bool watch = (std::find(v.begin(), v.end(), "watch") != v.end());
+ static bool wear = (wearSdkInt > 0) || watch;
+ static bool logged = false;
+ if (!logged) {
+ logged = true;
+ ALOGI("isWear(ro.cw_build.wear_sdk.version=%d[%s] ro.build.characteristics='%s'): %s",
+ wearSdkInt, wearSdkStr.c_str(), buildChars.c_str(), wear ? "true" : "false");
+ }
+ return wear;
+}
+
static int doLoad(char** argv, char * const envp[]) {
const bool runningAsRoot = !getuid(); // true iff U QPR3 or V+
@@ -402,7 +419,8 @@
* and 32-bit userspace on 64-bit kernel bpf ringbuffer compatibility is broken.
*/
ALOGE("64-bit userspace required on 6.2+ kernels.");
- if (!isTV()) return 1;
+ // Stuff won't work reliably, but exempt TVs & Arm Wear devices
+ if (!isTV() && !(isWear() && isArm())) return 1;
}
// Ensure we can determine the Android build type.
diff --git a/netd/BpfHandler.cpp b/netd/BpfHandler.cpp
index 4779b47..9682545 100644
--- a/netd/BpfHandler.cpp
+++ b/netd/BpfHandler.cpp
@@ -260,7 +260,10 @@
}
Status BpfHandler::initMaps() {
- mapLockTest();
+ // bpfLock() requires bpfGetFdMapId which is only available on 4.14+ kernels.
+ if (bpf::isAtLeastKernelVersion(4, 14, 0)) {
+ mapLockTest();
+ }
RETURN_IF_NOT_OK(mStatsMapA.init(STATS_MAP_A_PATH));
RETURN_IF_NOT_OK(mStatsMapB.init(STATS_MAP_B_PATH));
diff --git a/service/lint-baseline.xml b/service/lint-baseline.xml
index 3e11d52..b09589c 100644
--- a/service/lint-baseline.xml
+++ b/service/lint-baseline.xml
@@ -3,17 +3,6 @@
<issue
id="NewApi"
- message="Call requires API level 33 (current min is 30): `getUidRule`"
- errorLine1=" return BpfNetMapsReader.getUidRule(sUidOwnerMap, childChain, uid);"
- errorLine2=" ~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/BpfNetMaps.java"
- line="643"
- column="33"/>
- </issue>
-
- <issue
- id="NewApi"
message="Call requires API level 31 (current min is 30): `BpfBitmap`"
errorLine1=" return new BpfBitmap(BLOCKED_PORTS_MAP_PATH);"
errorLine2=" ~~~~~~~~~~~~~">
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index b3e7d8c..44868b2d 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -580,6 +580,7 @@
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*/
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
public int getUidRule(final int childChain, final int uid) {
return BpfNetMapsUtils.getUidRule(sUidOwnerMap, childChain, uid);
}
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 3dee305..310db9a 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -46,6 +46,7 @@
import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
import static android.net.ConnectivityManager.FIREWALL_RULE_DEFAULT;
import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
+import static android.net.ConnectivityManager.NetworkCallback.DECLARED_METHODS_NONE;
import static android.net.ConnectivityManager.TYPE_BLUETOOTH;
import static android.net.ConnectivityManager.TYPE_ETHERNET;
import static android.net.ConnectivityManager.TYPE_MOBILE;
@@ -65,6 +66,7 @@
import static android.net.ConnectivityManager.TYPE_WIFI_P2P;
import static android.net.ConnectivityManager.getNetworkTypeName;
import static android.net.ConnectivityManager.isNetworkTypeValid;
+import static android.net.ConnectivityManager.NetworkCallback.DECLARED_METHODS_ALL;
import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
import static android.net.INetd.PERMISSION_INTERNET;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_PRIVDNS;
@@ -136,8 +138,6 @@
import static com.android.server.connectivity.ConnectivityFlags.REQUEST_RESTRICTED_WIFI;
import static com.android.server.connectivity.ConnectivityFlags.INGRESS_TO_VPN_ADDRESS_FILTERING;
-import static java.util.Map.Entry;
-
import android.Manifest;
import android.annotation.CheckResult;
import android.annotation.NonNull;
@@ -1788,7 +1788,7 @@
mDefaultRequest = new NetworkRequestInfo(
Process.myUid(), defaultInternetRequest, null,
null /* binder */, NetworkCallback.FLAG_INCLUDE_LOCATION_INFO,
- null /* attributionTags */);
+ null /* attributionTags */, DECLARED_METHODS_NONE);
mNetworkRequests.put(defaultInternetRequest, mDefaultRequest);
mDefaultNetworkRequests.add(mDefaultRequest);
mNetworkRequestInfoLogs.log("REGISTER " + mDefaultRequest);
@@ -2175,7 +2175,7 @@
handleRegisterNetworkRequest(new NetworkRequestInfo(
Process.myUid(), networkRequest, null /* messenger */, null /* binder */,
NetworkCallback.FLAG_INCLUDE_LOCATION_INFO,
- null /* attributionTags */));
+ null /* attributionTags */, DECLARED_METHODS_NONE));
} else {
handleReleaseNetworkRequest(networkRequest, Process.SYSTEM_UID,
/* callOnUnavailable */ false);
@@ -7577,6 +7577,8 @@
// Preference order of this request.
final int mPreferenceOrder;
+ final int mDeclaredMethodsFlags;
+
// In order to preserve the mapping of NetworkRequest-to-callback when apps register
// callbacks using a returned NetworkRequest, the original NetworkRequest needs to be
// maintained for keying off of. This is only a concern when the original nri
@@ -7630,21 +7632,22 @@
mCallbackFlags = NetworkCallback.FLAG_NONE;
mCallingAttributionTag = callingAttributionTag;
mPreferenceOrder = preferenceOrder;
+ mDeclaredMethodsFlags = DECLARED_METHODS_NONE;
}
NetworkRequestInfo(int asUid, @NonNull final NetworkRequest r, @Nullable final Messenger m,
@Nullable final IBinder binder,
@NetworkCallback.Flag int callbackFlags,
- @Nullable String callingAttributionTag) {
+ @Nullable String callingAttributionTag, int declaredMethodsFlags) {
this(asUid, Collections.singletonList(r), r, m, binder, callbackFlags,
- callingAttributionTag);
+ callingAttributionTag, declaredMethodsFlags);
}
NetworkRequestInfo(int asUid, @NonNull final List<NetworkRequest> r,
@NonNull final NetworkRequest requestForCallback, @Nullable final Messenger m,
@Nullable final IBinder binder,
@NetworkCallback.Flag int callbackFlags,
- @Nullable String callingAttributionTag) {
+ @Nullable String callingAttributionTag, int declaredMethodsFlags) {
super();
ensureAllNetworkRequestsHaveType(r);
mRequests = initializeRequests(r);
@@ -7659,6 +7662,7 @@
mCallbackFlags = callbackFlags;
mCallingAttributionTag = callingAttributionTag;
mPreferenceOrder = PREFERENCE_ORDER_INVALID;
+ mDeclaredMethodsFlags = declaredMethodsFlags;
linkDeathRecipient();
}
@@ -7699,6 +7703,7 @@
mCallingAttributionTag = nri.mCallingAttributionTag;
mUidTrackedForBlockedStatus = nri.mUidTrackedForBlockedStatus;
mPreferenceOrder = PREFERENCE_ORDER_INVALID;
+ mDeclaredMethodsFlags = nri.mDeclaredMethodsFlags;
linkDeathRecipient();
}
@@ -7786,7 +7791,8 @@
+ (mPendingIntent == null ? "" : " to trigger " + mPendingIntent)
+ " callback flags: " + mCallbackFlags
+ " order: " + mPreferenceOrder
- + " isUidTracked: " + mUidTrackedForBlockedStatus;
+ + " isUidTracked: " + mUidTrackedForBlockedStatus
+ + " declaredMethods: 0x" + Integer.toHexString(mDeclaredMethodsFlags);
}
}
@@ -7924,7 +7930,21 @@
public NetworkRequest requestNetwork(int asUid, NetworkCapabilities networkCapabilities,
int reqTypeInt, Messenger messenger, int timeoutMs, final IBinder binder,
int legacyType, int callbackFlags, @NonNull String callingPackageName,
- @Nullable String callingAttributionTag) {
+ @Nullable String callingAttributionTag, int declaredMethodsFlag) {
+ if (declaredMethodsFlag == 0) {
+ // This could happen if raw binder calls are used to call the previous overload of
+ // requestNetwork, as missing int arguments in a binder call end up as 0
+ // (Parcel.readInt returns 0 at the end of a parcel). Such raw calls this would be
+ // really unexpected bad behavior from the caller though.
+ // TODO: remove after verifying this does not happen. This could allow enabling the
+ // optimization for callbacks that do not override any method (right now they use
+ // DECLARED_METHODS_ALL), if it is OK to break NetworkCallbacks created using
+ // dexmaker-mockito-inline and either spy() or MockSettings.useConstructor (see
+ // comment in ConnectivityManager which sets the flag to DECLARED_METHODS_ALL).
+ Log.wtf(TAG, "requestNetwork called without declaredMethodsFlag from "
+ + callingPackageName);
+ declaredMethodsFlag = DECLARED_METHODS_ALL;
+ }
if (legacyType != TYPE_NONE && !hasNetworkStackPermission()) {
if (isTargetSdkAtleast(Build.VERSION_CODES.M, mDeps.getCallingUid(),
callingPackageName)) {
@@ -8016,7 +8036,7 @@
nextNetworkRequestId(), reqType);
final NetworkRequestInfo nri = getNriToRegister(
asUid, networkRequest, messenger, binder, callbackFlags,
- callingAttributionTag);
+ callingAttributionTag, declaredMethodsFlag);
if (DBG) log("requestNetwork for " + nri);
trackUidAndRegisterNetworkRequest(EVENT_REGISTER_NETWORK_REQUEST, nri);
if (timeoutMs > 0) {
@@ -8042,7 +8062,7 @@
private NetworkRequestInfo getNriToRegister(final int asUid, @NonNull final NetworkRequest nr,
@Nullable final Messenger msgr, @Nullable final IBinder binder,
@NetworkCallback.Flag int callbackFlags,
- @Nullable String callingAttributionTag) {
+ @Nullable String callingAttributionTag, int declaredMethodsFlags) {
final List<NetworkRequest> requests;
if (NetworkRequest.Type.TRACK_DEFAULT == nr.type) {
requests = copyDefaultNetworkRequestsForUid(
@@ -8051,7 +8071,8 @@
requests = Collections.singletonList(nr);
}
return new NetworkRequestInfo(
- asUid, requests, nr, msgr, binder, callbackFlags, callingAttributionTag);
+ asUid, requests, nr, msgr, binder, callbackFlags, callingAttributionTag,
+ declaredMethodsFlags);
}
private boolean shouldCheckCapabilitiesDeclaration(
@@ -8188,21 +8209,13 @@
// Policy already enforced.
return;
}
- if (mDeps.isAtLeastV()) {
- if (mBpfNetMaps.isUidRestrictedOnMeteredNetworks(uid)) {
- // If UID is restricted, don't allow them to bring up metered APNs.
- networkCapabilities.addCapability(NET_CAPABILITY_NOT_METERED);
- }
- return;
- }
- final long ident = Binder.clearCallingIdentity();
- try {
- if (mPolicyManager.isUidRestrictedOnMeteredNetworks(uid)) {
- // If UID is restricted, don't allow them to bring up metered APNs.
- networkCapabilities.addCapability(NET_CAPABILITY_NOT_METERED);
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
+ final boolean isRestrictedOnMeteredNetworks = mDeps.isAtLeastV()
+ ? mBpfNetMaps.isUidRestrictedOnMeteredNetworks(uid)
+ : BinderUtils.withCleanCallingIdentity(() ->
+ mPolicyManager.isUidRestrictedOnMeteredNetworks(uid));
+ if (isRestrictedOnMeteredNetworks) {
+ // If UID is restricted, don't allow them to bring up metered APNs.
+ networkCapabilities.addCapability(NET_CAPABILITY_NOT_METERED);
}
}
@@ -8341,7 +8354,13 @@
public NetworkRequest listenForNetwork(NetworkCapabilities networkCapabilities,
Messenger messenger, IBinder binder,
@NetworkCallback.Flag int callbackFlags,
- @NonNull String callingPackageName, @NonNull String callingAttributionTag) {
+ @NonNull String callingPackageName, @NonNull String callingAttributionTag,
+ int declaredMethodsFlag) {
+ if (declaredMethodsFlag == 0) {
+ Log.wtf(TAG, "listenForNetwork called without declaredMethodsFlag from "
+ + callingPackageName);
+ declaredMethodsFlag = DECLARED_METHODS_ALL;
+ }
final int callingUid = mDeps.getCallingUid();
if (!hasWifiNetworkListenPermission(networkCapabilities)) {
enforceAccessPermission();
@@ -8363,7 +8382,7 @@
NetworkRequest.Type.LISTEN);
NetworkRequestInfo nri =
new NetworkRequestInfo(callingUid, networkRequest, messenger, binder, callbackFlags,
- callingAttributionTag);
+ callingAttributionTag, declaredMethodsFlag);
if (VDBG) log("listenForNetwork for " + nri);
trackUidAndRegisterNetworkRequest(EVENT_REGISTER_NETWORK_LISTENER, nri);
@@ -9805,7 +9824,8 @@
configBuilder.setUpstreamSelector(nr);
final NetworkRequestInfo nri = new NetworkRequestInfo(
nai.creatorUid, nr, null /* messenger */, null /* binder */,
- 0 /* callbackFlags */, null /* attributionTag */);
+ 0 /* callbackFlags */, null /* attributionTag */,
+ DECLARED_METHODS_NONE);
if (null != oldSatisfier) {
// Set the old satisfier in the new NRI so that the rematch will see any changes
nri.setSatisfier(oldSatisfier, nr);
@@ -10176,6 +10196,11 @@
// are Type.LISTEN, but should not have NetworkCallbacks invoked.
return;
}
+ if (mUseDeclaredMethodsForCallbacksEnabled
+ && (nri.mDeclaredMethodsFlags & (1 << notificationType)) == 0) {
+ // No need to send the notification as the recipient method is not overridden
+ return;
+ }
final Bundle bundle = new Bundle();
// TODO b/177608132: make sure callbacks are indexed by NRIs and not NetworkRequest objects.
// TODO: check if defensive copies of data is needed.
@@ -11800,6 +11825,10 @@
return 0;
}
case "get-package-networking-enabled": {
+ if (!mDeps.isAtLeastT()) {
+ throw new UnsupportedOperationException(
+ "This command is not supported on T-");
+ }
final String packageName = getNextArg();
final int rule = getPackageFirewallRule(
ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_3, packageName);
@@ -11829,6 +11858,10 @@
return 0;
}
case "get-background-networking-enabled-for-uid": {
+ if (!mDeps.isAtLeastT()) {
+ throw new UnsupportedOperationException(
+ "This command is not supported on T-");
+ }
final Integer uid = parseIntegerArgument(getNextArg());
if (null == uid) {
onHelp();
@@ -13877,6 +13910,7 @@
}
}
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
private int getPackageFirewallRule(final int chain, final String packageName)
throws PackageManager.NameNotFoundException {
final PackageManager pm = mContext.getPackageManager();
@@ -13884,6 +13918,7 @@
return getUidFirewallRule(chain, appId);
}
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
@Override
public int getUidFirewallRule(final int chain, final int uid) {
enforceNetworkStackOrSettingsPermission();
diff --git a/service/src/com/android/server/connectivity/ClatCoordinator.java b/service/src/com/android/server/connectivity/ClatCoordinator.java
index b1c770b..e808746 100644
--- a/service/src/com/android/server/connectivity/ClatCoordinator.java
+++ b/service/src/com/android/server/connectivity/ClatCoordinator.java
@@ -45,7 +45,6 @@
import com.android.net.module.util.BpfMap;
import com.android.net.module.util.IBpfMap;
import com.android.net.module.util.InterfaceParams;
-import com.android.net.module.util.SingleWriterBpfMap;
import com.android.net.module.util.TcUtils;
import com.android.net.module.util.bpf.ClatEgress4Key;
import com.android.net.module.util.bpf.ClatEgress4Value;
@@ -257,7 +256,8 @@
@Nullable
public IBpfMap<ClatIngress6Key, ClatIngress6Value> getBpfIngress6Map() {
try {
- return SingleWriterBpfMap.getSingleton(CLAT_INGRESS6_MAP_PATH,
+ // written from clatd.c
+ return new BpfMap<>(CLAT_INGRESS6_MAP_PATH,
ClatIngress6Key.class, ClatIngress6Value.class);
} catch (ErrnoException e) {
Log.e(TAG, "Cannot create ingress6 map: " + e);
@@ -269,7 +269,8 @@
@Nullable
public IBpfMap<ClatEgress4Key, ClatEgress4Value> getBpfEgress4Map() {
try {
- return SingleWriterBpfMap.getSingleton(CLAT_EGRESS4_MAP_PATH,
+ // written from clatd.c
+ return new BpfMap<>(CLAT_EGRESS4_MAP_PATH,
ClatEgress4Key.class, ClatEgress4Value.class);
} catch (ErrnoException e) {
Log.e(TAG, "Cannot create egress4 map: " + e);
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index 71f388d..34ea9ab 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -282,7 +282,7 @@
"//apex_available:platform",
],
lint: {
- baseline_filename: "lint-baseline.xml",
+ strict_updatability_linting: true,
error_checks: ["NewApi"],
},
}
diff --git a/staticlibs/device/com/android/net/module/util/SocketUtils.java b/staticlibs/device/com/android/net/module/util/SocketUtils.java
index 5e6a6c6..51671a6 100644
--- a/staticlibs/device/com/android/net/module/util/SocketUtils.java
+++ b/staticlibs/device/com/android/net/module/util/SocketUtils.java
@@ -19,8 +19,7 @@
import static android.net.util.SocketUtils.closeSocket;
import android.annotation.NonNull;
-import android.annotation.RequiresApi;
-import android.os.Build;
+import android.annotation.SuppressLint;
import android.system.NetlinkSocketAddress;
import java.io.FileDescriptor;
@@ -41,7 +40,11 @@
/**
* Make a socket address to communicate with netlink.
*/
- @NonNull @RequiresApi(Build.VERSION_CODES.S)
+ // NetlinkSocketAddress was CorePlatformApi on R and linter warns this is available on S+.
+ // android.net.util.SocketUtils.makeNetlinkSocketAddress can be used instead, but this method
+ // has been used on R, so suppress the linter and keep as it is.
+ @SuppressLint("NewApi")
+ @NonNull
public static SocketAddress makeNetlinkSocketAddress(int portId, int groupsMask) {
return new NetlinkSocketAddress(portId, groupsMask);
}
diff --git a/staticlibs/lint-baseline.xml b/staticlibs/lint-baseline.xml
deleted file mode 100644
index 2ee3a43..0000000
--- a/staticlibs/lint-baseline.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.4.0-alpha04" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha04">
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `makeNetlinkSocketAddress`"
- errorLine1=" Os.bind(fd, makeNetlinkSocketAddress(0, mBindGroups));"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/staticlibs/device/com/android/net/module/util/ip/NetlinkMonitor.java"
- line="111"
- column="25"/>
- </issue>
-
-</issues>
diff --git a/staticlibs/tests/unit/Android.bp b/staticlibs/tests/unit/Android.bp
index 5d537ff..e9f8858 100644
--- a/staticlibs/tests/unit/Android.bp
+++ b/staticlibs/tests/unit/Android.bp
@@ -39,6 +39,7 @@
"//packages/modules/NetworkStack/tests/integration",
],
lint: {
+ strict_updatability_linting: true,
test: true,
},
}
@@ -56,6 +57,9 @@
],
jarjar_rules: "jarjar-rules.txt",
test_suites: ["device-tests"],
+ lint: {
+ strict_updatability_linting: true,
+ },
}
python_test_host {
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt b/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt
index 69fdbf8..8687ac7 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt
@@ -90,25 +90,10 @@
Modifier.isStatic(it.modifiers) &&
it.isAnnotationPresent(Parameterized.Parameters::class.java) }
- override fun run(notifier: RunNotifier) {
- if (baseRunner == null) {
- // Report a single, skipped placeholder test for this class, as the class is expected to
- // report results when run. In practice runners that apply the Filterable implementation
- // would see a NoTestsRemainException and not call the run method.
- notifier.fireTestIgnored(
- Description.createTestDescription(klass, "skippedClassForDevSdkMismatch"))
- return
- }
- if (!shouldThreadLeakFailTest) {
- baseRunner.run(notifier)
- return
- }
-
- // Dump threads as a baseline to monitor thread leaks.
- val threadCountsBeforeTest = getAllThreadNameCounts()
-
- baseRunner.run(notifier)
-
+ private fun checkThreadLeak(
+ notifier: RunNotifier,
+ threadCountsBeforeTest: Map<String, Int>
+ ) {
notifier.fireTestStarted(leakMonitorDesc)
val threadCountsAfterTest = getAllThreadNameCounts()
// TODO : move CompareOrUpdateResult to its own util instead of LinkProperties.
@@ -122,13 +107,39 @@
val increasedThreads = threadsDiff.updated
.filter { threadCountsBeforeTest[it.key]!! < it.value }
if (threadsDiff.added.isNotEmpty() || increasedThreads.isNotEmpty()) {
- notifier.fireTestFailure(Failure(leakMonitorDesc,
- IllegalStateException("Unexpected thread changes: $threadsDiff")))
+ notifier.fireTestFailure(Failure(
+ leakMonitorDesc,
+ IllegalStateException("Unexpected thread changes: $threadsDiff")
+ ))
+ }
+ notifier.fireTestFinished(leakMonitorDesc)
+ }
+
+ override fun run(notifier: RunNotifier) {
+ if (baseRunner == null) {
+ // Report a single, skipped placeholder test for this class, as the class is expected to
+ // report results when run. In practice runners that apply the Filterable implementation
+ // would see a NoTestsRemainException and not call the run method.
+ notifier.fireTestIgnored(
+ Description.createTestDescription(klass, "skippedClassForDevSdkMismatch")
+ )
+ return
+ }
+ val threadCountsBeforeTest = if (shouldThreadLeakFailTest) {
+ // Dump threads as a baseline to monitor thread leaks.
+ getAllThreadNameCounts()
+ } else {
+ null
+ }
+
+ baseRunner.run(notifier)
+
+ if (threadCountsBeforeTest != null) {
+ checkThreadLeak(notifier, threadCountsBeforeTest)
}
// Clears up internal state of all inline mocks.
// TODO: Call clearInlineMocks() at the end of each test.
Mockito.framework().clearInlineMocks()
- notifier.fireTestFinished(leakMonitorDesc)
}
private fun getAllThreadNameCounts(): Map<String, Int> {
diff --git a/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt b/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
index 3563f2c..5662fca 100644
--- a/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
+++ b/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
@@ -27,9 +27,20 @@
import android.net.NetworkRequest
import android.net.apf.ApfCapabilities
import android.net.apf.ApfConstants.ETH_ETHERTYPE_OFFSET
+import android.net.apf.ApfConstants.ETH_HEADER_LEN
+import android.net.apf.ApfConstants.ICMP6_CHECKSUM_OFFSET
import android.net.apf.ApfConstants.ICMP6_TYPE_OFFSET
+import android.net.apf.ApfConstants.IPV6_DEST_ADDR_OFFSET
+import android.net.apf.ApfConstants.IPV6_HEADER_LEN
import android.net.apf.ApfConstants.IPV6_NEXT_HEADER_OFFSET
+import android.net.apf.ApfConstants.IPV6_SRC_ADDR_OFFSET
+import android.net.apf.ApfCounterTracker
+import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_MULTICAST_PING
+import android.net.apf.ApfCounterTracker.Counter.FILTER_AGE_16384THS
+import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_ICMP
import android.net.apf.ApfV4Generator
+import android.net.apf.ApfV4GeneratorBase
+import android.net.apf.ApfV6Generator
import android.net.apf.BaseApfGenerator
import android.net.apf.BaseApfGenerator.MemorySlot
import android.net.apf.BaseApfGenerator.Register.R0
@@ -57,6 +68,12 @@
import com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow
import com.android.compatibility.common.util.VsrTest
import com.android.internal.util.HexDump
+import com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN
+import com.android.net.module.util.NetworkStackConstants.ETHER_DST_ADDR_OFFSET
+import com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN
+import com.android.net.module.util.NetworkStackConstants.ETHER_SRC_ADDR_OFFSET
+import com.android.net.module.util.NetworkStackConstants.ICMPV6_HEADER_MIN_LEN
+import com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_LEN
import com.android.net.module.util.PacketReader
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
@@ -73,7 +90,6 @@
import com.google.common.truth.Truth.assertWithMessage
import com.google.common.truth.TruthJUnit.assume
import java.io.FileDescriptor
-import java.lang.Thread
import java.net.InetSocketAddress
import java.nio.ByteBuffer
import java.util.concurrent.CompletableFuture
@@ -213,8 +229,8 @@
Os.sendto(sockFd!!, packet, 0, packet.size, 0, PING_DESTINATION)
}
- fun expectPingReply(): ByteArray {
- return futureReply!!.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+ fun expectPingReply(timeoutMs: Long = TIMEOUT_MS): ByteArray {
+ return futureReply!!.get(timeoutMs, TimeUnit.MILLISECONDS)
}
fun expectPingDropped() {
@@ -394,7 +410,7 @@
}
}
- fun ApfV4Generator.addPassIfNotIcmpv6EchoReply() {
+ fun ApfV4GeneratorBase<*>.addPassIfNotIcmpv6EchoReply() {
// If not IPv6 -> PASS
addLoad16(R0, ETH_ETHERTYPE_OFFSET)
addJumpIfR0NotEquals(ETH_P_IPV6.toLong(), BaseApfGenerator.PASS_LABEL)
@@ -573,4 +589,149 @@
val timeDiff = filterAgeSeconds - filterAgeSecondsOrig
assertThat(timeDiff).isAnyOf(5, 6)
}
+
+ @VsrTest(requirements = ["VSR-5.3.12-002", "VSR-5.3.12-005"])
+ @Test
+ fun testFilterAge16384thsIncreasesBetweenPackets() {
+ assumeApfVersionSupportAtLeast(6000)
+ clearApfMemory()
+ val gen = ApfV6Generator(
+ caps.apfVersionSupported,
+ caps.maximumApfProgramSize,
+ caps.maximumApfProgramSize
+ )
+
+ // If not ICMPv6 Echo Reply -> PASS
+ gen.addPassIfNotIcmpv6EchoReply()
+
+ // Store all prefilled memory slots in counter region [500, 520)
+ gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_16384THS)
+ gen.addStoreCounter(FILTER_AGE_16384THS, R0)
+
+ installProgram(gen.generate())
+ readProgram() // wait for install completion
+
+ val payloadSize = 56
+ val data = ByteArray(payloadSize).also { Random.nextBytes(it) }
+ packetReader.sendPing(data, payloadSize)
+ packetReader.expectPingReply()
+
+ var apfRam = readProgram()
+ val filterAge16384thSecondsOrig =
+ ApfCounterTracker.getCounterValue(apfRam, FILTER_AGE_16384THS)
+
+ Thread.sleep(5000)
+
+ packetReader.sendPing(data, payloadSize)
+ packetReader.expectPingReply()
+
+ apfRam = readProgram()
+ val filterAge16384thSeconds = ApfCounterTracker.getCounterValue(apfRam, FILTER_AGE_16384THS)
+ val timeDiff = (filterAge16384thSeconds - filterAge16384thSecondsOrig)
+ // Expect the HAL plus ping latency to be less than 800ms.
+ val timeDiffLowerBound = (4.99 * 16384).toInt()
+ val timeDiffUpperBound = (5.81 * 16384).toInt()
+ // Assert that filter age has increased, but not too much.
+ assertThat(timeDiff).isGreaterThan(timeDiffLowerBound)
+ assertThat(timeDiff).isLessThan(timeDiffUpperBound)
+ }
+
+ @VsrTest(
+ requirements = ["VSR-5.3.12-002", "VSR-5.3.12-005", "VSR-5.3.12-012", "VSR-5.3.12-013",
+ "VSR-5.3.12-014", "VSR-5.3.12-015", "VSR-5.3.12-016", "VSR-5.3.12-017"]
+ )
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testReplyPing() {
+ assumeApfVersionSupportAtLeast(6000)
+ installProgram(ByteArray(caps.maximumApfProgramSize) { 0 }) // Clear previous program
+ readProgram() // Ensure installation is complete
+
+ val payloadSize = 56
+ val payload = ByteArray(payloadSize).also { Random.nextBytes(it) }
+ val firstByte = payload.take(1).toByteArray()
+
+ val pingRequestIpv6PayloadLen = PING_HEADER_LENGTH + 1
+ val pingRequestPktLen = ETH_HEADER_LEN + IPV6_HEADER_LEN + pingRequestIpv6PayloadLen
+
+ val gen = ApfV6Generator(
+ caps.apfVersionSupported,
+ caps.maximumApfProgramSize,
+ caps.maximumApfProgramSize
+ )
+ val skipPacketLabel = gen.uniqueLabel
+
+ // Summary of the program:
+ // if the packet is not ICMPv6 echo reply
+ // pass
+ // else if the echo reply payload size is 1
+ // increase PASSED_IPV6_ICMP counter
+ // pass
+ // else
+ // transmit a ICMPv6 echo request packet with the first byte of the payload in the reply
+ // increase DROPPED_IPV6_MULTICAST_PING counter
+ // drop
+ val program = gen
+ .addLoad16(R0, ETH_ETHERTYPE_OFFSET)
+ .addJumpIfR0NotEquals(ETH_P_IPV6.toLong(), skipPacketLabel)
+ .addLoad8(R0, IPV6_NEXT_HEADER_OFFSET)
+ .addJumpIfR0NotEquals(IPPROTO_ICMPV6.toLong(), skipPacketLabel)
+ .addLoad8(R0, ICMP6_TYPE_OFFSET)
+ .addJumpIfR0NotEquals(0x81, skipPacketLabel) // Echo reply type
+ .addLoadFromMemory(R0, MemorySlot.PACKET_SIZE)
+ .addCountAndPassIfR0Equals(
+ (ETHER_HEADER_LEN + IPV6_HEADER_LEN + PING_HEADER_LENGTH + firstByte.size)
+ .toLong(),
+ PASSED_IPV6_ICMP
+ )
+ // Ping Packet Generation
+ .addAllocate(pingRequestPktLen)
+ // Eth header
+ .addPacketCopy(ETHER_SRC_ADDR_OFFSET, ETHER_ADDR_LEN) // dst MAC address
+ .addPacketCopy(ETHER_DST_ADDR_OFFSET, ETHER_ADDR_LEN) // src MAC address
+ .addWriteU16(ETH_P_IPV6) // IPv6 type
+ // IPv6 Header
+ .addWrite32(0x60000000) // IPv6 Header: version, traffic class, flowlabel
+ // payload length (2 bytes) | next header: ICMPv6 (1 byte) | hop limit (1 byte)
+ .addWrite32(pingRequestIpv6PayloadLen shl 16 or (IPPROTO_ICMPV6 shl 8 or 64))
+ .addPacketCopy(IPV6_DEST_ADDR_OFFSET, IPV6_ADDR_LEN) // src ip
+ .addPacketCopy(IPV6_SRC_ADDR_OFFSET, IPV6_ADDR_LEN) // dst ip
+ // ICMPv6
+ .addWriteU8(0x80) // type: echo request
+ .addWriteU8(0) // code
+ .addWriteU16(pingRequestIpv6PayloadLen) // checksum
+ // identifier
+ .addPacketCopy(ETHER_HEADER_LEN + IPV6_HEADER_LEN + ICMPV6_HEADER_MIN_LEN, 2)
+ .addWriteU16(0) // sequence number
+ .addDataCopy(firstByte) // data
+ .addTransmitL4(
+ ETHER_HEADER_LEN, // ip_ofs
+ ICMP6_CHECKSUM_OFFSET, // csum_ofs
+ IPV6_SRC_ADDR_OFFSET, // csum_start
+ IPPROTO_ICMPV6, // partial_sum
+ false // udp
+ )
+ // Warning: the program abuse DROPPED_IPV6_MULTICAST_PING for debugging purpose
+ .addCountAndDrop(DROPPED_IPV6_MULTICAST_PING)
+ .defineLabel(skipPacketLabel)
+ .addPass()
+ .generate()
+
+ installProgram(program)
+ readProgram() // Ensure installation is complete
+
+ packetReader.sendPing(payload, payloadSize)
+
+ val replyPayload = try {
+ packetReader.expectPingReply(TIMEOUT_MS * 2)
+ } catch (e: TimeoutException) {
+ byteArrayOf() // Empty payload if timeout occurs
+ }
+
+ val apfCounterTracker = ApfCounterTracker()
+ apfCounterTracker.updateCountersFromData(readProgram())
+ Log.i(TAG, "counter map: ${apfCounterTracker.counters}")
+
+ assertThat(replyPayload).isEqualTo(firstByte)
+ }
}
diff --git a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
index 81608f7..8794847 100644
--- a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
+++ b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
@@ -235,6 +235,12 @@
assertEquals(TETHERING_USB, tr2.getTetheringType());
assertTrue(tr2.isExemptFromEntitlementCheck());
assertFalse(tr2.getShouldShowEntitlementUi());
+
+ final TetheringRequest tr3 = new TetheringRequest.Builder(TETHERING_USB)
+ .setStaticIpv4Addresses(localAddr, clientAddr)
+ .setExemptFromEntitlementCheck(true)
+ .setShouldShowEntitlementUi(false).build();
+ assertEquals(tr2, tr3);
}
@Test
@@ -246,15 +252,7 @@
.setExemptFromEntitlementCheck(true)
.setShouldShowEntitlementUi(false).build();
final TetheringRequest parceled = ParcelUtils.parcelingRoundTrip(unparceled);
- assertEquals(unparceled.getTetheringType(), parceled.getTetheringType());
- assertEquals(unparceled.getConnectivityScope(), parceled.getConnectivityScope());
- assertEquals(unparceled.getLocalIpv4Address(), parceled.getLocalIpv4Address());
- assertEquals(unparceled.getClientStaticIpv4Address(),
- parceled.getClientStaticIpv4Address());
- assertEquals(unparceled.isExemptFromEntitlementCheck(),
- parceled.isExemptFromEntitlementCheck());
- assertEquals(unparceled.getShouldShowEntitlementUi(),
- parceled.getShouldShowEntitlementUi());
+ assertEquals(unparceled, parceled);
}
@Test
diff --git a/tests/unit/java/android/net/ConnectivityManagerTest.java b/tests/unit/java/android/net/ConnectivityManagerTest.java
index b71a46f..9a77c89 100644
--- a/tests/unit/java/android/net/ConnectivityManagerTest.java
+++ b/tests/unit/java/android/net/ConnectivityManagerTest.java
@@ -52,6 +52,7 @@
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -72,6 +73,7 @@
import android.os.Messenger;
import android.os.Process;
+import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
import com.android.internal.util.test.BroadcastInterceptingContext;
@@ -83,6 +85,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -240,7 +243,7 @@
// register callback
when(mService.requestNetwork(anyInt(), any(), anyInt(), captor.capture(), anyInt(), any(),
- anyInt(), anyInt(), any(), nullable(String.class))).thenReturn(request);
+ anyInt(), anyInt(), any(), nullable(String.class), anyInt())).thenReturn(request);
manager.requestNetwork(request, callback, handler);
// callback triggers
@@ -269,7 +272,7 @@
// register callback
when(mService.requestNetwork(anyInt(), any(), anyInt(), captor.capture(), anyInt(), any(),
- anyInt(), anyInt(), any(), nullable(String.class))).thenReturn(req1);
+ anyInt(), anyInt(), any(), nullable(String.class), anyInt())).thenReturn(req1);
manager.requestNetwork(req1, callback, handler);
// callback triggers
@@ -287,7 +290,7 @@
// callback can be registered again
when(mService.requestNetwork(anyInt(), any(), anyInt(), captor.capture(), anyInt(), any(),
- anyInt(), anyInt(), any(), nullable(String.class))).thenReturn(req2);
+ anyInt(), anyInt(), any(), nullable(String.class), anyInt())).thenReturn(req2);
manager.requestNetwork(req2, callback, handler);
// callback triggers
@@ -311,7 +314,7 @@
when(mCtx.getApplicationInfo()).thenReturn(info);
when(mService.requestNetwork(anyInt(), any(), anyInt(), any(), anyInt(), any(), anyInt(),
- anyInt(), any(), nullable(String.class))).thenReturn(request);
+ anyInt(), any(), nullable(String.class), anyInt())).thenReturn(request);
Handler handler = new Handler(Looper.getMainLooper());
manager.requestNetwork(request, callback, handler);
@@ -403,15 +406,15 @@
manager.requestNetwork(request, callback);
verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(request.networkCapabilities),
eq(REQUEST.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(),
- eq(testPkgName), eq(testAttributionTag));
+ eq(testPkgName), eq(testAttributionTag), anyInt());
reset(mService);
// Verify that register network callback does not calls requestNetwork at all.
manager.registerNetworkCallback(request, callback);
verify(mService, never()).requestNetwork(anyInt(), any(), anyInt(), any(), anyInt(), any(),
- anyInt(), anyInt(), any(), any());
+ anyInt(), anyInt(), any(), any(), anyInt());
verify(mService).listenForNetwork(eq(request.networkCapabilities), any(), any(), anyInt(),
- eq(testPkgName), eq(testAttributionTag));
+ eq(testPkgName), eq(testAttributionTag), anyInt());
reset(mService);
Handler handler = new Handler(ConnectivityThread.getInstanceLooper());
@@ -419,24 +422,24 @@
manager.registerDefaultNetworkCallback(callback);
verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(null),
eq(TRACK_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(),
- eq(testPkgName), eq(testAttributionTag));
+ eq(testPkgName), eq(testAttributionTag), anyInt());
reset(mService);
manager.registerDefaultNetworkCallbackForUid(42, callback, handler);
verify(mService).requestNetwork(eq(42), eq(null),
eq(TRACK_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(),
- eq(testPkgName), eq(testAttributionTag));
+ eq(testPkgName), eq(testAttributionTag), anyInt());
manager.requestBackgroundNetwork(request, callback, handler);
verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(request.networkCapabilities),
eq(BACKGROUND_REQUEST.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(),
- eq(testPkgName), eq(testAttributionTag));
+ eq(testPkgName), eq(testAttributionTag), anyInt());
reset(mService);
manager.registerSystemDefaultNetworkCallback(callback, handler);
verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(null),
eq(TRACK_SYSTEM_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(),
- eq(testPkgName), eq(testAttributionTag));
+ eq(testPkgName), eq(testAttributionTag), anyInt());
reset(mService);
}
@@ -516,16 +519,154 @@
+ " attempts", ref.get());
}
- private <T> void mockService(Class<T> clazz, String name, T service) {
- doReturn(service).when(mCtx).getSystemService(name);
- doReturn(name).when(mCtx).getSystemServiceName(clazz);
+ @Test
+ public void testDeclaredMethodsFlag_requestWithMixedMethods_RegistrationFlagsMatch()
+ throws Exception {
+ doReturn(ConnectivityManager.FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS)
+ .when(mService).getEnabledConnectivityManagerFeatures();
+ final ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
- // If the test suite uses the inline mock maker library, such as for coverage tests,
- // then the final version of getSystemService must also be mocked, as the real
- // method will not be called by the test and null object is returned since no mock.
- // Otherwise, mocking a final method will fail the test.
- if (mCtx.getSystemService(clazz) == null) {
- doReturn(service).when(mCtx).getSystemService(clazz);
- }
+ final NetworkRequest request = new NetworkRequest.Builder().build();
+ final NetworkCallback callback1 = new ConnectivityManager.NetworkCallback() {
+ @Override
+ public void onPreCheck(@NonNull Network network) {}
+ @Override
+ public void onAvailable(@NonNull Network network) {}
+ @Override
+ public void onLost(@NonNull Network network) {}
+ @Override
+ public void onCapabilitiesChanged(@NonNull Network network,
+ @NonNull NetworkCapabilities networkCapabilities) {}
+ @Override
+ public void onLocalNetworkInfoChanged(@NonNull Network network,
+ @NonNull LocalNetworkInfo localNetworkInfo) {}
+ @Override
+ public void onNetworkResumed(@NonNull Network network) {}
+ @Override
+ public void onBlockedStatusChanged(@NonNull Network network, int blocked) {}
+ };
+ manager.requestNetwork(request, callback1);
+
+ final InOrder inOrder = inOrder(mService);
+ inOrder.verify(mService).requestNetwork(
+ anyInt(), any(), anyInt(), any(), anyInt(), any(), anyInt(), anyInt(), any(), any(),
+ eq(1 << ConnectivityManager.CALLBACK_PRECHECK
+ | 1 << ConnectivityManager.CALLBACK_AVAILABLE
+ | 1 << ConnectivityManager.CALLBACK_LOST
+ | 1 << ConnectivityManager.CALLBACK_CAP_CHANGED
+ | 1 << ConnectivityManager.CALLBACK_LOCAL_NETWORK_INFO_CHANGED
+ | 1 << ConnectivityManager.CALLBACK_RESUMED
+ | 1 << ConnectivityManager.CALLBACK_BLK_CHANGED));
+ }
+
+ @Test
+ public void testDeclaredMethodsFlag_listenWithMixedMethods_RegistrationFlagsMatch()
+ throws Exception {
+ final NetworkRequest request = new NetworkRequest.Builder().build();
+ doReturn(ConnectivityManager.FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS)
+ .when(mService).getEnabledConnectivityManagerFeatures();
+ final ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+
+ final NetworkCallback callback2 = new ConnectivityManager.NetworkCallback() {
+ @Override
+ public void onLosing(@NonNull Network network, int maxMsToLive) {}
+ @Override
+ public void onUnavailable() {}
+ @Override
+ public void onLinkPropertiesChanged(@NonNull Network network,
+ @NonNull LinkProperties linkProperties) {}
+ @Override
+ public void onNetworkSuspended(@NonNull Network network) {}
+ };
+ manager.registerNetworkCallback(request, callback2);
+ // Call a second time with the same callback to exercise caching
+ manager.registerNetworkCallback(request, callback2);
+
+ verify(mService, times(2)).listenForNetwork(
+ any(), any(), any(), anyInt(), any(), any(),
+ eq(1 << ConnectivityManager.CALLBACK_LOSING
+ // AVAILABLE calls IP_CHANGED and SUSPENDED so it gets added
+ | 1 << ConnectivityManager.CALLBACK_AVAILABLE
+ | 1 << ConnectivityManager.CALLBACK_UNAVAIL
+ | 1 << ConnectivityManager.CALLBACK_IP_CHANGED
+ | 1 << ConnectivityManager.CALLBACK_SUSPENDED));
+ }
+
+ @Test
+ public void testDeclaredMethodsFlag_requestWithHiddenAvailableCallback_RegistrationFlagsMatch()
+ throws Exception {
+ doReturn(ConnectivityManager.FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS)
+ .when(mService).getEnabledConnectivityManagerFeatures();
+ final ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+
+ final NetworkCallback hiddenOnAvailableCb = new ConnectivityManager.NetworkCallback() {
+ // This overload is @hide but might still be used by (bad) apps
+ @Override
+ public void onAvailable(@NonNull Network network,
+ @NonNull NetworkCapabilities networkCapabilities,
+ @NonNull LinkProperties linkProperties, boolean blocked) {}
+ };
+ manager.registerDefaultNetworkCallback(hiddenOnAvailableCb);
+
+ verify(mService).requestNetwork(
+ anyInt(), any(), anyInt(), any(), anyInt(), any(), anyInt(), anyInt(), any(), any(),
+ eq(1 << ConnectivityManager.CALLBACK_AVAILABLE));
+ }
+
+ public static class NetworkCallbackWithOnLostOnly extends NetworkCallback {
+ @Override
+ public void onLost(@NonNull Network network) {}
+ }
+
+ @Test
+ public void testDeclaredMethodsFlag_requestWithoutAvailableCallback_RegistrationFlagsMatch()
+ throws Exception {
+ doReturn(ConnectivityManager.FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS)
+ .when(mService).getEnabledConnectivityManagerFeatures();
+ final ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+ final Handler handler = new Handler(Looper.getMainLooper());
+
+ final NetworkCallback noOnAvailableCb = new NetworkCallbackWithOnLostOnly();
+ manager.registerSystemDefaultNetworkCallback(noOnAvailableCb, handler);
+
+ verify(mService).requestNetwork(
+ anyInt(), any(), anyInt(), any(), anyInt(), any(), anyInt(), anyInt(), any(), any(),
+ eq(1 << ConnectivityManager.CALLBACK_LOST));
+ }
+
+ @Test
+ public void testDeclaredMethodsFlag_listenWithMock_OptimizationDisabled()
+ throws Exception {
+ doReturn(ConnectivityManager.FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS)
+ .when(mService).getEnabledConnectivityManagerFeatures();
+ final ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+ final Handler handler = new Handler(Looper.getMainLooper());
+
+ final NetworkRequest request = new NetworkRequest.Builder().build();
+ manager.registerNetworkCallback(request, mock(NetworkCallbackWithOnLostOnly.class),
+ handler);
+
+ verify(mService).listenForNetwork(
+ any(), any(), any(), anyInt(), any(), any(),
+ // Mock that does not call the constructor -> do not use the optimization
+ eq(~0));
+ }
+
+ @Test
+ public void testDeclaredMethodsFlag_requestWitNoCallback_OptimizationDisabled()
+ throws Exception {
+ doReturn(ConnectivityManager.FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS)
+ .when(mService).getEnabledConnectivityManagerFeatures();
+ final ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+ final Handler handler = new Handler(Looper.getMainLooper());
+
+ final NetworkRequest request = new NetworkRequest.Builder().build();
+ final NetworkCallback noCallbackAtAll = new ConnectivityManager.NetworkCallback() {};
+ manager.requestBackgroundNetwork(request, noCallbackAtAll, handler);
+
+ verify(mService).requestNetwork(
+ anyInt(), any(), anyInt(), any(), anyInt(), any(), anyInt(), anyInt(), any(), any(),
+ // No callbacks overridden -> do not use the optimization
+ eq(~0));
}
}
diff --git a/tests/unit/java/android/net/NetworkCallbackFlagsTest.kt b/tests/unit/java/android/net/NetworkCallbackFlagsTest.kt
new file mode 100644
index 0000000..af06a64
--- /dev/null
+++ b/tests/unit/java/android/net/NetworkCallbackFlagsTest.kt
@@ -0,0 +1,212 @@
+/*
+ * 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.ConnectivityManager.NetworkCallback
+import android.net.ConnectivityManager.NetworkCallbackMethodsHolder
+import android.os.Build
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import java.lang.reflect.Method
+import java.lang.reflect.Modifier
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.any
+import org.mockito.Mockito.doCallRealMethod
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.mockingDetails
+
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@RunWith(DevSdkIgnoreRunner::class)
+class NetworkCallbackFlagsTest {
+
+ // To avoid developers forgetting to update NETWORK_CB_METHODS when modifying NetworkCallbacks,
+ // or using wrong values, calculate it from annotations here and verify that it matches.
+ // This avoids the runtime cost of reflection, but still ensures that the list is correct.
+ @Test
+ fun testNetworkCallbackMethods_calculateFromAnnotations_matchesHardcodedList() {
+ val calculatedMethods = getNetworkCallbackMethodsFromAnnotations()
+ assertEquals(
+ calculatedMethods.toSet(),
+ NetworkCallbackMethodsHolder.NETWORK_CB_METHODS.map {
+ NetworkCallbackMethodWithEquals(
+ it.mName,
+ it.mParameterTypes.toList(),
+ callbacksCallingThisMethod = it.mCallbacksCallingThisMethod
+ )
+ }.toSet()
+ )
+ }
+
+ data class NetworkCallbackMethodWithEquals(
+ val name: String,
+ val parameterTypes: List<Class<*>>,
+ val callbacksCallingThisMethod: Int
+ )
+
+ data class NetworkCallbackMethodBuilder(
+ val name: String,
+ val parameterTypes: List<Class<*>>,
+ val isFinal: Boolean,
+ val methodId: Int,
+ val mayCall: Set<Int>?,
+ var callbacksCallingThisMethod: Int
+ ) {
+ fun build() = NetworkCallbackMethodWithEquals(
+ name,
+ parameterTypes,
+ callbacksCallingThisMethod
+ )
+ }
+
+ /**
+ * Build [NetworkCallbackMethodsHolder.NETWORK_CB_METHODS] from [NetworkCallback] annotations.
+ */
+ private fun getNetworkCallbackMethodsFromAnnotations(): List<NetworkCallbackMethodWithEquals> {
+ val parsedMethods = mutableListOf<NetworkCallbackMethodBuilder>()
+ val methods = NetworkCallback::class.java.declaredMethods
+ methods.forEach { method ->
+ val cb = method.getAnnotation(
+ NetworkCallback.FilteredCallback::class.java
+ ) ?: return@forEach
+ val callbacksCallingThisMethod = if (cb.calledByCallbackId == 0) {
+ 0
+ } else {
+ 1 shl cb.calledByCallbackId
+ }
+ parsedMethods.add(
+ NetworkCallbackMethodBuilder(
+ method.name,
+ method.parameterTypes.toList(),
+ Modifier.isFinal(method.modifiers),
+ cb.methodId,
+ cb.mayCall.toSet(),
+ callbacksCallingThisMethod
+ )
+ )
+ }
+
+ // Propagate callbacksCallingThisMethod for transitive calls
+ do {
+ var hadChange = false
+ parsedMethods.forEach { caller ->
+ parsedMethods.forEach { callee ->
+ if (caller.mayCall?.contains(callee.methodId) == true) {
+ // Callbacks that call the caller also cause calls to the callee. So
+ // callbacksCallingThisMethod for the callee should include
+ // callbacksCallingThisMethod from the caller.
+ val newValue =
+ caller.callbacksCallingThisMethod or callee.callbacksCallingThisMethod
+ hadChange = hadChange || callee.callbacksCallingThisMethod != newValue
+ callee.callbacksCallingThisMethod = newValue
+ }
+ }
+ }
+ } while (hadChange)
+
+ // Final methods may affect the flags for transitive calls, but cannot be overridden, so do
+ // not need to be in the list (no overridden method in NetworkCallback will match them).
+ return parsedMethods.filter { !it.isFinal }.map { it.build() }
+ }
+
+ @Test
+ fun testMethodsAreAnnotated() {
+ val annotations = NetworkCallback::class.java.declaredMethods.mapNotNull { method ->
+ if (!Modifier.isPublic(method.modifiers) && !Modifier.isProtected(method.modifiers)) {
+ return@mapNotNull null
+ }
+ val annotation = method.getAnnotation(NetworkCallback.FilteredCallback::class.java)
+ assertNotNull(annotation, "$method is missing the @FilteredCallback annotation")
+ return@mapNotNull annotation
+ }
+
+ annotations.groupingBy { it.methodId }.eachCount().forEach { (methodId, cnt) ->
+ assertEquals(1, cnt, "Method ID $methodId is used more than once in @FilteredCallback")
+ }
+ }
+
+ @Test
+ fun testObviousCalleesAreInAnnotation() {
+ NetworkCallback::class.java.declaredMethods.forEach { method ->
+ val annotation = method.getAnnotation(NetworkCallback.FilteredCallback::class.java)
+ ?: return@forEach
+ val missingFlags = getObviousCallees(method).toMutableSet().apply {
+ removeAll(annotation.mayCall.toSet())
+ }
+ val msg = "@FilteredCallback on $method is missing flags " +
+ "$missingFlags in mayCall. There may be other " +
+ "calls that are not detected if they are done conditionally."
+ assertEquals(emptySet(), missingFlags, msg)
+ }
+ }
+
+ /**
+ * Invoke the specified NetworkCallback method with mock arguments, return a set of transitively
+ * called methods.
+ *
+ * This provides an idea of which methods are transitively called by the specified method. It's
+ * not perfect as some callees could be called or not depending on the exact values of the mock
+ * arguments that are passed in (for example, onAvailable calls onNetworkSuspended only if the
+ * capabilities lack the NOT_SUSPENDED capability), but it should catch obvious forgotten calls.
+ */
+ private fun getObviousCallees(method: Method): Set<Int> {
+ // Create a mock NetworkCallback that mocks all methods except the one specified by the
+ // caller.
+ val mockCallback = mock(NetworkCallback::class.java)
+
+ if (!Modifier.isFinal(method.modifiers) ||
+ // The mock class will be NetworkCallback (not a subclass) if using mockito-inline,
+ // which mocks final methods too
+ mockCallback.javaClass == NetworkCallback::class.java) {
+ doCallRealMethod().`when`(mockCallback).let { mockObj ->
+ val anyArgs = method.parameterTypes.map { any(it) }
+ method.invoke(mockObj, *anyArgs.toTypedArray())
+ }
+ }
+
+ // Invoke the target method with mock parameters
+ val mockParameters = method.parameterTypes.map { getMockFor(method, it) }
+ method.invoke(mockCallback, *mockParameters.toTypedArray())
+
+ // Aggregate callees
+ val mockingDetails = mockingDetails(mockCallback)
+ return mockingDetails.invocations.mapNotNull { inv ->
+ if (inv.method == method) {
+ null
+ } else {
+ inv.method.getAnnotation(NetworkCallback.FilteredCallback::class.java)?.methodId
+ }
+ }.toSet()
+ }
+
+ private fun getMockFor(method: Method, c: Class<*>): Any {
+ if (!c.isPrimitive && !Modifier.isFinal(c.modifiers)) {
+ return mock(c)
+ }
+ return when (c) {
+ NetworkCapabilities::class.java -> NetworkCapabilities()
+ LinkProperties::class.java -> LinkProperties()
+ LocalNetworkInfo::class.java -> LocalNetworkInfo(null)
+ Boolean::class.java -> false
+ Int::class.java -> 0
+ else -> fail("No mock set for parameter type $c used in $method")
+ }
+ }
+}
diff --git a/tests/unit/java/com/android/server/BpfNetMapsTest.java b/tests/unit/java/com/android/server/BpfNetMapsTest.java
index cbc060a..859c54a 100644
--- a/tests/unit/java/com/android/server/BpfNetMapsTest.java
+++ b/tests/unit/java/com/android/server/BpfNetMapsTest.java
@@ -87,7 +87,9 @@
import android.net.InetAddresses;
import android.net.UidOwnerValue;
import android.os.Build;
+import android.os.Process;
import android.os.ServiceSpecificException;
+import android.os.UserHandle;
import android.system.ErrnoException;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
@@ -1249,6 +1251,32 @@
);
}
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testIsUidNetworkingBlockedForCoreUids() throws Exception {
+ final long allowlistMatch = BACKGROUND_MATCH; // Enable any allowlist match.
+ mConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, new U32(allowlistMatch));
+
+ // Verify that a normal uid that is not on this chain is indeed blocked.
+ assertTrue(BpfNetMapsUtils.isUidNetworkingBlocked(TEST_UID, false, mConfigurationMap,
+ mUidOwnerMap, mDataSaverEnabledMap));
+
+ final int[] coreAids = new int[] {
+ Process.ROOT_UID,
+ Process.SYSTEM_UID,
+ Process.FIRST_APPLICATION_UID - 10,
+ Process.FIRST_APPLICATION_UID - 1,
+ };
+ // Core appIds are not on the chain but should still be allowed on any user.
+ for (int userId = 0; userId < 20; userId++) {
+ for (final int aid : coreAids) {
+ final int uid = UserHandle.getUid(userId, aid);
+ assertFalse(BpfNetMapsUtils.isUidNetworkingBlocked(uid, false, mConfigurationMap,
+ mUidOwnerMap, mDataSaverEnabledMap));
+ }
+ }
+ }
+
private void doTestIsUidRestrictedOnMeteredNetworks(
final long enabledMatches,
final long uidRules,
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 8526a9a..be7f2a3 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -2871,7 +2871,7 @@
};
final NetworkRequest request = mService.listenForNetwork(caps, messenger, binder,
NetworkCallback.FLAG_NONE, mContext.getOpPackageName(),
- mContext.getAttributionTag());
+ mContext.getAttributionTag(), ~0 /* declaredMethodsFlag */);
mService.releaseNetworkRequest(request);
deathRecipient.get().binderDied();
// Wait for the release message to be processed.
@@ -5407,7 +5407,7 @@
mService.requestNetwork(Process.INVALID_UID, networkCapabilities,
NetworkRequest.Type.REQUEST.ordinal(), null, 0, null,
ConnectivityManager.TYPE_WIFI, NetworkCallback.FLAG_NONE,
- mContext.getPackageName(), getAttributionTag());
+ mContext.getPackageName(), getAttributionTag(), ~0 /* declaredMethodsFlag */);
});
final NetworkRequest.Builder builder =
@@ -13655,7 +13655,8 @@
IllegalArgumentException.class,
() -> mService.requestNetwork(Process.INVALID_UID, nc, reqTypeInt, null, 0,
null, ConnectivityManager.TYPE_NONE, NetworkCallback.FLAG_NONE,
- mContext.getPackageName(), getAttributionTag())
+ mContext.getPackageName(), getAttributionTag(),
+ ~0 /* declaredMethodsFlag */)
);
}
}
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSBlockedReasonsTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSBlockedReasonsTest.kt
index 3ad8de8..985d403 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSBlockedReasonsTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSBlockedReasonsTest.kt
@@ -419,4 +419,30 @@
deps.setChangeIdEnabled(true, NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION)
doTestBlockedReasonsNoInternetPermission(blockedByNoInternetPermission = true)
}
+
+ private fun doTestEnforceMeteredApnPolicy(restricted: Boolean) {
+ doReturn(restricted).`when`(bpfNetMaps).isUidRestrictedOnMeteredNetworks(Process.myUid())
+
+ val cellAgent = Agent(nc = cellNc())
+ cellAgent.connect()
+ val cb = TestableNetworkCallback()
+ cm.requestNetwork(cellRequest(), cb)
+
+ if (restricted) {
+ waitForIdle()
+ cb.assertNoCallback()
+ } else {
+ cb.expectAvailableCallbacks(cellAgent.network, validated = false)
+ }
+ }
+
+ @Test
+ fun testEnforceMeteredApnPolicy_restricted() {
+ doTestEnforceMeteredApnPolicy(restricted = true)
+ }
+
+ @Test
+ fun testEnforceMeteredApnPolicy_notRestricted() {
+ doTestEnforceMeteredApnPolicy(restricted = false)
+ }
}
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSDeclaredMethodsForCallbacksTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSDeclaredMethodsForCallbacksTest.kt
new file mode 100644
index 0000000..cf990b1
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivityservice/CSDeclaredMethodsForCallbacksTest.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivityservice
+
+import android.net.ConnectivityManager
+import android.net.ConnectivityManager.CALLBACK_CAP_CHANGED
+import android.net.ConnectivityManager.CALLBACK_IP_CHANGED
+import android.net.ConnectivityManager.CALLBACK_LOST
+import android.net.ConnectivityManager.NetworkCallback.DECLARED_METHODS_ALL
+import android.net.LinkAddress
+import android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED
+import android.net.NetworkRequest
+import android.os.Build
+import com.android.net.module.util.BitUtils.packBits
+import com.android.server.CSTest
+import com.android.server.ConnectivityService
+import com.android.server.defaultLp
+import com.android.server.defaultNc
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.RecorderCallback.CallbackEntry
+import com.android.testutils.TestableNetworkCallback
+import com.android.testutils.tryTest
+import java.util.concurrent.atomic.AtomicInteger
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.spy
+
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRunner.MonitorThreadLeak
+@RunWith(DevSdkIgnoreRunner::class)
+class CSDeclaredMethodsForCallbacksTest : CSTest() {
+ private val mockedCallbackFlags = AtomicInteger(DECLARED_METHODS_ALL)
+ private lateinit var wrappedService: ConnectivityService
+
+ private val instrumentedCm by lazy { ConnectivityManager(context, wrappedService) }
+
+ @Before
+ fun setUpWrappedService() {
+ // Mock the callback flags set by ConnectivityManager when calling ConnectivityService, to
+ // simulate methods not being overridden
+ wrappedService = spy(service)
+ doAnswer { inv ->
+ service.requestNetwork(
+ inv.getArgument(0),
+ inv.getArgument(1),
+ inv.getArgument(2),
+ inv.getArgument(3),
+ inv.getArgument(4),
+ inv.getArgument(5),
+ inv.getArgument(6),
+ inv.getArgument(7),
+ inv.getArgument(8),
+ inv.getArgument(9),
+ mockedCallbackFlags.get())
+ }.`when`(wrappedService).requestNetwork(
+ anyInt(),
+ any(),
+ anyInt(),
+ any(),
+ anyInt(),
+ any(),
+ anyInt(),
+ anyInt(),
+ any(),
+ any(),
+ anyInt()
+ )
+ doAnswer { inv ->
+ service.listenForNetwork(
+ inv.getArgument(0),
+ inv.getArgument(1),
+ inv.getArgument(2),
+ inv.getArgument(3),
+ inv.getArgument(4),
+ inv.getArgument(5),
+ mockedCallbackFlags.get()
+ )
+ }.`when`(wrappedService)
+ .listenForNetwork(any(), any(), any(), anyInt(), any(), any(), anyInt())
+ }
+
+ @Test
+ fun testCallbacksAreFiltered() {
+ val requestCb = TestableNetworkCallback()
+ val listenCb = TestableNetworkCallback()
+ mockedCallbackFlags.withFlags(CALLBACK_IP_CHANGED, CALLBACK_LOST) {
+ instrumentedCm.requestNetwork(NetworkRequest.Builder().build(), requestCb)
+ }
+ mockedCallbackFlags.withFlags(CALLBACK_CAP_CHANGED) {
+ instrumentedCm.registerNetworkCallback(NetworkRequest.Builder().build(), listenCb)
+ }
+
+ with(Agent()) {
+ connect()
+ sendLinkProperties(defaultLp().apply {
+ addLinkAddress(LinkAddress("fe80:db8::123/64"))
+ })
+ sendNetworkCapabilities(defaultNc().apply {
+ addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)
+ })
+ disconnect()
+ }
+ waitForIdle()
+
+ // Only callbacks for the corresponding flags are called
+ requestCb.expect<CallbackEntry.LinkPropertiesChanged>()
+ requestCb.expect<CallbackEntry.Lost>()
+ requestCb.assertNoCallback(timeoutMs = 0L)
+
+ listenCb.expect<CallbackEntry.CapabilitiesChanged>()
+ listenCb.assertNoCallback(timeoutMs = 0L)
+ }
+}
+
+private fun AtomicInteger.withFlags(vararg flags: Int, action: () -> Unit) {
+ tryTest {
+ set(packBits(flags).toInt())
+ action()
+ } cleanup {
+ set(DECLARED_METHODS_ALL)
+ }
+}
diff --git a/thread/framework/java/android/net/thread/OperationalDatasetTimestamp.java b/thread/framework/java/android/net/thread/OperationalDatasetTimestamp.java
index 520acbd..cecb4e9 100644
--- a/thread/framework/java/android/net/thread/OperationalDatasetTimestamp.java
+++ b/thread/framework/java/android/net/thread/OperationalDatasetTimestamp.java
@@ -65,11 +65,32 @@
*/
@NonNull
public static OperationalDatasetTimestamp fromInstant(@NonNull Instant instant) {
+ return OperationalDatasetTimestamp.fromInstant(instant, true /* isAuthoritativeSource */);
+ }
+
+ /**
+ * Creates a new {@link OperationalDatasetTimestamp} object from an {@link Instant}.
+ *
+ * <p>The {@code seconds} is set to {@code instant.getEpochSecond()}, {@code ticks} is set to
+ * {@link instant#getNano()} based on frequency of 32768 Hz, and {@code isAuthoritativeSource}
+ * is set to {@code isAuthoritativeSource}.
+ *
+ * <p>Note that this conversion can lose precision and a value returned by {@link #toInstant}
+ * may not equal exactly the {@code instant}.
+ *
+ * @throws IllegalArgumentException if {@code instant.getEpochSecond()} is larger than {@code
+ * 0xffffffffffffL}
+ * @see toInstant
+ * @hide
+ */
+ @NonNull
+ public static OperationalDatasetTimestamp fromInstant(
+ @NonNull Instant instant, boolean isAuthoritativeSource) {
int ticks = getRoundedTicks(instant.getNano());
long seconds = instant.getEpochSecond() + ticks / TICKS_UPPER_BOUND;
// the rounded ticks can be 0x8000 if instant.getNano() >= 999984742
ticks = ticks % TICKS_UPPER_BOUND;
- return new OperationalDatasetTimestamp(seconds, ticks, true /* isAuthoritativeSource */);
+ return new OperationalDatasetTimestamp(seconds, ticks, isAuthoritativeSource);
}
/**
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
index 1d823e0..2f60d9a 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
@@ -955,9 +955,7 @@
final byte[] securityFlags = new byte[] {(byte) 0xff, (byte) 0xf8};
return new ActiveOperationalDataset.Builder()
- .setActiveTimestamp(
- new OperationalDatasetTimestamp(
- now.getEpochSecond() & 0xffffffffffffL, 0, authoritative))
+ .setActiveTimestamp(OperationalDatasetTimestamp.fromInstant(now, authoritative))
.setExtendedPanId(newRandomBytes(random, LENGTH_EXTENDED_PAN_ID))
.setPanId(panId)
.setNetworkName(networkName)
diff --git a/thread/tests/unit/src/android/net/thread/OperationalDatasetTimestampTest.java b/thread/tests/unit/src/android/net/thread/OperationalDatasetTimestampTest.java
index 2244a89..11c78e3 100644
--- a/thread/tests/unit/src/android/net/thread/OperationalDatasetTimestampTest.java
+++ b/thread/tests/unit/src/android/net/thread/OperationalDatasetTimestampTest.java
@@ -41,6 +41,19 @@
}
@Test
+ public void fromInstant_authoritativeIsSetAsSpecified() {
+ Instant instant = Instant.now();
+
+ OperationalDatasetTimestamp timestampAuthoritativeFalse =
+ OperationalDatasetTimestamp.fromInstant(instant, false);
+ OperationalDatasetTimestamp timestampAuthoritativeTrue =
+ OperationalDatasetTimestamp.fromInstant(instant, true);
+
+ assertThat(timestampAuthoritativeFalse.isAuthoritativeSource()).isEqualTo(false);
+ assertThat(timestampAuthoritativeTrue.isAuthoritativeSource()).isEqualTo(true);
+ }
+
+ @Test
public void fromTlvValue_goodValue_success() {
OperationalDatasetTimestamp timestamp =
OperationalDatasetTimestamp.fromTlvValue(base16().decode("FFEEDDCCBBAA9989"));
diff --git a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
index de6d1e1..6e2369f 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
@@ -99,6 +99,8 @@
import java.nio.charset.StandardCharsets;
import java.time.Clock;
import java.time.DateTimeException;
+import java.time.Instant;
+import java.time.ZoneId;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicReference;
@@ -584,6 +586,55 @@
}
@Test
+ public void createRandomizedDataset_zeroNanoseconds_returnsZeroTicks() throws Exception {
+ Instant now = Instant.ofEpochSecond(0, 0);
+ Clock clock = Clock.fixed(now, ZoneId.systemDefault());
+ MockitoSession session =
+ ExtendedMockito.mockitoSession().mockStatic(SystemClock.class).startMocking();
+ final IActiveOperationalDatasetReceiver mockReceiver =
+ ExtendedMockito.mock(IActiveOperationalDatasetReceiver.class);
+
+ try {
+ ExtendedMockito.when(SystemClock.currentNetworkTimeClock()).thenReturn(clock);
+ mService.createRandomizedDataset(DEFAULT_NETWORK_NAME, mockReceiver);
+ mTestLooper.dispatchAll();
+ } finally {
+ session.finishMocking();
+ }
+
+ verify(mockReceiver, never()).onError(anyInt(), anyString());
+ verify(mockReceiver, times(1)).onSuccess(mActiveDatasetCaptor.capture());
+ ActiveOperationalDataset activeDataset = mActiveDatasetCaptor.getValue();
+ assertThat(activeDataset.getActiveTimestamp().getTicks()).isEqualTo(0);
+ }
+
+ @Test
+ public void createRandomizedDataset_maxNanoseconds_returnsMaxTicks() throws Exception {
+ // The nanoseconds to ticks conversion is rounded in the current implementation.
+ // 32767.5 / 32768 * 1000000000 = 999984741.2109375, using 999984741 to
+ // produce the maximum ticks.
+ Instant now = Instant.ofEpochSecond(0, 999984741);
+ Clock clock = Clock.fixed(now, ZoneId.systemDefault());
+ MockitoSession session =
+ ExtendedMockito.mockitoSession().mockStatic(SystemClock.class).startMocking();
+ final IActiveOperationalDatasetReceiver mockReceiver =
+ ExtendedMockito.mock(IActiveOperationalDatasetReceiver.class);
+
+ try {
+ ExtendedMockito.when(SystemClock.currentNetworkTimeClock()).thenReturn(clock);
+ mService.createRandomizedDataset(DEFAULT_NETWORK_NAME, mockReceiver);
+ mTestLooper.dispatchAll();
+ } finally {
+ session.finishMocking();
+ }
+
+ verify(mockReceiver, never()).onError(anyInt(), anyString());
+ verify(mockReceiver, times(1)).onSuccess(mActiveDatasetCaptor.capture());
+ ActiveOperationalDataset activeDataset = mActiveDatasetCaptor.getValue();
+ assertThat(activeDataset.getActiveTimestamp().getTicks()).isEqualTo(32767);
+ }
+
+ @Test
public void createRandomizedDataset_hasNetworkTimeClock_datasetActiveTimestampIsAuthoritative()
throws Exception {
MockitoSession session =