Merge "Improve error message for doTestContinuousQueries" into main
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 83619d6..39009cb 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,6 +1,15 @@
+[Builtin Hooks]
+bpfmt = true
+clang_format = true
+ktfmt = true
+
+[Builtin Hooks Options]
+clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp,hpp
+ktfmt = --kotlinlang-style
+
[Hook Scripts]
checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
-ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py -f ${PREUPLOAD_FILES}
+ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py --no-verify-format -f ${PREUPLOAD_FILES}
hidden_api_txt_checksorted_hook = ${REPO_ROOT}/tools/platform-compat/hiddenapi/checksorted_sha.sh ${PREUPLOAD_COMMIT} ${REPO_ROOT}
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index 8b3102a..0f5a014 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -198,6 +198,10 @@
/**
* VIRTUAL tethering type.
+ *
+ * This tethering type is for providing external network to virtual machines
+ * running on top of Android devices, which are created and managed by
+ * AVF(Android Virtualization Framework).
* @hide
*/
@FlaggedApi(Flags.TETHERING_REQUEST_VIRTUAL)
@@ -1379,6 +1383,9 @@
@RequiresPermission(Manifest.permission.ACCESS_NETWORK_STATE)
public void registerTetheringEventCallback(@NonNull Executor executor,
@NonNull TetheringEventCallback callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
final String callerPkg = mContext.getOpPackageName();
Log.i(TAG, "registerTetheringEventCallback caller:" + callerPkg);
@@ -1533,6 +1540,8 @@
Manifest.permission.ACCESS_NETWORK_STATE
})
public void unregisterTetheringEventCallback(@NonNull final TetheringEventCallback callback) {
+ Objects.requireNonNull(callback);
+
final String callerPkg = mContext.getOpPackageName();
Log.i(TAG, "unregisterTetheringEventCallback caller:" + callerPkg);
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index 5cdd6ab..b807544 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -72,7 +72,6 @@
import com.android.net.module.util.InterfaceParams;
import com.android.net.module.util.NetdUtils;
import com.android.net.module.util.RoutingCoordinatorManager;
-import com.android.net.module.util.SdkUtil.LateSdk;
import com.android.net.module.util.SharedLog;
import com.android.net.module.util.SyncStateMachine.StateInfo;
import com.android.net.module.util.ip.InterfaceController;
@@ -239,11 +238,8 @@
private final INetd mNetd;
@NonNull
private final BpfCoordinator mBpfCoordinator;
- // Contains null if the connectivity module is unsupported, as the routing coordinator is not
- // available. Must use LateSdk because MessageUtils enumerates fields in this class, so it
- // must be able to find all classes at runtime.
@NonNull
- private final LateSdk<RoutingCoordinatorManager> mRoutingCoordinator;
+ private final RoutingCoordinatorManager mRoutingCoordinator;
private final Callback mCallback;
private final InterfaceController mInterfaceCtrl;
private final PrivateAddressCoordinator mPrivateAddressCoordinator;
@@ -301,7 +297,7 @@
public IpServer(
String ifaceName, Handler handler, int interfaceType, SharedLog log,
INetd netd, @NonNull BpfCoordinator bpfCoordinator,
- @Nullable LateSdk<RoutingCoordinatorManager> routingCoordinator, Callback callback,
+ RoutingCoordinatorManager routingCoordinatorManager, Callback callback,
TetheringConfiguration config, PrivateAddressCoordinator addressCoordinator,
TetheringMetrics tetheringMetrics, Dependencies deps) {
super(ifaceName, USE_SYNC_SM ? null : handler.getLooper());
@@ -309,7 +305,7 @@
mLog = log.forSubComponent(ifaceName);
mNetd = netd;
mBpfCoordinator = bpfCoordinator;
- mRoutingCoordinator = routingCoordinator;
+ mRoutingCoordinator = routingCoordinatorManager;
mCallback = callback;
mInterfaceCtrl = new InterfaceController(ifaceName, mNetd, mLog);
mIfaceName = ifaceName;
@@ -825,47 +821,25 @@
private void addInterfaceToNetwork(final int netId, @NonNull final String ifaceName) {
try {
- if (SdkLevel.isAtLeastS() && null != mRoutingCoordinator.value) {
- // TODO : remove this call in favor of using the LocalNetworkConfiguration
- // correctly, which will let ConnectivityService do it automatically.
- mRoutingCoordinator.value.addInterfaceToNetwork(netId, ifaceName);
- } else {
- mNetd.networkAddInterface(netId, ifaceName);
- }
- } catch (ServiceSpecificException | RemoteException e) {
+ // TODO : remove this call in favor of using the LocalNetworkConfiguration
+ // correctly, which will let ConnectivityService do it automatically.
+ mRoutingCoordinator.addInterfaceToNetwork(netId, ifaceName);
+ } catch (ServiceSpecificException e) {
mLog.e("Failed to add " + mIfaceName + " to local table: ", e);
}
}
- private void addInterfaceForward(@NonNull final String fromIface,
- @NonNull final String toIface) throws ServiceSpecificException, RemoteException {
- if (SdkLevel.isAtLeastS() && null != mRoutingCoordinator.value) {
- mRoutingCoordinator.value.addInterfaceForward(fromIface, toIface);
- } else {
- mNetd.tetherAddForward(fromIface, toIface);
- mNetd.ipfwdAddInterfaceForward(fromIface, toIface);
- }
+ private void addInterfaceForward(@NonNull final String fromIface, @NonNull final String toIface)
+ throws ServiceSpecificException {
+ mRoutingCoordinator.addInterfaceForward(fromIface, toIface);
}
private void removeInterfaceForward(@NonNull final String fromIface,
@NonNull final String toIface) {
- if (SdkLevel.isAtLeastS() && null != mRoutingCoordinator.value) {
- try {
- mRoutingCoordinator.value.removeInterfaceForward(fromIface, toIface);
- } catch (ServiceSpecificException e) {
- mLog.e("Exception in removeInterfaceForward", e);
- }
- } else {
- try {
- mNetd.ipfwdRemoveInterfaceForward(fromIface, toIface);
- } catch (RemoteException | ServiceSpecificException e) {
- mLog.e("Exception in ipfwdRemoveInterfaceForward", e);
- }
- try {
- mNetd.tetherRemoveForward(fromIface, toIface);
- } catch (RemoteException | ServiceSpecificException e) {
- mLog.e("Exception in disableNat", e);
- }
+ try {
+ mRoutingCoordinator.removeInterfaceForward(fromIface, toIface);
+ } catch (RuntimeException e) {
+ mLog.e("Exception in removeInterfaceForward", e);
}
}
@@ -1370,7 +1344,7 @@
mBpfCoordinator.maybeAttachProgram(mIfaceName, ifname);
try {
addInterfaceForward(mIfaceName, ifname);
- } catch (RemoteException | ServiceSpecificException e) {
+ } catch (RuntimeException e) {
mLog.e("Exception enabling iface forward", e);
cleanupUpstream();
mLastError = TETHER_ERROR_ENABLE_FORWARDING_ERROR;
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 163fe24..d62f18f 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -138,7 +138,6 @@
import com.android.net.module.util.HandlerUtils;
import com.android.net.module.util.NetdUtils;
import com.android.net.module.util.RoutingCoordinatorManager;
-import com.android.net.module.util.SdkUtil.LateSdk;
import com.android.net.module.util.SharedLog;
import com.android.networkstack.apishim.common.BluetoothPanShim;
import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceCallbackShim;
@@ -246,10 +245,7 @@
private final Handler mHandler;
private final INetd mNetd;
private final NetdCallback mNetdCallback;
- // Contains null if the connectivity module is unsupported, as the routing coordinator is not
- // available. Must use LateSdk because MessageUtils enumerates fields in this class, so it
- // must be able to find all classes at runtime.
- @NonNull private final LateSdk<RoutingCoordinatorManager> mRoutingCoordinator;
+ private final RoutingCoordinatorManager mRoutingCoordinator;
private final UserRestrictionActionListener mTetheringRestriction;
private final ActiveDataSubIdListener mActiveDataSubIdListener;
private final ConnectedClientsTracker mConnectedClientsTracker;
@@ -294,11 +290,11 @@
mLog.mark("Tethering.constructed");
mDeps = deps;
mContext = mDeps.getContext();
- mNetd = mDeps.getINetd(mContext);
- mRoutingCoordinator = mDeps.getRoutingCoordinator(mContext);
+ mNetd = mDeps.getINetd(mContext, mLog);
+ mRoutingCoordinator = mDeps.getRoutingCoordinator(mContext, mLog);
mLooper = mDeps.makeTetheringLooper();
mNotificationUpdater = mDeps.makeNotificationUpdater(mContext, mLooper);
- mTetheringMetrics = mDeps.makeTetheringMetrics();
+ mTetheringMetrics = mDeps.makeTetheringMetrics(mContext);
// This is intended to ensrure that if something calls startTethering(bluetooth) just after
// bluetooth is enabled. Before onServiceConnected is called, store the calls into this
@@ -2397,9 +2393,6 @@
hasCallingPermission(NETWORK_SETTINGS)
|| hasCallingPermission(PERMISSION_MAINLINE_NETWORK_STACK)
|| hasCallingPermission(NETWORK_STACK);
- if (callback == null) {
- throw new NullPointerException();
- }
mHandler.post(() -> {
mTetheringEventCallbacks.register(callback, new CallbackCookie(hasListPermission));
final TetheringCallbackStartedParcel parcel = new TetheringCallbackStartedParcel();
@@ -2437,9 +2430,6 @@
/** Unregister tethering event callback */
void unregisterTetheringEventCallback(ITetheringEventCallback callback) {
- if (callback == null) {
- throw new NullPointerException();
- }
mHandler.post(() -> {
mTetheringEventCallbacks.unregister(callback);
});
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
index 54dbf6c..5d9d349 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
@@ -36,7 +36,7 @@
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.RoutingCoordinatorManager;
-import com.android.net.module.util.SdkUtil.LateSdk;
+import com.android.net.module.util.RoutingCoordinatorService;
import com.android.net.module.util.SharedLog;
import com.android.networkstack.apishim.BluetoothPanShimImpl;
import com.android.networkstack.apishim.common.BluetoothPanShim;
@@ -120,20 +120,26 @@
/**
* Get a reference to INetd to be used by tethering.
*/
- public INetd getINetd(Context context) {
- return INetd.Stub.asInterface(
- (IBinder) context.getSystemService(Context.NETD_SERVICE));
+ public INetd getINetd(Context context, SharedLog log) {
+ final INetd netd =
+ INetd.Stub.asInterface((IBinder) context.getSystemService(Context.NETD_SERVICE));
+ if (netd == null) {
+ log.wtf("INetd is null");
+ }
+ return netd;
}
/**
- * Get the routing coordinator, or null if below S.
+ * Get the routing coordinator.
*/
- @Nullable
- public LateSdk<RoutingCoordinatorManager> getRoutingCoordinator(Context context) {
- if (!SdkLevel.isAtLeastS()) return new LateSdk<>(null);
- return new LateSdk<>(
- new RoutingCoordinatorManager(
- context, ConnectivityInternalApiUtil.getRoutingCoordinator(context)));
+ public RoutingCoordinatorManager getRoutingCoordinator(Context context, SharedLog log) {
+ IBinder binder;
+ if (!SdkLevel.isAtLeastS()) {
+ binder = new RoutingCoordinatorService(getINetd(context, log));
+ } else {
+ binder = ConnectivityInternalApiUtil.getRoutingCoordinator(context);
+ }
+ return new RoutingCoordinatorManager(context, binder);
}
/**
@@ -187,8 +193,8 @@
/**
* Make the TetheringMetrics to be used by tethering.
*/
- public TetheringMetrics makeTetheringMetrics() {
- return new TetheringMetrics();
+ public TetheringMetrics makeTetheringMetrics(Context ctx) {
+ return new TetheringMetrics(ctx);
}
/**
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringService.java b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
index a147a4a..454cbf1 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringService.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
@@ -164,6 +164,8 @@
@Override
public void registerTetheringEventCallback(ITetheringEventCallback callback,
String callerPkg) {
+ // Silently ignore call if the callback is null. This can only happen via reflection.
+ if (callback == null) return;
try {
if (!hasTetherAccessPermission()) {
callback.onCallbackStopped(TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION);
@@ -176,6 +178,8 @@
@Override
public void unregisterTetheringEventCallback(ITetheringEventCallback callback,
String callerPkg) {
+ // Silently ignore call if the callback is null. This can only happen via reflection.
+ if (callback == null) return;
try {
if (!hasTetherAccessPermission()) {
callback.onCallbackStopped(TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION);
diff --git a/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java b/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java
index 814afcd..136dfb1 100644
--- a/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java
+++ b/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java
@@ -46,6 +46,7 @@
import static android.net.TetheringManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
import android.annotation.Nullable;
+import android.content.Context;
import android.net.NetworkCapabilities;
import android.stats.connectivity.DownstreamType;
import android.stats.connectivity.ErrorCode;
@@ -81,16 +82,50 @@
private final SparseArray<NetworkTetheringReported.Builder> mBuilderMap = new SparseArray<>();
private final SparseArray<Long> mDownstreamStartTime = new SparseArray<Long>();
private final ArrayList<RecordUpstreamEvent> mUpstreamEventList = new ArrayList<>();
+ private final Context mContext;
+ private final Dependencies mDependencies;
private UpstreamType mCurrentUpstream = null;
private Long mCurrentUpStreamStartTime = 0L;
+ /**
+ * Dependencies of TetheringMetrics, for injection in tests.
+ */
+ @VisibleForTesting
+ public static class Dependencies {
+ /**
+ * @see TetheringStatsLog
+ */
+ public void write(NetworkTetheringReported reported) {
+ TetheringStatsLog.write(
+ TetheringStatsLog.NETWORK_TETHERING_REPORTED,
+ reported.getErrorCode().getNumber(),
+ reported.getDownstreamType().getNumber(),
+ reported.getUpstreamType().getNumber(),
+ reported.getUserType().getNumber(),
+ reported.getUpstreamEvents().toByteArray(),
+ reported.getDurationMillis());
+ }
+
+ /**
+ * @see System#currentTimeMillis()
+ */
+ public long timeNow() {
+ return System.currentTimeMillis();
+ }
+ }
/**
- * Return the current system time in milliseconds.
- * @return the current system time in milliseconds.
+ * Constructor for the TetheringMetrics class.
+ *
+ * @param context The Context object used to access system services.
*/
- public long timeNow() {
- return System.currentTimeMillis();
+ public TetheringMetrics(Context context) {
+ this(context, new Dependencies());
+ }
+
+ TetheringMetrics(Context context, Dependencies dependencies) {
+ mContext = context;
+ mDependencies = dependencies;
}
private static class RecordUpstreamEvent {
@@ -123,7 +158,7 @@
.setUpstreamEvents(UpstreamEvents.newBuilder())
.setDurationMillis(0);
mBuilderMap.put(downstreamType, statsBuilder);
- mDownstreamStartTime.put(downstreamType, timeNow());
+ mDownstreamStartTime.put(downstreamType, mDependencies.timeNow());
}
/**
@@ -149,7 +184,7 @@
UpstreamType upstream = transportTypeToUpstreamTypeEnum(ns);
if (upstream.equals(mCurrentUpstream)) return;
- final long newTime = timeNow();
+ final long newTime = mDependencies.timeNow();
if (mCurrentUpstream != null) {
mUpstreamEventList.add(new RecordUpstreamEvent(mCurrentUpStreamStartTime, newTime,
mCurrentUpstream));
@@ -206,7 +241,7 @@
event.mUpstreamType, 0L /* txBytes */, 0L /* rxBytes */);
}
final long startTime = Math.max(downstreamStartTime, mCurrentUpStreamStartTime);
- final long stopTime = timeNow();
+ final long stopTime = mDependencies.timeNow();
// Handle the last upstream event.
addUpstreamEvent(upstreamEventsBuilder, startTime, stopTime, mCurrentUpstream,
0L /* txBytes */, 0L /* rxBytes */);
@@ -248,15 +283,7 @@
@VisibleForTesting
public void write(@NonNull final NetworkTetheringReported reported) {
final byte[] upstreamEvents = reported.getUpstreamEvents().toByteArray();
-
- TetheringStatsLog.write(
- TetheringStatsLog.NETWORK_TETHERING_REPORTED,
- reported.getErrorCode().getNumber(),
- reported.getDownstreamType().getNumber(),
- reported.getUpstreamType().getNumber(),
- reported.getUserType().getNumber(),
- upstreamEvents,
- reported.getDurationMillis());
+ mDependencies.write(reported);
if (DBG) {
Log.d(
TAG,
@@ -374,4 +401,22 @@
return UpstreamType.UT_UNKNOWN;
}
+
+ /**
+ * Check whether tethering metrics' data usage can be collected for a given upstream type.
+ *
+ * @param type the upstream type
+ */
+ public static boolean isUsageSupportedForUpstreamType(@NonNull UpstreamType type) {
+ switch(type) {
+ case UT_CELLULAR:
+ case UT_WIFI:
+ case UT_BLUETOOTH:
+ case UT_ETHERNET:
+ return true;
+ default:
+ break;
+ }
+ return false;
+ }
}
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index 748f23c..177296a 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -51,6 +51,7 @@
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
@@ -174,8 +175,7 @@
@Mock private RouterAdvertisementDaemon mRaDaemon;
@Mock private IpServer.Dependencies mDependencies;
@Mock private PrivateAddressCoordinator mAddressCoordinator;
- private final LateSdk<RoutingCoordinatorManager> mRoutingCoordinatorManager =
- new LateSdk<>(SdkLevel.isAtLeastS() ? mock(RoutingCoordinatorManager.class) : null);
+ @Mock private RoutingCoordinatorManager mRoutingCoordinatorManager;
@Mock private NetworkStatsManager mStatsManager;
@Mock private TetheringConfiguration mTetherConfig;
@Mock private TetheringMetrics mTetheringMetrics;
@@ -280,24 +280,6 @@
when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(DEFAULT_USING_BPF_OFFLOAD);
when(mTetherConfig.useLegacyDhcpServer()).thenReturn(false /* default value */);
- // Simulate the behavior of RoutingCoordinator
- if (null != mRoutingCoordinatorManager.value) {
- doAnswer(it -> {
- final String fromIface = (String) it.getArguments()[0];
- final String toIface = (String) it.getArguments()[1];
- mNetd.tetherAddForward(fromIface, toIface);
- mNetd.ipfwdAddInterfaceForward(fromIface, toIface);
- return null;
- }).when(mRoutingCoordinatorManager.value).addInterfaceForward(any(), any());
- doAnswer(it -> {
- final String fromIface = (String) it.getArguments()[0];
- final String toIface = (String) it.getArguments()[1];
- mNetd.ipfwdRemoveInterfaceForward(fromIface, toIface);
- mNetd.tetherRemoveForward(fromIface, toIface);
- return null;
- }).when(mRoutingCoordinatorManager.value).removeInterfaceForward(any(), any());
- }
-
setUpDhcpServer();
}
@@ -455,105 +437,114 @@
// Telling the state machine about its upstream interface triggers
// a little more configuration.
dispatchTetherConnectionChanged(UPSTREAM_IFACE);
- InOrder inOrder = inOrder(mNetd, mBpfCoordinator);
+ InOrder inOrder = inOrder(mBpfCoordinator, mRoutingCoordinatorManager);
// Add the forwarding pair <IFACE_NAME, UPSTREAM_IFACE>.
inOrder.verify(mBpfCoordinator).maybeAddUpstreamToLookupTable(UPSTREAM_IFINDEX,
UPSTREAM_IFACE);
inOrder.verify(mBpfCoordinator).maybeAttachProgram(IFACE_NAME, UPSTREAM_IFACE);
- inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE);
- inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
+ inOrder.verify(mRoutingCoordinatorManager).addInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
- verifyNoMoreInteractions(mNetd, mCallback, mBpfCoordinator);
+ verifyNoMoreInteractions(mCallback, mBpfCoordinator, mRoutingCoordinatorManager);
}
@Test
public void handlesChangingUpstream() throws Exception {
initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
+ clearInvocations(mBpfCoordinator, mRoutingCoordinatorManager);
dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
- InOrder inOrder = inOrder(mNetd, mBpfCoordinator);
+ InOrder inOrder = inOrder(mBpfCoordinator, mRoutingCoordinatorManager);
// Remove the forwarding pair <IFACE_NAME, UPSTREAM_IFACE>.
inOrder.verify(mBpfCoordinator).maybeDetachProgram(IFACE_NAME, UPSTREAM_IFACE);
- inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
- inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
+ inOrder.verify(mRoutingCoordinatorManager)
+ .removeInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
// Add the forwarding pair <IFACE_NAME, UPSTREAM_IFACE2>.
inOrder.verify(mBpfCoordinator).maybeAddUpstreamToLookupTable(UPSTREAM_IFINDEX2,
UPSTREAM_IFACE2);
inOrder.verify(mBpfCoordinator).maybeAttachProgram(IFACE_NAME, UPSTREAM_IFACE2);
- inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
- inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
+ inOrder.verify(mRoutingCoordinatorManager).addInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
- verifyNoMoreInteractions(mNetd, mCallback, mBpfCoordinator);
+ verifyNoMoreInteractions(mCallback, mBpfCoordinator, mRoutingCoordinatorManager);
}
@Test
public void handlesChangingUpstreamNatFailure() throws Exception {
initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE);
- doThrow(RemoteException.class).when(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
+ doThrow(RuntimeException.class)
+ .when(mRoutingCoordinatorManager)
+ .addInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
- InOrder inOrder = inOrder(mNetd, mBpfCoordinator);
+ InOrder inOrder = inOrder(mBpfCoordinator, mRoutingCoordinatorManager);
// Remove the forwarding pair <IFACE_NAME, UPSTREAM_IFACE>.
inOrder.verify(mBpfCoordinator).maybeDetachProgram(IFACE_NAME, UPSTREAM_IFACE);
- inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
- inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
+ inOrder.verify(mRoutingCoordinatorManager)
+ .removeInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
// Add the forwarding pair <IFACE_NAME, UPSTREAM_IFACE2> and expect that failed on
- // tetherAddForward.
+ // addInterfaceForward.
inOrder.verify(mBpfCoordinator).maybeAddUpstreamToLookupTable(UPSTREAM_IFINDEX2,
UPSTREAM_IFACE2);
inOrder.verify(mBpfCoordinator).maybeAttachProgram(IFACE_NAME, UPSTREAM_IFACE2);
- inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
+ inOrder.verify(mRoutingCoordinatorManager).addInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
// Remove the forwarding pair <IFACE_NAME, UPSTREAM_IFACE2> to fallback.
inOrder.verify(mBpfCoordinator).maybeDetachProgram(IFACE_NAME, UPSTREAM_IFACE2);
- inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
- inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE2);
+ inOrder.verify(mRoutingCoordinatorManager)
+ .removeInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
}
@Test
public void handlesChangingUpstreamInterfaceForwardingFailure() throws Exception {
initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE);
- doThrow(RemoteException.class).when(mNetd).ipfwdAddInterfaceForward(
- IFACE_NAME, UPSTREAM_IFACE2);
+ doThrow(RuntimeException.class)
+ .when(mRoutingCoordinatorManager)
+ .addInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
- InOrder inOrder = inOrder(mNetd, mBpfCoordinator);
+ InOrder inOrder = inOrder(mBpfCoordinator, mRoutingCoordinatorManager);
// Remove the forwarding pair <IFACE_NAME, UPSTREAM_IFACE>.
inOrder.verify(mBpfCoordinator).maybeDetachProgram(IFACE_NAME, UPSTREAM_IFACE);
- inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
- inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
+ inOrder.verify(mRoutingCoordinatorManager)
+ .removeInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
// Add the forwarding pair <IFACE_NAME, UPSTREAM_IFACE2> and expect that failed on
// ipfwdAddInterfaceForward.
inOrder.verify(mBpfCoordinator).maybeAddUpstreamToLookupTable(UPSTREAM_IFINDEX2,
UPSTREAM_IFACE2);
inOrder.verify(mBpfCoordinator).maybeAttachProgram(IFACE_NAME, UPSTREAM_IFACE2);
- inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
- inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
+ inOrder.verify(mRoutingCoordinatorManager).addInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
// Remove the forwarding pair <IFACE_NAME, UPSTREAM_IFACE2> to fallback.
inOrder.verify(mBpfCoordinator).maybeDetachProgram(IFACE_NAME, UPSTREAM_IFACE2);
- inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
- inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE2);
+ inOrder.verify(mRoutingCoordinatorManager)
+ .removeInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
}
@Test
public void canUnrequestTetheringWithUpstream() throws Exception {
initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
+ clearInvocations(
+ mNetd, mCallback, mAddressCoordinator, mBpfCoordinator, mRoutingCoordinatorManager);
dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED);
- InOrder inOrder = inOrder(mNetd, mCallback, mAddressCoordinator, mBpfCoordinator);
+ InOrder inOrder =
+ inOrder(
+ mNetd,
+ mCallback,
+ mAddressCoordinator,
+ mBpfCoordinator,
+ mRoutingCoordinatorManager);
inOrder.verify(mBpfCoordinator).maybeDetachProgram(IFACE_NAME, UPSTREAM_IFACE);
- inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
- inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
+ inOrder.verify(mRoutingCoordinatorManager)
+ .removeInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mBpfCoordinator).updateIpv6UpstreamInterface(
mIpServer, NO_UPSTREAM, NO_PREFIXES);
// When tethering stops, upstream interface is set to zero and thus clearing all upstream
@@ -572,7 +563,8 @@
mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
inOrder.verify(mCallback).updateLinkProperties(
eq(mIpServer), any(LinkProperties.class));
- verifyNoMoreInteractions(mNetd, mCallback, mAddressCoordinator, mBpfCoordinator);
+ verifyNoMoreInteractions(
+ mNetd, mCallback, mAddressCoordinator, mBpfCoordinator, mRoutingCoordinatorManager);
}
@Test
@@ -624,10 +616,14 @@
public void shouldTearDownUsbOnUpstreamError() throws Exception {
initTetheredStateMachine(TETHERING_USB, null);
- doThrow(RemoteException.class).when(mNetd).tetherAddForward(anyString(), anyString());
+ doThrow(RuntimeException.class)
+ .when(mRoutingCoordinatorManager)
+ .addInterfaceForward(anyString(), anyString());
dispatchTetherConnectionChanged(UPSTREAM_IFACE);
- InOrder usbTeardownOrder = inOrder(mNetd, mCallback);
- usbTeardownOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE);
+ InOrder usbTeardownOrder = inOrder(mNetd, mCallback, mRoutingCoordinatorManager);
+ usbTeardownOrder
+ .verify(mRoutingCoordinatorManager)
+ .addInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
usbTeardownOrder.verify(mNetd, times(2)).interfaceSetCfg(
argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
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 df7141f..6ba5d48 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -191,7 +191,6 @@
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.InterfaceParams;
import com.android.net.module.util.RoutingCoordinatorManager;
-import com.android.net.module.util.SdkUtil.LateSdk;
import com.android.net.module.util.SharedLog;
import com.android.net.module.util.ip.IpNeighborMonitor;
import com.android.networkstack.apishim.common.BluetoothPanShim;
@@ -292,6 +291,7 @@
@Mock private BluetoothPanShim mBluetoothPanShim;
@Mock private TetheredInterfaceRequestShim mTetheredInterfaceRequestShim;
@Mock private TetheringMetrics mTetheringMetrics;
+ @Mock private RoutingCoordinatorManager mRoutingCoordinatorManager;
private final MockIpServerDependencies mIpServerDependencies =
spy(new MockIpServerDependencies());
@@ -484,10 +484,10 @@
return mEntitleMgr;
}
- @Nullable
@Override
- public LateSdk<RoutingCoordinatorManager> getRoutingCoordinator(final Context context) {
- return new LateSdk<>(null);
+ public RoutingCoordinatorManager getRoutingCoordinator(final Context context,
+ SharedLog log) {
+ return mRoutingCoordinatorManager;
}
@Override
@@ -498,7 +498,7 @@
}
@Override
- public INetd getINetd(Context context) {
+ public INetd getINetd(Context context, SharedLog log) {
return mNetd;
}
@@ -528,7 +528,7 @@
}
@Override
- public TetheringMetrics makeTetheringMetrics() {
+ public TetheringMetrics makeTetheringMetrics(Context ctx) {
return mTetheringMetrics;
}
@@ -1082,8 +1082,8 @@
UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState();
runUsbTethering(upstreamState);
- verify(mNetd, times(1)).tetherAddForward(TEST_RNDIS_IFNAME, TEST_MOBILE_IFNAME);
- verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_RNDIS_IFNAME, TEST_MOBILE_IFNAME);
+ verify(mRoutingCoordinatorManager, times(1))
+ .addInterfaceForward(TEST_RNDIS_IFNAME, TEST_MOBILE_IFNAME);
sendIPv6TetherUpdates(upstreamState);
assertSetIfaceToDadProxy(0 /* numOfCalls */, "" /* ifaceName */);
@@ -1111,8 +1111,8 @@
UpstreamNetworkState upstreamState = buildMobileIPv6UpstreamState();
runUsbTethering(upstreamState);
- verify(mNetd, times(1)).tetherAddForward(TEST_RNDIS_IFNAME, TEST_MOBILE_IFNAME);
- verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_RNDIS_IFNAME, TEST_MOBILE_IFNAME);
+ verify(mRoutingCoordinatorManager, times(1))
+ .addInterfaceForward(TEST_RNDIS_IFNAME, TEST_MOBILE_IFNAME);
sendIPv6TetherUpdates(upstreamState);
// TODO: add interfaceParams to compare in verify.
@@ -1127,8 +1127,8 @@
UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState();
runUsbTethering(upstreamState);
- verify(mNetd, times(1)).tetherAddForward(TEST_RNDIS_IFNAME, TEST_MOBILE_IFNAME);
- verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_RNDIS_IFNAME, TEST_MOBILE_IFNAME);
+ verify(mRoutingCoordinatorManager, times(1))
+ .addInterfaceForward(TEST_RNDIS_IFNAME, TEST_MOBILE_IFNAME);
verify(mRouterAdvertisementDaemon, times(1)).start();
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
any(), any());
@@ -1145,13 +1145,12 @@
UpstreamNetworkState upstreamState = buildMobile464xlatUpstreamState();
runUsbTethering(upstreamState);
- verify(mNetd, times(1)).tetherAddForward(TEST_RNDIS_IFNAME, TEST_XLAT_MOBILE_IFNAME);
- verify(mNetd, times(1)).tetherAddForward(TEST_RNDIS_IFNAME, TEST_MOBILE_IFNAME);
+ verify(mRoutingCoordinatorManager, times(1))
+ .addInterfaceForward(TEST_RNDIS_IFNAME, TEST_XLAT_MOBILE_IFNAME);
+ verify(mRoutingCoordinatorManager, times(1))
+ .addInterfaceForward(TEST_RNDIS_IFNAME, TEST_MOBILE_IFNAME);
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
any(), any());
- verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_RNDIS_IFNAME,
- TEST_XLAT_MOBILE_IFNAME);
- verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_RNDIS_IFNAME, TEST_MOBILE_IFNAME);
sendIPv6TetherUpdates(upstreamState);
assertSetIfaceToDadProxy(1 /* numOfCalls */, TEST_MOBILE_IFNAME /* ifaceName */);
@@ -1172,10 +1171,10 @@
UpstreamNetworkState upstreamState = buildMobileIPv6UpstreamState();
runUsbTethering(upstreamState);
- verify(mNetd, times(1)).tetherAddForward(TEST_NCM_IFNAME, TEST_MOBILE_IFNAME);
+ verify(mRoutingCoordinatorManager, times(1))
+ .addInterfaceForward(TEST_NCM_IFNAME, TEST_MOBILE_IFNAME);
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
any(), any());
- verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_NCM_IFNAME, TEST_MOBILE_IFNAME);
// Then 464xlat comes up
upstreamState = buildMobile464xlatUpstreamState();
@@ -1187,12 +1186,11 @@
mLooper.dispatchAll();
// Forwarding is added for 464xlat
- verify(mNetd, times(1)).tetherAddForward(TEST_NCM_IFNAME, TEST_XLAT_MOBILE_IFNAME);
- verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_NCM_IFNAME,
- TEST_XLAT_MOBILE_IFNAME);
+ verify(mRoutingCoordinatorManager, times(1))
+ .addInterfaceForward(TEST_NCM_IFNAME, TEST_XLAT_MOBILE_IFNAME);
// Forwarding was not re-added for v6 (still times(1))
- verify(mNetd, times(1)).tetherAddForward(TEST_NCM_IFNAME, TEST_MOBILE_IFNAME);
- verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_NCM_IFNAME, TEST_MOBILE_IFNAME);
+ verify(mRoutingCoordinatorManager, times(1))
+ .addInterfaceForward(TEST_NCM_IFNAME, TEST_MOBILE_IFNAME);
// DHCP not restarted on downstream (still times(1))
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
any(), any());
@@ -3435,8 +3433,7 @@
runUsbTethering(upstreamState);
verify(mNetd).interfaceGetList();
- verify(mNetd).tetherAddForward(expectedIface, TEST_MOBILE_IFNAME);
- verify(mNetd).ipfwdAddInterfaceForward(expectedIface, TEST_MOBILE_IFNAME);
+ verify(mRoutingCoordinatorManager).addInterfaceForward(expectedIface, TEST_MOBILE_IFNAME);
verify(mRouterAdvertisementDaemon).start();
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS)).startWithCallbacks(
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java
index e2c924c..7cef9cb 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java
@@ -46,10 +46,11 @@
import static android.net.TetheringManager.TETHER_ERROR_UNSUPPORTED;
import static android.net.TetheringManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
+import android.content.Context;
import android.net.NetworkCapabilities;
import android.stats.connectivity.DownstreamType;
import android.stats.connectivity.ErrorCode;
@@ -60,10 +61,12 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.networkstack.tethering.UpstreamNetworkState;
+import com.android.networkstack.tethering.metrics.TetheringMetrics.Dependencies;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class)
@@ -76,28 +79,23 @@
private static final long TEST_START_TIME = 1670395936033L;
private static final long SECOND_IN_MILLIS = 1_000L;
+ @Mock private Context mContext;
+ @Mock private Dependencies mDeps;
+
private TetheringMetrics mTetheringMetrics;
private final NetworkTetheringReported.Builder mStatsBuilder =
NetworkTetheringReported.newBuilder();
private long mElapsedRealtime;
- private class MockTetheringMetrics extends TetheringMetrics {
- @Override
- public void write(final NetworkTetheringReported reported) {}
- @Override
- public long timeNow() {
- return currentTimeMillis();
- }
- }
-
private long currentTimeMillis() {
return TEST_START_TIME + mElapsedRealtime;
}
private void incrementCurrentTime(final long duration) {
mElapsedRealtime += duration;
- mTetheringMetrics.timeNow();
+ final long currentTimeMillis = currentTimeMillis();
+ doReturn(currentTimeMillis).when(mDeps).timeNow();
}
private long getElapsedRealtime() {
@@ -106,12 +104,14 @@
private void clearElapsedRealtime() {
mElapsedRealtime = 0;
+ doReturn(TEST_START_TIME).when(mDeps).timeNow();
}
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mTetheringMetrics = spy(new MockTetheringMetrics());
+ doReturn(TEST_START_TIME).when(mDeps).timeNow();
+ mTetheringMetrics = new TetheringMetrics(mContext, mDeps);
mElapsedRealtime = 0L;
}
@@ -126,7 +126,7 @@
.setUpstreamEvents(upstreamEvents)
.setDurationMillis(duration)
.build();
- verify(mTetheringMetrics).write(expectedReport);
+ verify(mDeps).write(expectedReport);
}
private void updateErrorAndSendReport(final int downstream, final int error) {
@@ -162,6 +162,7 @@
private void runDownstreamTypesTest(final int type, final DownstreamType expectedResult)
throws Exception {
+ mTetheringMetrics = new TetheringMetrics(mContext, mDeps);
mTetheringMetrics.createBuilder(type, TEST_CALLER_PKG);
final long duration = 2 * SECOND_IN_MILLIS;
incrementCurrentTime(duration);
@@ -172,9 +173,7 @@
verifyReport(expectedResult, ErrorCode.EC_NO_ERROR, UserType.USER_UNKNOWN,
upstreamEvents, getElapsedRealtime());
- reset(mTetheringMetrics);
clearElapsedRealtime();
- mTetheringMetrics.cleanup();
}
@Test
@@ -189,6 +188,7 @@
private void runErrorCodesTest(final int errorCode, final ErrorCode expectedResult)
throws Exception {
+ mTetheringMetrics = new TetheringMetrics(mContext, mDeps);
mTetheringMetrics.createBuilder(TETHERING_WIFI, TEST_CALLER_PKG);
mTetheringMetrics.maybeUpdateUpstreamType(buildUpstreamState(TRANSPORT_WIFI));
final long duration = 2 * SECOND_IN_MILLIS;
@@ -199,9 +199,7 @@
addUpstreamEvent(upstreamEvents, UpstreamType.UT_WIFI, duration, 0L, 0L);
verifyReport(DownstreamType.DS_TETHERING_WIFI, expectedResult, UserType.USER_UNKNOWN,
upstreamEvents, getElapsedRealtime());
- reset(mTetheringMetrics);
clearElapsedRealtime();
- mTetheringMetrics.cleanup();
}
@Test
@@ -231,6 +229,7 @@
private void runUserTypesTest(final String callerPkg, final UserType expectedResult)
throws Exception {
+ mTetheringMetrics = new TetheringMetrics(mContext, mDeps);
mTetheringMetrics.createBuilder(TETHERING_WIFI, callerPkg);
final long duration = 1 * SECOND_IN_MILLIS;
incrementCurrentTime(duration);
@@ -241,9 +240,7 @@
addUpstreamEvent(upstreamEvents, UpstreamType.UT_NO_NETWORK, duration, 0L, 0L);
verifyReport(DownstreamType.DS_TETHERING_WIFI, ErrorCode.EC_NO_ERROR, expectedResult,
upstreamEvents, getElapsedRealtime());
- reset(mTetheringMetrics);
clearElapsedRealtime();
- mTetheringMetrics.cleanup();
}
@Test
@@ -256,6 +253,7 @@
private void runUpstreamTypesTest(final UpstreamNetworkState ns,
final UpstreamType expectedResult) throws Exception {
+ mTetheringMetrics = new TetheringMetrics(mContext, mDeps);
mTetheringMetrics.createBuilder(TETHERING_WIFI, TEST_CALLER_PKG);
mTetheringMetrics.maybeUpdateUpstreamType(ns);
final long duration = 2 * SECOND_IN_MILLIS;
@@ -266,9 +264,7 @@
addUpstreamEvent(upstreamEvents, expectedResult, duration, 0L, 0L);
verifyReport(DownstreamType.DS_TETHERING_WIFI, ErrorCode.EC_NO_ERROR,
UserType.USER_UNKNOWN, upstreamEvents, getElapsedRealtime());
- reset(mTetheringMetrics);
clearElapsedRealtime();
- mTetheringMetrics.cleanup();
}
@Test
@@ -379,4 +375,21 @@
UserType.USER_SETTINGS, upstreamEvents,
currentTimeMillis() - wifiTetheringStartTime);
}
+
+ private void runUsageSupportedForUpstreamTypeTest(final UpstreamType upstreamType,
+ final boolean isSupported) {
+ final boolean result = TetheringMetrics.isUsageSupportedForUpstreamType(upstreamType);
+ assertEquals(isSupported, result);
+ }
+
+ @Test
+ public void testUsageSupportedForUpstreamTypeTest() {
+ runUsageSupportedForUpstreamTypeTest(UpstreamType.UT_CELLULAR, true /* isSupported */);
+ runUsageSupportedForUpstreamTypeTest(UpstreamType.UT_WIFI, true /* isSupported */);
+ runUsageSupportedForUpstreamTypeTest(UpstreamType.UT_BLUETOOTH, true /* isSupported */);
+ runUsageSupportedForUpstreamTypeTest(UpstreamType.UT_ETHERNET, true /* isSupported */);
+ runUsageSupportedForUpstreamTypeTest(UpstreamType.UT_WIFI_AWARE, false /* isSupported */);
+ runUsageSupportedForUpstreamTypeTest(UpstreamType.UT_LOWPAN, false /* isSupported */);
+ runUsageSupportedForUpstreamTypeTest(UpstreamType.UT_UNKNOWN, false /* isSupported */);
+ }
}
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index 5e41dd9..a6a967b 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -4489,6 +4489,7 @@
public static final int CALLBACK_BLK_CHANGED = 11;
/** @hide */
public static final int CALLBACK_LOCAL_NETWORK_INFO_CHANGED = 12;
+ // When adding new IDs, note CallbackQueue assumes callback IDs are at most 16 bits.
/** @hide */
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
index fcfb15f..c575d40 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
@@ -29,7 +29,9 @@
import android.util.ArrayMap;
import android.util.Log;
+import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.SharedLog;
+import com.android.server.connectivity.mdns.util.MdnsUtils;
import java.io.IOException;
import java.net.DatagramPacket;
@@ -225,6 +227,12 @@
Log.wtf(TAG, "No mDns packets to send");
return;
}
+ // Check all packets with the same address
+ if (!MdnsUtils.checkAllPacketsWithSameAddress(packets)) {
+ Log.wtf(TAG, "Some mDNS packets have a different target address. addresses="
+ + CollectionUtils.map(packets, DatagramPacket::getSocketAddress));
+ return;
+ }
final boolean isIpv6 = ((InetSocketAddress) packets.get(0).getSocketAddress())
.getAddress() instanceof Inet6Address;
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
index b3bdbe0..643430a 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
@@ -303,8 +303,8 @@
serviceCache.unregisterServiceExpiredCallback(cacheKey);
}
- private static MdnsServiceInfo buildMdnsServiceInfoFromResponse(
- @NonNull MdnsResponse response, @NonNull String[] serviceTypeLabels) {
+ private static MdnsServiceInfo buildMdnsServiceInfoFromResponse(@NonNull MdnsResponse response,
+ @NonNull String[] serviceTypeLabels, long elapsedRealtimeMillis) {
String[] hostName = null;
int port = 0;
if (response.hasServiceRecord()) {
@@ -351,7 +351,7 @@
textEntries,
response.getInterfaceIndex(),
response.getNetwork(),
- now.plusMillis(response.getMinRemainingTtl(now.toEpochMilli())));
+ now.plusMillis(response.getMinRemainingTtl(elapsedRealtimeMillis)));
}
private List<MdnsResponse> getExistingServices() {
@@ -380,8 +380,8 @@
if (existingInfo == null) {
for (MdnsResponse existingResponse : serviceCache.getCachedServices(cacheKey)) {
if (!responseMatchesOptions(existingResponse, searchOptions)) continue;
- final MdnsServiceInfo info =
- buildMdnsServiceInfoFromResponse(existingResponse, serviceTypeLabels);
+ final MdnsServiceInfo info = buildMdnsServiceInfoFromResponse(
+ existingResponse, serviceTypeLabels, clock.elapsedRealtime());
listener.onServiceNameDiscovered(info, true /* isServiceFromCache */);
listenerInfo.setServiceDiscovered(info.getServiceInstanceName());
if (existingResponse.isComplete()) {
@@ -561,7 +561,7 @@
if (response.getServiceInstanceName() != null) {
listeners.valueAt(i).unsetServiceDiscovered(response.getServiceInstanceName());
final MdnsServiceInfo serviceInfo = buildMdnsServiceInfoFromResponse(
- response, serviceTypeLabels);
+ response, serviceTypeLabels, clock.elapsedRealtime());
if (response.isComplete()) {
sharedLog.log(message + ". onServiceRemoved: " + serviceInfo);
listener.onServiceRemoved(serviceInfo);
@@ -605,8 +605,8 @@
+ " %b, responseIsComplete: %b",
serviceInstanceName, newInCache, serviceBecomesComplete,
response.isComplete()));
- MdnsServiceInfo serviceInfo =
- buildMdnsServiceInfoFromResponse(response, serviceTypeLabels);
+ final MdnsServiceInfo serviceInfo = buildMdnsServiceInfoFromResponse(
+ response, serviceTypeLabels, clock.elapsedRealtime());
for (int i = 0; i < listeners.size(); i++) {
// If a service stops matching the options (currently can only happen if it loses a
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java
index 9cfcba1..17e5b31 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java
@@ -28,7 +28,9 @@
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.SharedLog;
+import com.android.server.connectivity.mdns.util.MdnsUtils;
import java.io.IOException;
import java.net.DatagramPacket;
@@ -249,6 +251,12 @@
Log.wtf(TAG, "No mDns packets to send");
return;
}
+ // Check all packets with the same address
+ if (!MdnsUtils.checkAllPacketsWithSameAddress(packets)) {
+ Log.wtf(TAG, "Some mDNS packets have a different target address. addresses="
+ + CollectionUtils.map(packets, DatagramPacket::getSocketAddress));
+ return;
+ }
final boolean isIpv4 = ((InetSocketAddress) packets.get(0).getSocketAddress())
.getAddress() instanceof Inet4Address;
diff --git a/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java b/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
index 3c11a24..226867f 100644
--- a/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
+++ b/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
@@ -34,6 +34,7 @@
import java.io.IOException;
import java.net.DatagramPacket;
+import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
@@ -361,4 +362,23 @@
return SystemClock.elapsedRealtime();
}
}
+
+ /**
+ * Check all DatagramPackets with the same destination address.
+ */
+ public static boolean checkAllPacketsWithSameAddress(List<DatagramPacket> packets) {
+ // No packet for address check
+ if (packets.isEmpty()) {
+ return true;
+ }
+
+ final InetAddress address =
+ ((InetSocketAddress) packets.get(0).getSocketAddress()).getAddress();
+ for (DatagramPacket packet : packets) {
+ if (!address.equals(((InetSocketAddress) packet.getSocketAddress()).getAddress())) {
+ return false;
+ }
+ }
+ return true;
+ }
}
\ No newline at end of file
diff --git a/service/ServiceConnectivityResources/res/values/config_thread.xml b/service/ServiceConnectivityResources/res/values/config_thread.xml
index 02a9ce6..4027038 100644
--- a/service/ServiceConnectivityResources/res/values/config_thread.xml
+++ b/service/ServiceConnectivityResources/res/values/config_thread.xml
@@ -54,9 +54,21 @@
-->
<string translatable="false" name="config_thread_model_name">Thread Border Router</string>
- <!-- Whether the Thread network will be managed by the Google Home ecosystem. When this value
- is set, a TXT entry "vgh=0" or "vgh=1" will be added to the "_mehscop._udp" mDNS service
- respectively (The TXT value is a string).
+ <!-- Specifies vendor-specific mDNS TXT entries which will be included in the "_meshcop._udp"
+ service. The TXT entries list MUST conform to the format requirement in RFC 6763 section 6. For
+ example, the key and value of each TXT entry MUST be separated with "=". If the value length is
+ 0, the trailing "=" may be omitted. Additionally, the TXT keys MUST start with "v" and be at
+ least 2 characters.
+
+ Note, do not include credentials in any of the TXT entries - they will be advertised on Wi-Fi
+ or Ethernet link.
+
+ An example config can be:
+ <string-array name="config_thread_mdns_vendor_specific_txts">
+ <item>vab=123</item>
+ <item>vcd</item>
+ </string-array>
-->
- <bool name="config_thread_managed_by_google_home">false</bool>
+ <string-array name="config_thread_mdns_vendor_specific_txts">
+ </string-array>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values/overlayable.xml b/service/ServiceConnectivityResources/res/values/overlayable.xml
index 158b0c8..fbaae05 100644
--- a/service/ServiceConnectivityResources/res/values/overlayable.xml
+++ b/service/ServiceConnectivityResources/res/values/overlayable.xml
@@ -51,6 +51,7 @@
<item type="string" name="config_thread_vendor_name" />
<item type="string" name="config_thread_vendor_oui" />
<item type="string" name="config_thread_model_name" />
+ <item type="array" name="config_thread_mdns_vendor_specific_txts" />
</policy>
</overlayable>
</resources>
diff --git a/service/src/com/android/server/CallbackQueue.java b/service/src/com/android/server/CallbackQueue.java
new file mode 100644
index 0000000..060a984
--- /dev/null
+++ b/service/src/com/android/server/CallbackQueue.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.annotation.NonNull;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+
+import com.android.net.module.util.GrowingIntArray;
+
+/**
+ * A utility class to add/remove {@link NetworkCallback}s from a queue.
+ *
+ * <p>This is intended to be used as a temporary builder to create/modify callbacks stored in an int
+ * array for memory efficiency.
+ *
+ * <p>Intended usage:
+ * <pre>
+ * final CallbackQueue queue = new CallbackQueue(storedCallbacks);
+ * queue.forEach(netId, callbackId -> { [...] });
+ * queue.addCallback(netId, callbackId);
+ * [...]
+ * queue.shrinkToLength();
+ * storedCallbacks = queue.getBackingArray();
+ * </pre>
+ *
+ * <p>This class is not thread-safe.
+ */
+public class CallbackQueue extends GrowingIntArray {
+ public CallbackQueue(int[] initialCallbacks) {
+ super(initialCallbacks);
+ }
+
+ /**
+ * Get a callback int from netId and callbackId.
+ *
+ * <p>The first 16 bits of each int is the netId; the last 16 bits are the callback index.
+ */
+ private static int getCallbackInt(int netId, int callbackId) {
+ return (netId << 16) | (callbackId & 0xffff);
+ }
+
+ private static int getNetId(int callbackInt) {
+ return callbackInt >>> 16;
+ }
+
+ private static int getCallbackId(int callbackInt) {
+ return callbackInt & 0xffff;
+ }
+
+ /**
+ * A consumer interface for {@link #forEach(CallbackConsumer)}.
+ *
+ * <p>This is similar to a BiConsumer<Integer, Integer>, but avoids the boxing cost.
+ */
+ public interface CallbackConsumer {
+ /**
+ * Method called on each callback in the queue.
+ */
+ void accept(int netId, int callbackId);
+ }
+
+ /**
+ * Iterate over all callbacks in the queue.
+ */
+ public void forEach(@NonNull CallbackConsumer consumer) {
+ forEach(value -> {
+ final int netId = getNetId(value);
+ final int callbackId = getCallbackId(value);
+ consumer.accept(netId, callbackId);
+ });
+ }
+
+ /**
+ * Indicates whether the queue contains a callback for the given (netId, callbackId).
+ */
+ public boolean hasCallback(int netId, int callbackId) {
+ return contains(getCallbackInt(netId, callbackId));
+ }
+
+ /**
+ * Remove all callbacks for the given netId.
+ *
+ * @return true if at least one callback was removed.
+ */
+ public boolean removeCallbacksForNetId(int netId) {
+ return removeValues(cb -> getNetId(cb) == netId);
+ }
+
+ /**
+ * Remove all callbacks for the given netId and callbackId.
+ * @return true if at least one callback was removed.
+ */
+ public boolean removeCallbacks(int netId, int callbackId) {
+ final int cbInt = getCallbackInt(netId, callbackId);
+ return removeValues(cb -> cb == cbInt);
+ }
+
+ /**
+ * Add a callback at the end of the queue.
+ */
+ public void addCallback(int netId, int callbackId) {
+ add(getCallbackInt(netId, callbackId));
+ }
+
+ @Override
+ protected String valueToString(int item) {
+ final int callbackId = getCallbackId(item);
+ final int netId = getNetId(item);
+ return ConnectivityManager.getCallbackName(callbackId) + "(" + netId + ")";
+ }
+}
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index f015742..4d4dacf 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -145,6 +145,7 @@
import static com.android.net.module.util.PermissionUtils.hasAnyPermissionOf;
import static com.android.server.ConnectivityStatsLog.CONNECTIVITY_STATE_SAMPLE;
import static com.android.server.connectivity.ConnectivityFlags.DELAY_DESTROY_SOCKETS;
+import static com.android.server.connectivity.ConnectivityFlags.QUEUE_CALLBACKS_FOR_FROZEN_APPS;
import static com.android.server.connectivity.ConnectivityFlags.REQUEST_RESTRICTED_WIFI;
import static com.android.server.connectivity.ConnectivityFlags.INGRESS_TO_VPN_ADDRESS_FILTERING;
@@ -329,9 +330,9 @@
import com.android.net.module.util.LinkPropertiesUtils.CompareOrUpdateResult;
import com.android.net.module.util.LinkPropertiesUtils.CompareResult;
import com.android.net.module.util.LocationPermissionChecker;
-import com.android.net.module.util.RoutingCoordinatorService;
import com.android.net.module.util.PerUidCounter;
import com.android.net.module.util.PermissionUtils;
+import com.android.net.module.util.RoutingCoordinatorService;
import com.android.net.module.util.TcUtils;
import com.android.net.module.util.netlink.InetDiagMessage;
import com.android.networkstack.apishim.BroadcastOptionsShimImpl;
@@ -510,6 +511,9 @@
private final boolean mUseDeclaredMethodsForCallbacksEnabled;
+ // Flag to delay callbacks for frozen apps, suppressing duplicate and stale callbacks.
+ private final boolean mQueueCallbacksForFrozenApps;
+
/**
* Uids ConnectivityService tracks blocked status of to send blocked status callbacks.
* Key is uid based on mAsUid of registered networkRequestInfo
@@ -1873,6 +1877,9 @@
context, ConnectivityFlags.BACKGROUND_FIREWALL_CHAIN);
mUseDeclaredMethodsForCallbacksEnabled = mDeps.isFeatureEnabled(context,
ConnectivityFlags.USE_DECLARED_METHODS_FOR_CALLBACKS);
+ // registerUidFrozenStateChangedCallback is only available on U+
+ mQueueCallbacksForFrozenApps = mDeps.isAtLeastU()
+ && mDeps.isFeatureEnabled(context, QUEUE_CALLBACKS_FOR_FROZEN_APPS);
mCarrierPrivilegeAuthenticator = mDeps.makeCarrierPrivilegeAuthenticator(
mContext, mTelephonyManager, mRequestRestrictedWifiEnabled,
this::handleUidCarrierPrivilegesLost, mHandler);
@@ -2028,7 +2035,7 @@
mDelayDestroySockets = mDeps.isFeatureNotChickenedOut(context, DELAY_DESTROY_SOCKETS);
mAllowSysUiConnectivityReports = mDeps.isFeatureNotChickenedOut(
mContext, ALLOW_SYSUI_CONNECTIVITY_REPORTS);
- if (mDestroyFrozenSockets) {
+ if (mDestroyFrozenSockets || mQueueCallbacksForFrozenApps) {
final UidFrozenStateChangedCallback frozenStateChangedCallback =
new UidFrozenStateChangedCallback() {
@Override
@@ -3464,6 +3471,14 @@
private void handleFrozenUids(int[] uids, int[] frozenStates) {
ensureRunningOnConnectivityServiceThread();
+ handleDestroyFrozenSockets(uids, frozenStates);
+ // TODO: handle freezing NetworkCallbacks
+ }
+
+ private void handleDestroyFrozenSockets(int[] uids, int[] frozenStates) {
+ if (!mDestroyFrozenSockets) {
+ return;
+ }
for (int i = 0; i < uids.length; i++) {
final int uid = uids[i];
final boolean addReason = frozenStates[i] == UID_FROZEN_STATE_FROZEN;
@@ -7764,6 +7779,11 @@
}
}
+ boolean isCallbackOverridden(int callbackId) {
+ return !mUseDeclaredMethodsForCallbacksEnabled
+ || (mDeclaredMethodsFlags & (1 << callbackId)) != 0;
+ }
+
boolean hasHigherOrderThan(@NonNull final NetworkRequestInfo target) {
// Compare two preference orders.
return mPreferenceOrder < target.mPreferenceOrder;
@@ -10233,6 +10253,18 @@
return new LocalNetworkInfo.Builder().setUpstreamNetwork(upstream).build();
}
+ private Bundle makeCommonBundleForCallback(@NonNull final NetworkRequestInfo nri,
+ @Nullable Network network) {
+ 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.
+ putParcelable(bundle, nri.getNetworkRequestForCallback());
+ if (network != null) {
+ putParcelable(bundle, network);
+ }
+ return bundle;
+ }
+
// networkAgent is only allowed to be null if notificationType is
// CALLBACK_UNAVAIL. This is because UNAVAIL is about no network being
// available, while all other cases are about some particular network.
@@ -10245,22 +10277,17 @@
// are Type.LISTEN, but should not have NetworkCallbacks invoked.
return;
}
- if (mUseDeclaredMethodsForCallbacksEnabled
- && (nri.mDeclaredMethodsFlags & (1 << notificationType)) == 0) {
+ if (!nri.isCallbackOverridden(notificationType)) {
// 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.
- final NetworkRequest nrForCallback = nri.getNetworkRequestForCallback();
- putParcelable(bundle, nrForCallback);
- Message msg = Message.obtain();
- if (notificationType != CALLBACK_UNAVAIL) {
- putParcelable(bundle, networkAgent.network);
- }
+ final Network bundleNetwork = notificationType == CALLBACK_UNAVAIL
+ ? null
+ : networkAgent.network;
+ final Bundle bundle = makeCommonBundleForCallback(nri, bundleNetwork);
final boolean includeLocationSensitiveInfo =
(nri.mCallbackFlags & NetworkCallback.FLAG_INCLUDE_LOCATION_INFO) != 0;
+ final NetworkRequest nrForCallback = nri.getNetworkRequestForCallback();
switch (notificationType) {
case CALLBACK_AVAILABLE: {
final NetworkCapabilities nc =
@@ -10277,12 +10304,6 @@
// method here.
bundle.putParcelable(LocalNetworkInfo.class.getSimpleName(),
localNetworkInfoForNai(networkAgent));
- // For this notification, arg1 contains the blocked status.
- msg.arg1 = arg1;
- break;
- }
- case CALLBACK_LOSING: {
- msg.arg1 = arg1;
break;
}
case CALLBACK_CAP_CHANGED: {
@@ -10305,7 +10326,6 @@
}
case CALLBACK_BLK_CHANGED: {
maybeLogBlockedStatusChanged(nri, networkAgent.network, arg1);
- msg.arg1 = arg1;
break;
}
case CALLBACK_LOCAL_NETWORK_INFO_CHANGED: {
@@ -10317,17 +10337,26 @@
break;
}
}
+ callCallbackForRequest(nri, notificationType, bundle, arg1);
+ }
+
+ private void callCallbackForRequest(@NonNull final NetworkRequestInfo nri, int notificationType,
+ Bundle bundle, int arg1) {
+ Message msg = Message.obtain();
+ msg.arg1 = arg1;
msg.what = notificationType;
msg.setData(bundle);
try {
if (VDBG) {
String notification = ConnectivityManager.getCallbackName(notificationType);
- log("sending notification " + notification + " for " + nrForCallback);
+ log("sending notification " + notification + " for "
+ + nri.getNetworkRequestForCallback());
}
nri.mMessenger.send(msg);
} catch (RemoteException e) {
// may occur naturally in the race of binder death.
- loge("RemoteException caught trying to send a callback msg for " + nrForCallback);
+ loge("RemoteException caught trying to send a callback msg for "
+ + nri.getNetworkRequestForCallback());
}
}
@@ -11416,11 +11445,7 @@
return;
}
- final int blockedReasons = mUidBlockedReasons.get(nri.mAsUid, BLOCKED_REASON_NONE);
- final boolean metered = nai.networkCapabilities.isMetered();
- final boolean vpnBlocked = isUidBlockedByVpn(nri.mAsUid, mVpnBlockedUidRanges);
- callCallbackForRequest(nri, nai, CALLBACK_AVAILABLE,
- getBlockedState(nri.mAsUid, blockedReasons, metered, vpnBlocked));
+ callCallbackForRequest(nri, nai, CALLBACK_AVAILABLE, getBlockedState(nai, nri.mAsUid));
}
// Notify the requests on this NAI that the network is now lingered.
@@ -11450,6 +11475,13 @@
: reasons & ~BLOCKED_REASON_LOCKDOWN_VPN;
}
+ private int getBlockedState(@NonNull NetworkAgentInfo nai, int uid) {
+ final boolean metered = nai.networkCapabilities.isMetered();
+ final boolean vpnBlocked = isUidBlockedByVpn(uid, mVpnBlockedUidRanges);
+ final int blockedReasons = mUidBlockedReasons.get(uid, BLOCKED_REASON_NONE);
+ return getBlockedState(uid, blockedReasons, metered, vpnBlocked);
+ }
+
private void setUidBlockedReasons(int uid, @BlockedReason int blockedReasons) {
if (blockedReasons == BLOCKED_REASON_NONE) {
mUidBlockedReasons.delete(uid);
diff --git a/service/src/com/android/server/connectivity/ConnectivityFlags.java b/service/src/com/android/server/connectivity/ConnectivityFlags.java
index 1ee1ed7..df87316 100644
--- a/service/src/com/android/server/connectivity/ConnectivityFlags.java
+++ b/service/src/com/android/server/connectivity/ConnectivityFlags.java
@@ -49,6 +49,9 @@
public static final String USE_DECLARED_METHODS_FOR_CALLBACKS =
"use_declared_methods_for_callbacks";
+ public static final String QUEUE_CALLBACKS_FOR_FROZEN_APPS =
+ "queue_callbacks_for_frozen_apps";
+
private boolean mNoRematchAllRequestsOnRegister;
/**
diff --git a/service/src/com/android/server/connectivity/ConnectivityNativeService.java b/service/src/com/android/server/connectivity/ConnectivityNativeService.java
index cf6127f..917ad4d 100644
--- a/service/src/com/android/server/connectivity/ConnectivityNativeService.java
+++ b/service/src/com/android/server/connectivity/ConnectivityNativeService.java
@@ -23,6 +23,7 @@
import android.os.Binder;
import android.os.Process;
import android.os.ServiceSpecificException;
+import android.os.UserHandle;
import android.system.ErrnoException;
import android.util.Log;
@@ -67,8 +68,8 @@
}
private void enforceBlockPortPermission() {
- final int uid = Binder.getCallingUid();
- if (uid == Process.ROOT_UID || uid == Process.PHONE_UID) return;
+ final int appId = UserHandle.getAppId(Binder.getCallingUid());
+ if (appId == Process.ROOT_UID || appId == Process.PHONE_UID) return;
PermissionUtils.enforceNetworkStackPermission(mContext);
}
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index 2c3a558..14bd5df 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -345,12 +345,10 @@
],
sdk_version: "module_current",
min_sdk_version: "30",
- static_libs: [
- "modules-utils-build_system",
- ],
libs: [
"framework-annotations-lib",
"framework-connectivity",
+ "modules-utils-build_system",
],
// TODO: remove "apex_available:platform".
apex_available: [
diff --git a/staticlibs/device/com/android/net/module/util/GrowingIntArray.java b/staticlibs/device/com/android/net/module/util/GrowingIntArray.java
new file mode 100644
index 0000000..4a81c10
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/GrowingIntArray.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util;
+
+import android.annotation.NonNull;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Arrays;
+import java.util.StringJoiner;
+import java.util.function.IntConsumer;
+import java.util.function.IntPredicate;
+
+/**
+ * A growing array of primitive ints.
+ *
+ * <p>This is similar to ArrayList<Integer>, but avoids the cost of boxing (each Integer costs
+ * 16 bytes) and creation / garbage collection of individual Integer objects.
+ *
+ * <p>This class does not use any heuristic for growing capacity, so every call to
+ * {@link #add(int)} may reallocate the backing array. Callers should use
+ * {@link #ensureHasCapacity(int)} to minimize this behavior when they plan to add several values.
+ */
+public class GrowingIntArray {
+ private int[] mValues;
+ private int mLength;
+
+ /**
+ * Create an empty GrowingIntArray with the given capacity.
+ */
+ public GrowingIntArray(int initialCapacity) {
+ mValues = new int[initialCapacity];
+ mLength = 0;
+ }
+
+ /**
+ * Create a GrowingIntArray with an initial array of values.
+ *
+ * <p>The array will be used as-is and may be modified, so callers must stop using it after
+ * calling this constructor.
+ */
+ protected GrowingIntArray(int[] initialValues) {
+ mValues = initialValues;
+ mLength = initialValues.length;
+ }
+
+ /**
+ * Add a value to the array.
+ */
+ public void add(int value) {
+ ensureHasCapacity(1);
+ mValues[mLength] = value;
+ mLength++;
+ }
+
+ /**
+ * Get the current number of values in the array.
+ */
+ public int length() {
+ return mLength;
+ }
+
+ /**
+ * Get the value at a given index.
+ *
+ * @throws ArrayIndexOutOfBoundsException if the index is out of bounds.
+ */
+ public int get(int index) {
+ if (index < 0 || index >= mLength) {
+ throw new ArrayIndexOutOfBoundsException(index);
+ }
+ return mValues[index];
+ }
+
+ /**
+ * Iterate over all values in the array.
+ */
+ public void forEach(@NonNull IntConsumer consumer) {
+ for (int i = 0; i < mLength; i++) {
+ consumer.accept(mValues[i]);
+ }
+ }
+
+ /**
+ * Remove all values matching a predicate.
+ *
+ * @return true if at least one value was removed.
+ */
+ public boolean removeValues(@NonNull IntPredicate predicate) {
+ int newQueueLength = 0;
+ for (int i = 0; i < mLength; i++) {
+ final int cb = mValues[i];
+ if (!predicate.test(cb)) {
+ mValues[newQueueLength] = cb;
+ newQueueLength++;
+ }
+ }
+ if (mLength != newQueueLength) {
+ mLength = newQueueLength;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Indicates whether the array contains the given value.
+ */
+ public boolean contains(int value) {
+ for (int i = 0; i < mLength; i++) {
+ if (mValues[i] == value) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Remove all values from the array.
+ */
+ public void clear() {
+ mLength = 0;
+ }
+
+ /**
+ * Ensure at least the given number of values can be added to the array without reallocating.
+ *
+ * @param capacity The minimum number of additional values the array must be able to hold.
+ */
+ public void ensureHasCapacity(int capacity) {
+ if (mValues.length >= mLength + capacity) {
+ return;
+ }
+ mValues = Arrays.copyOf(mValues, mLength + capacity);
+ }
+
+ @VisibleForTesting
+ int getBackingArrayLength() {
+ return mValues.length;
+ }
+
+ /**
+ * Shrink the array backing this class to the minimum required length.
+ */
+ public void shrinkToLength() {
+ if (mValues.length != mLength) {
+ mValues = Arrays.copyOf(mValues, mLength);
+ }
+ }
+
+ /**
+ * Get values as array by shrinking the internal array to length and returning it.
+ *
+ * <p>This avoids reallocations if the array is already the correct length, but callers should
+ * stop using this instance of {@link GrowingIntArray} if they use the array returned by this
+ * method.
+ */
+ public int[] getShrinkedBackingArray() {
+ shrinkToLength();
+ return mValues;
+ }
+
+ /**
+ * Get the String representation of an item in the array, for use by {@link #toString()}.
+ */
+ protected String valueToString(int item) {
+ return String.valueOf(item);
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ final StringJoiner joiner = new StringJoiner(",", "[", "]");
+ forEach(item -> joiner.add(valueToString(item)));
+ return joiner.toString();
+ }
+}
diff --git a/staticlibs/device/com/android/net/module/util/RoutingCoordinatorManager.java b/staticlibs/device/com/android/net/module/util/RoutingCoordinatorManager.java
index e37061c..02e3643 100644
--- a/staticlibs/device/com/android/net/module/util/RoutingCoordinatorManager.java
+++ b/staticlibs/device/com/android/net/module/util/RoutingCoordinatorManager.java
@@ -18,12 +18,10 @@
import android.content.Context;
import android.net.RouteInfo;
-import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
/**
* A manager class for talking to the routing coordinator service.
@@ -32,7 +30,6 @@
* by the build rules. Do not change build rules to gain access to this class from elsewhere.
* @hide
*/
-@RequiresApi(Build.VERSION_CODES.S)
public class RoutingCoordinatorManager {
@NonNull final Context mContext;
@NonNull final IRoutingCoordinator mService;
diff --git a/staticlibs/device/com/android/net/module/util/SingleWriterBpfMap.java b/staticlibs/device/com/android/net/module/util/SingleWriterBpfMap.java
index cd6bfec..a638cc4 100644
--- a/staticlibs/device/com/android/net/module/util/SingleWriterBpfMap.java
+++ b/staticlibs/device/com/android/net/module/util/SingleWriterBpfMap.java
@@ -19,6 +19,7 @@
import android.system.ErrnoException;
import android.util.Pair;
+import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
@@ -60,6 +61,7 @@
public class SingleWriterBpfMap<K extends Struct, V extends Struct> extends BpfMap<K, V> {
// HashMap instead of ArrayMap because it performs better on larger maps, and many maps used in
// our code can contain hundreds of items.
+ @GuardedBy("this")
private final HashMap<K, V> mCache = new HashMap<>();
// This should only ever be called (hence private) once for a given 'path'.
@@ -72,10 +74,12 @@
super(path, BPF_F_RDWR_EXCLUSIVE, key, value);
// Populate cache with the current map contents.
- K currentKey = super.getFirstKey();
- while (currentKey != null) {
- mCache.put(currentKey, super.getValue(currentKey));
- currentKey = super.getNextKey(currentKey);
+ synchronized (this) {
+ K currentKey = super.getFirstKey();
+ while (currentKey != null) {
+ mCache.put(currentKey, super.getValue(currentKey));
+ currentKey = super.getNextKey(currentKey);
+ }
}
}
diff --git a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
index a8c50d8..f1ff2e4 100644
--- a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
+++ b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
@@ -260,6 +260,18 @@
public static final short DNS_OVER_TLS_PORT = 853;
/**
+ * Dns query type constants.
+ *
+ * See also:
+ * - https://datatracker.ietf.org/doc/html/rfc1035#section-3.2.2
+ */
+ public static final int TYPE_A = 1;
+ public static final int TYPE_PTR = 12;
+ public static final int TYPE_TXT = 16;
+ public static final int TYPE_AAAA = 28;
+ public static final int TYPE_SRV = 33;
+
+ /**
* IEEE802.11 standard constants.
*
* See also:
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Log.h b/staticlibs/netd/libnetdutils/include/netdutils/Log.h
index d266cbc..2de5ed7 100644
--- a/staticlibs/netd/libnetdutils/include/netdutils/Log.h
+++ b/staticlibs/netd/libnetdutils/include/netdutils/Log.h
@@ -203,7 +203,7 @@
void record(Level lvl, const std::string& entry);
mutable std::shared_mutex mLock;
- std::deque<const std::string> mEntries; // GUARDED_BY(mLock), when supported
+ std::deque<std::string> mEntries; // GUARDED_BY(mLock), when supported
};
} // namespace netdutils
diff --git a/staticlibs/tests/unit/host/python/apf_utils_test.py b/staticlibs/tests/unit/host/python/apf_utils_test.py
index 8b390e3..caaf959 100644
--- a/staticlibs/tests/unit/host/python/apf_utils_test.py
+++ b/staticlibs/tests/unit/host/python/apf_utils_test.py
@@ -13,13 +13,16 @@
# limitations under the License.
from unittest.mock import MagicMock, patch
+from absl.testing import parameterized
from mobly import asserts
from mobly import base_test
from mobly import config_parser
from mobly.controllers.android_device_lib.adb import AdbError
from net_tests_utils.host.python.apf_utils import (
+ ApfCapabilities,
PatternNotFoundException,
UnsupportedOperationException,
+ get_apf_capabilities,
get_apf_counter,
get_apf_counters_from_dumpsys,
get_hardware_address,
@@ -29,7 +32,7 @@
from net_tests_utils.host.python.assert_utils import UnexpectedBehaviorError
-class TestApfUtils(base_test.BaseTestClass):
+class TestApfUtils(base_test.BaseTestClass, parameterized.TestCase):
def __init__(self, configs: config_parser.TestRunConfig):
super().__init__(configs)
@@ -150,3 +153,23 @@
)
with asserts.assert_raises(UnsupportedOperationException):
send_raw_packet_downstream(self.mock_ad, "eth0", "AABBCCDDEEFF")
+
+ @parameterized.parameters(
+ ("2,2048,1", ApfCapabilities(2, 2048, 1)), # Valid input
+ ("3,1024,0", ApfCapabilities(3, 1024, 0)), # Valid input
+ ("invalid,output", ApfCapabilities(0, 0, 0)), # Invalid input
+ ("", ApfCapabilities(0, 0, 0)), # Empty input
+ )
+ @patch("net_tests_utils.host.python.adb_utils.adb_shell")
+ def test_get_apf_capabilities(
+ self, mock_output, expected_result, mock_adb_shell
+ ):
+ """Tests the get_apf_capabilities function with various inputs and expected results."""
+ # Configure the mock adb_shell to return the specified output
+ mock_adb_shell.return_value = mock_output
+
+ # Call the function under test
+ result = get_apf_capabilities(self.mock_ad, "wlan0")
+
+ # Assert that the result matches the expected result
+ asserts.assert_equal(result, expected_result)
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/GrowingIntArrayTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/GrowingIntArrayTest.kt
new file mode 100644
index 0000000..bdcb8c0
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/GrowingIntArrayTest.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import kotlin.test.assertContentEquals
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+import kotlin.test.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class GrowingIntArrayTest {
+ @Test
+ fun testAddAndGet() {
+ val array = GrowingIntArray(1)
+ array.add(-1)
+ array.add(0)
+ array.add(2)
+
+ assertEquals(-1, array.get(0))
+ assertEquals(0, array.get(1))
+ assertEquals(2, array.get(2))
+ assertEquals(3, array.length())
+ }
+
+ @Test
+ fun testForEach() {
+ val array = GrowingIntArray(10)
+ array.add(-1)
+ array.add(0)
+ array.add(2)
+
+ val actual = mutableListOf<Int>()
+ array.forEach { actual.add(it) }
+
+ val expected = listOf(-1, 0, 2)
+ assertEquals(expected, actual)
+ }
+
+ @Test
+ fun testForEach_EmptyArray() {
+ val array = GrowingIntArray(10)
+ array.forEach {
+ fail("This should not be called")
+ }
+ }
+
+ @Test
+ fun testRemoveValues() {
+ val array = GrowingIntArray(10)
+ array.add(-1)
+ array.add(0)
+ array.add(2)
+
+ array.removeValues { it <= 0 }
+ assertEquals(1, array.length())
+ assertEquals(2, array.get(0))
+ }
+
+ @Test
+ fun testContains() {
+ val array = GrowingIntArray(10)
+ array.add(-1)
+ array.add(2)
+
+ assertTrue(array.contains(-1))
+ assertTrue(array.contains(2))
+
+ assertFalse(array.contains(0))
+ assertFalse(array.contains(3))
+ }
+
+ @Test
+ fun testClear() {
+ val array = GrowingIntArray(10)
+ array.add(-1)
+ array.add(2)
+ array.clear()
+
+ assertEquals(0, array.length())
+ }
+
+ @Test
+ fun testEnsureHasCapacity() {
+ val array = GrowingIntArray(0)
+ array.add(42)
+ array.ensureHasCapacity(2)
+
+ assertEquals(3, array.backingArrayLength)
+ }
+
+ @Test
+ fun testGetShrinkedBackingArray() {
+ val array = GrowingIntArray(10)
+ array.add(-1)
+ array.add(2)
+
+ assertContentEquals(intArrayOf(-1, 2), array.shrinkedBackingArray)
+ }
+
+ @Test
+ fun testToString() {
+ assertEquals("[]", GrowingIntArray(10).toString())
+ assertEquals("[1,2,3]", GrowingIntArray(3).apply {
+ add(1)
+ add(2)
+ add(3)
+ }.toString())
+ }
+}
diff --git a/staticlibs/tests/unit/src/com/android/testutils/DefaultNetworkRestoreMonitorTest.kt b/staticlibs/tests/unit/src/com/android/testutils/DefaultNetworkRestoreMonitorTest.kt
new file mode 100644
index 0000000..7e508fb
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/testutils/DefaultNetworkRestoreMonitorTest.kt
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.testutils
+
+import android.content.Context
+import android.content.pm.PackageManager
+import android.net.ConnectivityManager
+import android.net.ConnectivityManager.NetworkCallback
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import org.junit.Test
+import org.junit.runner.Description
+import org.junit.runner.notification.RunListener
+import org.junit.runner.notification.RunNotifier
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.ArgumentMatchers.argThat
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.doNothing
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+class DefaultNetworkRestoreMonitorTest {
+ private val restoreDefaultNetworkDesc =
+ Description.createSuiteDescription("RestoreDefaultNetwork")
+ private val testDesc = Description.createTestDescription("testClass", "testMethod")
+ private val wifiCap = NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
+ .build()
+ private val cellCap = NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
+ .build()
+ private val cm = mock(ConnectivityManager::class.java)
+ private val pm = mock(PackageManager::class.java).also {
+ doReturn(true).`when`(it).hasSystemFeature(anyString())
+ }
+ private val ctx = mock(Context::class.java).also {
+ doReturn(cm).`when`(it).getSystemService(ConnectivityManager::class.java)
+ doReturn(pm).`when`(it).getPackageManager()
+ }
+ private val notifier = mock(RunNotifier::class.java)
+ private val defaultNetworkMonitor = DefaultNetworkRestoreMonitor(
+ ctx,
+ notifier,
+ timeoutMs = 0
+ )
+
+ private fun getRunListener(): RunListener {
+ val captor = ArgumentCaptor.forClass(RunListener::class.java)
+ verify(notifier).addListener(captor.capture())
+ return captor.value
+ }
+
+ private fun mockDefaultNetworkCapabilities(cap: NetworkCapabilities?) {
+ if (cap == null) {
+ doNothing().`when`(cm).registerDefaultNetworkCallback(any())
+ return
+ }
+ doAnswer {
+ val callback = it.getArgument(0) as NetworkCallback
+ callback.onCapabilitiesChanged(Network(100), cap)
+ }.`when`(cm).registerDefaultNetworkCallback(any())
+ }
+
+ @Test
+ fun testDefaultNetworkRestoreMonitor_defaultNetworkRestored() {
+ mockDefaultNetworkCapabilities(wifiCap)
+ defaultNetworkMonitor.init(mock(ConnectUtil::class.java))
+
+ val listener = getRunListener()
+ listener.testFinished(testDesc)
+
+ defaultNetworkMonitor.reportResultAndCleanUp(restoreDefaultNetworkDesc)
+ val inOrder = inOrder(notifier)
+ inOrder.verify(notifier).fireTestStarted(restoreDefaultNetworkDesc)
+ inOrder.verify(notifier, never()).fireTestFailure(any())
+ inOrder.verify(notifier).fireTestFinished(restoreDefaultNetworkDesc)
+ inOrder.verify(notifier).removeListener(listener)
+ }
+
+ @Test
+ fun testDefaultNetworkRestoreMonitor_testStartWithoutDefaultNetwork() {
+ // There is no default network when the tests start
+ mockDefaultNetworkCapabilities(null)
+ defaultNetworkMonitor.init(mock(ConnectUtil::class.java))
+
+ mockDefaultNetworkCapabilities(wifiCap)
+ val listener = getRunListener()
+ listener.testFinished(testDesc)
+
+ defaultNetworkMonitor.reportResultAndCleanUp(restoreDefaultNetworkDesc)
+ val inOrder = inOrder(notifier)
+ inOrder.verify(notifier).fireTestStarted(restoreDefaultNetworkDesc)
+ // fireTestFailure is called
+ inOrder.verify(notifier).fireTestFailure(any())
+ inOrder.verify(notifier).fireTestFinished(restoreDefaultNetworkDesc)
+ inOrder.verify(notifier).removeListener(listener)
+ }
+
+ @Test
+ fun testDefaultNetworkRestoreMonitor_testEndWithoutDefaultNetwork() {
+ mockDefaultNetworkCapabilities(wifiCap)
+ defaultNetworkMonitor.init(mock(ConnectUtil::class.java))
+
+ // There is no default network after the test
+ mockDefaultNetworkCapabilities(null)
+ val listener = getRunListener()
+ listener.testFinished(testDesc)
+
+ defaultNetworkMonitor.reportResultAndCleanUp(restoreDefaultNetworkDesc)
+ val inOrder = inOrder(notifier)
+ inOrder.verify(notifier).fireTestStarted(restoreDefaultNetworkDesc)
+ // fireTestFailure is called with method name
+ inOrder.verify(
+ notifier
+ ).fireTestFailure(
+ argThat{failure -> failure.exception.message?.contains("testMethod") ?: false}
+ )
+ inOrder.verify(notifier).fireTestFinished(restoreDefaultNetworkDesc)
+ inOrder.verify(notifier).removeListener(listener)
+ }
+
+ @Test
+ fun testDefaultNetworkRestoreMonitor_testChangeDefaultNetwork() {
+ mockDefaultNetworkCapabilities(wifiCap)
+ defaultNetworkMonitor.init(mock(ConnectUtil::class.java))
+
+ // The default network transport types change after the test
+ mockDefaultNetworkCapabilities(cellCap)
+ val listener = getRunListener()
+ listener.testFinished(testDesc)
+
+ defaultNetworkMonitor.reportResultAndCleanUp(restoreDefaultNetworkDesc)
+ val inOrder = inOrder(notifier)
+ inOrder.verify(notifier).fireTestStarted(restoreDefaultNetworkDesc)
+ // fireTestFailure is called with method name
+ inOrder.verify(
+ notifier
+ ).fireTestFailure(
+ argThat{failure -> failure.exception.message?.contains("testMethod") ?: false}
+ )
+ inOrder.verify(notifier).fireTestFinished(restoreDefaultNetworkDesc)
+ inOrder.verify(notifier).removeListener(listener)
+ }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/DefaultNetworkRestoreMonitor.kt b/staticlibs/testutils/devicetests/com/android/testutils/DefaultNetworkRestoreMonitor.kt
new file mode 100644
index 0000000..1b709b2
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/DefaultNetworkRestoreMonitor.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.testutils
+
+import android.content.Context
+import android.content.pm.PackageManager
+import android.net.ConnectivityManager
+import android.net.Network
+import android.net.NetworkCapabilities
+import com.android.internal.annotations.VisibleForTesting
+import com.android.net.module.util.BitUtils
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.TimeUnit
+import org.junit.runner.Description
+import org.junit.runner.notification.Failure
+import org.junit.runner.notification.RunListener
+import org.junit.runner.notification.RunNotifier
+
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+class DefaultNetworkRestoreMonitor(
+ ctx: Context,
+ private val notifier: RunNotifier,
+ private val timeoutMs: Long = 3000
+) {
+ var firstFailure: Exception? = null
+ var initialTransports = 0L
+ val cm = ctx.getSystemService(ConnectivityManager::class.java)!!
+ val pm = ctx.packageManager
+ val listener = object : RunListener() {
+ override fun testFinished(desc: Description) {
+ // Only the first method that does not restore the default network should be blamed.
+ if (firstFailure != null) {
+ return
+ }
+ val cb = TestableNetworkCallback()
+ cm.registerDefaultNetworkCallback(cb)
+ try {
+ cb.eventuallyExpect<RecorderCallback.CallbackEntry.CapabilitiesChanged>(
+ timeoutMs = timeoutMs
+ ) {
+ BitUtils.packBits(it.caps.transportTypes) == initialTransports &&
+ it.caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
+ }
+ } catch (e: AssertionError) {
+ firstFailure = IllegalStateException(desc.methodName +
+ " does not restore the default network")
+ } finally {
+ cm.unregisterNetworkCallback(cb)
+ }
+ }
+ }
+
+ fun init(connectUtil: ConnectUtil) {
+ // Ensure Wi-Fi and cellular connection before running test to avoid starting test
+ // with unexpected default network.
+ // ConnectivityTestTargetPreparer does the same thing, but it's possible that previous tests
+ // don't enable DefaultNetworkRestoreMonitor and the default network is not restored.
+ // This can be removed if all tests enable DefaultNetworkRestoreMonitor
+ if (pm.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
+ connectUtil.ensureWifiValidated()
+ }
+ if (pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+ connectUtil.ensureCellularValidated()
+ }
+
+ val capFuture = CompletableFuture<NetworkCapabilities>()
+ val cb = object : ConnectivityManager.NetworkCallback() {
+ override fun onCapabilitiesChanged(
+ network: Network,
+ cap: NetworkCapabilities
+ ) {
+ capFuture.complete(cap)
+ }
+ }
+ cm.registerDefaultNetworkCallback(cb)
+ try {
+ val cap = capFuture.get(100, TimeUnit.MILLISECONDS)
+ initialTransports = BitUtils.packBits(cap.transportTypes)
+ } catch (e: Exception) {
+ firstFailure = IllegalStateException(
+ "Failed to get default network status before starting tests", e
+ )
+ } finally {
+ cm.unregisterNetworkCallback(cb)
+ }
+ notifier.addListener(listener)
+ }
+
+ fun reportResultAndCleanUp(desc: Description) {
+ notifier.fireTestStarted(desc)
+ if (firstFailure != null) {
+ notifier.fireTestFailure(
+ Failure(desc, firstFailure)
+ )
+ }
+ notifier.fireTestFinished(desc)
+ notifier.removeListener(listener)
+ }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt b/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt
index 8687ac7..a014834 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt
@@ -16,6 +16,8 @@
package com.android.testutils
+import android.content.Context
+import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.net.module.util.LinkPropertiesUtils.CompareOrUpdateResult
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter
@@ -57,6 +59,10 @@
class DevSdkIgnoreRunner(private val klass: Class<*>) : Runner(), Filterable, Sortable {
private val leakMonitorDesc = Description.createTestDescription(klass, "ThreadLeakMonitor")
private val shouldThreadLeakFailTest = klass.isAnnotationPresent(MonitorThreadLeak::class.java)
+ private val restoreDefaultNetworkDesc =
+ Description.createTestDescription(klass, "RestoreDefaultNetwork")
+ private val restoreDefaultNetwork = klass.isAnnotationPresent(RestoreDefaultNetwork::class.java)
+ val ctx = ApplicationProvider.getApplicationContext<Context>()
// Inference correctly infers Runner & Filterable & Sortable for |baseRunner|, but the
// Java bytecode doesn't have a way to express this. Give this type a name by wrapping it.
@@ -71,6 +77,10 @@
// TODO(b/307693729): Remove this annotation and monitor thread leak by default.
annotation class MonitorThreadLeak
+ // Annotation for test classes to indicate the test runner should verify the default network is
+ // restored after each test.
+ annotation class RestoreDefaultNetwork
+
private val baseRunner: RunnerWrapper<*>? = klass.let {
val ignoreAfter = it.getAnnotation(IgnoreAfter::class.java)
val ignoreUpTo = it.getAnnotation(IgnoreUpTo::class.java)
@@ -125,6 +135,14 @@
)
return
}
+
+ val networkRestoreMonitor = if (restoreDefaultNetwork) {
+ DefaultNetworkRestoreMonitor(ctx, notifier).apply{
+ init(ConnectUtil(ctx))
+ }
+ } else {
+ null
+ }
val threadCountsBeforeTest = if (shouldThreadLeakFailTest) {
// Dump threads as a baseline to monitor thread leaks.
getAllThreadNameCounts()
@@ -137,6 +155,7 @@
if (threadCountsBeforeTest != null) {
checkThreadLeak(notifier, threadCountsBeforeTest)
}
+ networkRestoreMonitor?.reportResultAndCleanUp(restoreDefaultNetworkDesc)
// Clears up internal state of all inline mocks.
// TODO: Call clearInlineMocks() at the end of each test.
Mockito.framework().clearInlineMocks()
@@ -163,6 +182,9 @@
if (shouldThreadLeakFailTest) {
it.addChild(leakMonitorDesc)
}
+ if (restoreDefaultNetwork) {
+ it.addChild(restoreDefaultNetworkDesc)
+ }
}
}
@@ -173,7 +195,14 @@
// When ignoring the tests, a skipped placeholder test is reported, so test count is 1.
if (baseRunner == null) return 1
- return baseRunner.testCount() + if (shouldThreadLeakFailTest) 1 else 0
+ var testCount = baseRunner.testCount()
+ if (shouldThreadLeakFailTest) {
+ testCount += 1
+ }
+ if (restoreDefaultNetwork) {
+ testCount += 1
+ }
+ return testCount
}
@Throws(NoTestsRemainException::class)
diff --git a/staticlibs/testutils/host/python/apf_utils.py b/staticlibs/testutils/host/python/apf_utils.py
index f71464c..415799c 100644
--- a/staticlibs/testutils/host/python/apf_utils.py
+++ b/staticlibs/testutils/host/python/apf_utils.py
@@ -12,7 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from dataclasses import dataclass
import re
+from mobly import asserts
from mobly.controllers import android_device
from mobly.controllers.android_device_lib.adb import AdbError
from net_tests_utils.host.python import adb_utils, assert_utils
@@ -190,3 +192,65 @@
raise assert_utils.UnexpectedBehaviorError(
f"Got unexpected output: {output} for command: {cmd}."
)
+
+
+@dataclass
+class ApfCapabilities:
+ """APF program support capabilities.
+
+ See android.net.apf.ApfCapabilities.
+
+ Attributes:
+ apf_version_supported (int): Version of APF instruction set supported for
+ packet filtering. 0 indicates no support for packet filtering using APF
+ programs.
+ apf_ram_size (int): Size of APF ram.
+ apf_packet_format (int): Format of packets passed to APF filter. Should be
+ one of ARPHRD_*
+ """
+
+ apf_version_supported: int
+ apf_ram_size: int
+ apf_packet_format: int
+
+ def __init__(
+ self,
+ apf_version_supported: int,
+ apf_ram_size: int,
+ apf_packet_format: int,
+ ):
+ self.apf_version_supported = apf_version_supported
+ self.apf_ram_size = apf_ram_size
+ self.apf_packet_format = apf_packet_format
+
+ def __str__(self):
+ """Returns a user-friendly string representation of the APF capabilities."""
+ return (
+ f"APF Version: {self.apf_version_supported}\n"
+ f"Ram Size: {self.apf_ram_size} bytes\n"
+ f"Packet Format: {self.apf_packet_format}"
+ )
+
+
+def get_apf_capabilities(
+ ad: android_device.AndroidDevice, iface_name: str
+) -> ApfCapabilities:
+ output = adb_utils.adb_shell(
+ ad, f"cmd network_stack apf {iface_name} capabilities"
+ )
+ try:
+ values = [int(value_str) for value_str in output.split(",")]
+ except ValueError:
+ return ApfCapabilities(0, 0, 0) # Conversion to integer failed
+ return ApfCapabilities(values[0], values[1], values[2])
+
+
+def assume_apf_version_support_at_least(
+ ad: android_device.AndroidDevice, iface_name: str, expected_version: int
+) -> None:
+ caps = get_apf_capabilities(ad, iface_name)
+ asserts.skip_if(
+ caps.apf_version_supported < expected_version,
+ f"Supported apf version {caps.apf_version_supported} < expected version"
+ f" {expected_version}",
+ )
diff --git a/staticlibs/testutils/host/python/mdns_utils.py b/staticlibs/testutils/host/python/mdns_utils.py
index ec1fea0..1234e54 100644
--- a/staticlibs/testutils/host/python/mdns_utils.py
+++ b/staticlibs/testutils/host/python/mdns_utils.py
@@ -12,9 +12,24 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from mobly import asserts
from mobly.controllers import android_device
+def assume_mdns_test_preconditions(
+ advertising_device: android_device, discovery_device: android_device
+) -> None:
+ advertising = advertising_device.connectivity_multi_devices_snippet
+ discovery = discovery_device.connectivity_multi_devices_snippet
+
+ asserts.skip_if(
+ not advertising.isAtLeastT(), "Advertising device SDK is lower than T."
+ )
+ asserts.skip_if(
+ not discovery.isAtLeastT(), "Discovery device SDK is lower than T."
+ )
+
+
def register_mdns_service_and_discover_resolve(
advertising_device: android_device, discovery_device: android_device
) -> None:
diff --git a/staticlibs/testutils/host/python/wifip2p_utils.py b/staticlibs/testutils/host/python/wifip2p_utils.py
new file mode 100644
index 0000000..8b4ffa5
--- /dev/null
+++ b/staticlibs/testutils/host/python/wifip2p_utils.py
@@ -0,0 +1,50 @@
+# 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.
+
+from mobly import asserts
+from mobly.controllers import android_device
+
+
+def assume_wifi_p2p_test_preconditions(
+ server_device: android_device, client_device: android_device
+) -> None:
+ server = server_device.connectivity_multi_devices_snippet
+ client = client_device.connectivity_multi_devices_snippet
+
+ # Assert pre-conditions
+ asserts.skip_if(not server.hasWifiFeature(), "Server requires Wifi feature")
+ asserts.skip_if(not client.hasWifiFeature(), "Client requires Wifi feature")
+ asserts.skip_if(
+ not server.isP2pSupported(), "Server requires Wi-fi P2P feature"
+ )
+ asserts.skip_if(
+ not client.isP2pSupported(), "Client requires Wi-fi P2P feature"
+ )
+
+
+def setup_wifi_p2p_server_and_client(
+ server_device: android_device, client_device: android_device
+) -> None:
+ """Set up the Wi-Fi P2P server and client."""
+ # Start Wi-Fi P2P on both server and client.
+ server_device.connectivity_multi_devices_snippet.startWifiP2p()
+ client_device.connectivity_multi_devices_snippet.startWifiP2p()
+
+
+def cleanup_wifi_p2p(
+ server_device: android_device, client_device: android_device
+) -> None:
+ # Stop Wi-Fi P2P
+ server_device.connectivity_multi_devices_snippet.stopWifiP2p()
+ client_device.connectivity_multi_devices_snippet.stopWifiP2p()
diff --git a/tests/cts/multidevices/connectivity_multi_devices_test.py b/tests/cts/multidevices/connectivity_multi_devices_test.py
index 0cfc361..7e7bbf5 100644
--- a/tests/cts/multidevices/connectivity_multi_devices_test.py
+++ b/tests/cts/multidevices/connectivity_multi_devices_test.py
@@ -68,6 +68,9 @@
tether_utils.assume_hotspot_test_preconditions(
self.serverDevice, self.clientDevice, UpstreamType.NONE
)
+ mdns_utils.assume_mdns_test_preconditions(
+ self.clientDevice, self.serverDevice
+ )
try:
# Connectivity of the client verified by asserting the validated capability.
tether_utils.setup_hotspot_and_client_for_upstream_type(
diff --git a/tests/cts/multidevices/snippet/Android.bp b/tests/cts/multidevices/snippet/Android.bp
index b0b32c2..c94087e 100644
--- a/tests/cts/multidevices/snippet/Android.bp
+++ b/tests/cts/multidevices/snippet/Android.bp
@@ -26,6 +26,7 @@
srcs: [
"ConnectivityMultiDevicesSnippet.kt",
"MdnsMultiDevicesSnippet.kt",
+ "Wifip2pMultiDevicesSnippet.kt",
],
manifest: "AndroidManifest.xml",
static_libs: [
diff --git a/tests/cts/multidevices/snippet/AndroidManifest.xml b/tests/cts/multidevices/snippet/AndroidManifest.xml
index 967e581..4637497 100644
--- a/tests/cts/multidevices/snippet/AndroidManifest.xml
+++ b/tests/cts/multidevices/snippet/AndroidManifest.xml
@@ -21,6 +21,8 @@
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES"
+ android:usesPermissionFlags="neverForLocation" />
<application>
<!-- Add any classes that implement the Snippet interface as meta-data, whose
value is a comma-separated string, each section being the package path
@@ -28,7 +30,8 @@
<meta-data
android:name="mobly-snippets"
android:value="com.google.snippet.connectivity.ConnectivityMultiDevicesSnippet,
- com.google.snippet.connectivity.MdnsMultiDevicesSnippet" />
+ com.google.snippet.connectivity.MdnsMultiDevicesSnippet,
+ com.google.snippet.connectivity.Wifip2pMultiDevicesSnippet" />
</application>
<!-- Add an instrumentation tag so that the app can be launched through an
instrument command. The runner `com.google.android.mobly.snippet.SnippetRunner`
diff --git a/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt b/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt
index 9bdf4a3..7368669 100644
--- a/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt
+++ b/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt
@@ -36,6 +36,7 @@
import android.net.wifi.WifiNetworkSpecifier
import android.net.wifi.WifiSsid
import androidx.test.platform.app.InstrumentationRegistry
+import com.android.modules.utils.build.SdkLevel
import com.android.testutils.AutoReleaseNetworkCallbackRule
import com.android.testutils.ConnectUtil
import com.android.testutils.NetworkCallbackHelper
@@ -71,6 +72,9 @@
@Rpc(description = "Check whether the device supporters AP + STA concurrency.")
fun isStaApConcurrencySupported() = wifiManager.isStaApConcurrencySupported()
+ @Rpc(description = "Check whether the device SDK is as least T")
+ fun isAtLeastT() = SdkLevel.isAtLeastT()
+
@Rpc(description = "Request cellular connection and ensure it is the default network.")
fun requestCellularAndEnsureDefault() {
ctsNetUtils.disableWifi()
diff --git a/tests/cts/multidevices/snippet/Wifip2pMultiDevicesSnippet.kt b/tests/cts/multidevices/snippet/Wifip2pMultiDevicesSnippet.kt
new file mode 100644
index 0000000..e0929bb
--- /dev/null
+++ b/tests/cts/multidevices/snippet/Wifip2pMultiDevicesSnippet.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.snippet.connectivity
+
+import android.net.wifi.WifiManager
+import android.net.wifi.p2p.WifiP2pManager
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.android.mobly.snippet.Snippet
+import com.google.android.mobly.snippet.rpc.Rpc
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.TimeUnit
+import kotlin.test.fail
+
+private const val TIMEOUT_MS = 60000L
+
+class Wifip2pMultiDevicesSnippet : Snippet {
+ private val context by lazy { InstrumentationRegistry.getInstrumentation().getTargetContext() }
+ private val wifiManager by lazy {
+ context.getSystemService(WifiManager::class.java)
+ ?: fail("Could not get WifiManager service")
+ }
+ private val wifip2pManager by lazy {
+ context.getSystemService(WifiP2pManager::class.java)
+ ?: fail("Could not get WifiP2pManager service")
+ }
+ private lateinit var wifip2pChannel: WifiP2pManager.Channel
+
+ @Rpc(description = "Check whether the device supports Wi-Fi P2P.")
+ fun isP2pSupported() = wifiManager.isP2pSupported()
+
+ @Rpc(description = "Start Wi-Fi P2P")
+ fun startWifiP2p() {
+ // Initialize Wi-Fi P2P
+ wifip2pChannel = wifip2pManager.initialize(context, context.mainLooper, null)
+
+ // Ensure the Wi-Fi P2P channel is available
+ val p2pStateEnabledFuture = CompletableFuture<Boolean>()
+ wifip2pManager.requestP2pState(wifip2pChannel) { state ->
+ if (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED) {
+ p2pStateEnabledFuture.complete(true)
+ }
+ }
+ p2pStateEnabledFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+ }
+
+ @Rpc(description = "Stop Wi-Fi P2P")
+ fun stopWifiP2p() {
+ if (this::wifip2pChannel.isInitialized) {
+ wifip2pManager.cancelConnect(wifip2pChannel, null)
+ wifip2pManager.removeGroup(wifip2pChannel, null)
+ }
+ }
+}
diff --git a/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java b/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
index 06a827b..2c7d5c6 100644
--- a/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
+++ b/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
@@ -40,10 +40,10 @@
import android.system.OsConstants;
import android.util.ArraySet;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.testutils.AutoReleaseNetworkCallbackRule;
+import com.android.testutils.DevSdkIgnoreRunner;
import com.android.testutils.DeviceConfigRule;
import org.junit.Before;
@@ -53,7 +53,8 @@
import java.util.Set;
-@RunWith(AndroidJUnit4.class)
+@DevSdkIgnoreRunner.RestoreDefaultNetwork
+@RunWith(DevSdkIgnoreRunner.class)
public class MultinetworkApiTest {
@Rule(order = 1)
public final DeviceConfigRule mDeviceConfigRule = new DeviceConfigRule();
diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index beb9274..60081d4 100644
--- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -194,15 +194,16 @@
// TODO : enable this in a Mainline update or in V.
private const val SHOULD_CREATE_NETWORKS_IMMEDIATELY = false
-@RunWith(DevSdkIgnoreRunner::class)
-// NetworkAgent is not updatable in R-, so this test does not need to be compatible with older
-// versions. NetworkAgent was also based on AsyncChannel before S so cannot be tested the same way.
-@IgnoreUpTo(Build.VERSION_CODES.R)
+@AppModeFull(reason = "Instant apps can't use NetworkAgent because it needs NETWORK_FACTORY'.")
// NetworkAgent is updated as part of the connectivity module, and running NetworkAgent tests in MTS
// for modules other than Connectivity does not provide much value. Only run them in connectivity
// module MTS, so the tests only need to cover the case of an updated NetworkAgent.
@ConnectivityModuleTest
-@AppModeFull(reason = "Instant apps can't use NetworkAgent because it needs NETWORK_FACTORY'.")
+@DevSdkIgnoreRunner.RestoreDefaultNetwork
+// NetworkAgent is not updatable in R-, so this test does not need to be compatible with older
+// versions. NetworkAgent was also based on AsyncChannel before S so cannot be tested the same way.
+@IgnoreUpTo(Build.VERSION_CODES.R)
+@RunWith(DevSdkIgnoreRunner::class)
class NetworkAgentTest {
private val LOCAL_IPV4_ADDRESS = InetAddresses.parseNumericAddress("192.0.2.1")
private val REMOTE_IPV4_ADDRESS = InetAddresses.parseNumericAddress("192.0.2.2")
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index 2f88c41..ef3ebb0 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -92,6 +92,7 @@
"frameworks-net-integration-testutils",
"framework-protos",
"mockito-target-minus-junit4",
+ "modules-utils-build",
"net-tests-utils",
"net-utils-services-common",
"platform-compat-test-rules",
diff --git a/tests/unit/java/com/android/server/CallbackQueueTest.kt b/tests/unit/java/com/android/server/CallbackQueueTest.kt
new file mode 100644
index 0000000..a6dd5c3
--- /dev/null
+++ b/tests/unit/java/com/android/server/CallbackQueueTest.kt
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server
+
+import android.net.ConnectivityManager
+import android.net.ConnectivityManager.CALLBACK_AVAILABLE
+import android.net.ConnectivityManager.CALLBACK_CAP_CHANGED
+import android.net.ConnectivityManager.CALLBACK_IP_CHANGED
+import android.os.Build
+import androidx.test.filters.SmallTest
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import java.lang.reflect.Modifier
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val TEST_NETID_1 = 123
+
+// Maximum 16 bits unsigned value
+private const val TEST_NETID_2 = 0xffff
+
+@RunWith(DevSdkIgnoreRunner::class)
+@SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+class CallbackQueueTest {
+ @Test
+ fun testAddCallback() {
+ val cbs = listOf(
+ TEST_NETID_1 to CALLBACK_AVAILABLE,
+ TEST_NETID_2 to CALLBACK_AVAILABLE,
+ TEST_NETID_1 to CALLBACK_CAP_CHANGED,
+ TEST_NETID_1 to CALLBACK_CAP_CHANGED
+ )
+ val queue = CallbackQueue(intArrayOf()).apply {
+ cbs.forEach { addCallback(it.first, it.second) }
+ }
+
+ assertQueueEquals(cbs, queue)
+ }
+
+ @Test
+ fun testHasCallback() {
+ val queue = CallbackQueue(intArrayOf()).apply {
+ addCallback(TEST_NETID_1, CALLBACK_AVAILABLE)
+ addCallback(TEST_NETID_2, CALLBACK_AVAILABLE)
+ addCallback(TEST_NETID_1, CALLBACK_CAP_CHANGED)
+ addCallback(TEST_NETID_1, CALLBACK_CAP_CHANGED)
+ }
+
+ assertTrue(queue.hasCallback(TEST_NETID_1, CALLBACK_AVAILABLE))
+ assertTrue(queue.hasCallback(TEST_NETID_2, CALLBACK_AVAILABLE))
+ assertTrue(queue.hasCallback(TEST_NETID_1, CALLBACK_CAP_CHANGED))
+
+ assertFalse(queue.hasCallback(TEST_NETID_2, CALLBACK_CAP_CHANGED))
+ assertFalse(queue.hasCallback(1234, CALLBACK_AVAILABLE))
+ assertFalse(queue.hasCallback(TEST_NETID_1, 5678))
+ assertFalse(queue.hasCallback(1234, 5678))
+ }
+
+ @Test
+ fun testRemoveCallbacks() {
+ val queue = CallbackQueue(intArrayOf()).apply {
+ assertFalse(removeCallbacks(TEST_NETID_1, CALLBACK_AVAILABLE))
+ addCallback(TEST_NETID_1, CALLBACK_AVAILABLE)
+ addCallback(TEST_NETID_1, CALLBACK_CAP_CHANGED)
+ addCallback(TEST_NETID_2, CALLBACK_AVAILABLE)
+ addCallback(TEST_NETID_1, CALLBACK_AVAILABLE)
+ assertTrue(removeCallbacks(TEST_NETID_1, CALLBACK_AVAILABLE))
+ }
+ assertQueueEquals(listOf(
+ TEST_NETID_1 to CALLBACK_CAP_CHANGED,
+ TEST_NETID_2 to CALLBACK_AVAILABLE
+ ), queue)
+ }
+
+ @Test
+ fun testRemoveCallbacksForNetId() {
+ val queue = CallbackQueue(intArrayOf()).apply {
+ assertFalse(removeCallbacksForNetId(TEST_NETID_2))
+ addCallback(TEST_NETID_2, CALLBACK_AVAILABLE)
+ assertFalse(removeCallbacksForNetId(TEST_NETID_1))
+ addCallback(TEST_NETID_1, CALLBACK_AVAILABLE)
+ addCallback(TEST_NETID_1, CALLBACK_CAP_CHANGED)
+ addCallback(TEST_NETID_2, CALLBACK_CAP_CHANGED)
+ addCallback(TEST_NETID_1, CALLBACK_AVAILABLE)
+ addCallback(TEST_NETID_2, CALLBACK_IP_CHANGED)
+ assertTrue(removeCallbacksForNetId(TEST_NETID_2))
+ }
+ assertQueueEquals(listOf(
+ TEST_NETID_1 to CALLBACK_AVAILABLE,
+ TEST_NETID_1 to CALLBACK_CAP_CHANGED,
+ TEST_NETID_1 to CALLBACK_AVAILABLE,
+ ), queue)
+ }
+
+ @Test
+ fun testConstructorFromExistingArray() {
+ val queue1 = CallbackQueue(intArrayOf()).apply {
+ addCallback(TEST_NETID_1, CALLBACK_AVAILABLE)
+ addCallback(TEST_NETID_2, CALLBACK_AVAILABLE)
+ }
+ val queue2 = CallbackQueue(queue1.shrinkedBackingArray)
+ assertQueueEquals(listOf(
+ TEST_NETID_1 to CALLBACK_AVAILABLE,
+ TEST_NETID_2 to CALLBACK_AVAILABLE
+ ), queue2)
+ }
+
+ @Test
+ fun testToString() {
+ assertEquals("[]", CallbackQueue(intArrayOf()).toString())
+ assertEquals(
+ "[CALLBACK_AVAILABLE($TEST_NETID_1)]",
+ CallbackQueue(intArrayOf()).apply {
+ addCallback(TEST_NETID_1, CALLBACK_AVAILABLE)
+ }.toString()
+ )
+ assertEquals(
+ "[CALLBACK_AVAILABLE($TEST_NETID_1),CALLBACK_CAP_CHANGED($TEST_NETID_2)]",
+ CallbackQueue(intArrayOf()).apply {
+ addCallback(TEST_NETID_1, CALLBACK_AVAILABLE)
+ addCallback(TEST_NETID_2, CALLBACK_CAP_CHANGED)
+ }.toString()
+ )
+ }
+
+ @Test
+ fun testMaxNetId() {
+ // CallbackQueue assumes netIds are at most 16 bits
+ assertTrue(NetIdManager.MAX_NET_ID <= 0xffff)
+ }
+
+ @Test
+ fun testMaxCallbackId() {
+ // CallbackQueue assumes callback IDs are at most 16 bits.
+ val constants = ConnectivityManager::class.java.declaredFields.filter {
+ Modifier.isStatic(it.modifiers) && Modifier.isFinal(it.modifiers) &&
+ it.name.startsWith("CALLBACK_")
+ }
+ constants.forEach {
+ it.isAccessible = true
+ assertTrue(it.get(null) as Int <= 0xffff)
+ }
+ }
+}
+
+private fun assertQueueEquals(expected: List<Pair<Int, Int>>, actual: CallbackQueue) {
+ assertEquals(
+ expected.size,
+ actual.length(),
+ "Size mismatch between expected: $expected and actual: $actual"
+ )
+
+ var nextIndex = 0
+ actual.forEach { netId, cbId ->
+ val (expNetId, expCbId) = expected[nextIndex]
+ val msg = "$actual does not match $expected at index $nextIndex"
+ assertEquals(expNetId, netId, msg)
+ assertEquals(expCbId, cbId, msg)
+ nextIndex++
+ }
+ // Ensure forEach iterations and size are consistent
+ assertEquals(expected.size, nextIndex)
+}
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index be7f2a3..999d17d 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -2180,6 +2180,7 @@
case ConnectivityFlags.CARRIER_SERVICE_CHANGED_USE_CALLBACK:
case ConnectivityFlags.REQUEST_RESTRICTED_WIFI:
case ConnectivityFlags.USE_DECLARED_METHODS_FOR_CALLBACKS:
+ case ConnectivityFlags.QUEUE_CALLBACKS_FOR_FROZEN_APPS:
case KEY_DESTROY_FROZEN_SOCKETS_VERSION:
return true;
default:
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
index fb3d183..4c71991 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
@@ -18,8 +18,10 @@
import static com.android.server.connectivity.mdns.MdnsSocketProvider.SocketCallback;
import static com.android.server.connectivity.mdns.MulticastPacketReader.PacketHandler;
+import static com.android.testutils.Cleanup.testAndCleanup;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
@@ -35,6 +37,7 @@
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
+import android.util.Log;
import com.android.net.module.util.HexDump;
import com.android.net.module.util.SharedLog;
@@ -59,6 +62,7 @@
import java.net.SocketException;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
@RunWith(DevSdkIgnoreRunner.class)
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
@@ -437,4 +441,34 @@
inOrder.verify(mSocket).send(packets.get(i));
}
}
+
+ @Test
+ public void testSendPacketWithMultiplePacketsWithDifferentAddresses() throws IOException {
+ final SocketCallback callback = expectSocketCallback();
+ final DatagramPacket ipv4Packet = new DatagramPacket(BUFFER, 0 /* offset */, BUFFER.length,
+ InetAddresses.parseNumericAddress("192.0.2.1"), 0 /* port */);
+ final DatagramPacket ipv6Packet = new DatagramPacket(BUFFER, 0 /* offset */, BUFFER.length,
+ InetAddresses.parseNumericAddress("2001:db8::"), 0 /* port */);
+ doReturn(true).when(mSocket).hasJoinedIpv4();
+ doReturn(true).when(mSocket).hasJoinedIpv6();
+ doReturn(createEmptyNetworkInterface()).when(mSocket).getInterface();
+
+ // Notify socket created
+ callback.onSocketCreated(mSocketKey, mSocket, List.of());
+ verify(mSocketCreationCallback).onSocketCreated(mSocketKey);
+
+ // Send packets with IPv4 and IPv6 then verify wtf logs and sending has never been called.
+ // Override the default TerribleFailureHandler, as that handler might terminate the process
+ // (if we're on an eng build).
+ final AtomicBoolean hasFailed = new AtomicBoolean(false);
+ final Log.TerribleFailureHandler originalHandler =
+ Log.setWtfHandler((tag, what, system) -> hasFailed.set(true));
+ testAndCleanup(() -> {
+ mSocketClient.sendPacketRequestingMulticastResponse(List.of(ipv4Packet, ipv6Packet),
+ mSocketKey, false /* onlyUseIpv6OnIpv6OnlyNetworks */);
+ HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ assertTrue(hasFailed.get());
+ verify(mSocket, never()).send(any());
+ }, () -> Log.setWtfHandler(originalHandler));
+ }
}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java
index 1989ed3..ab70e38 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java
@@ -16,6 +16,7 @@
package com.android.server.connectivity.mdns;
+import static com.android.testutils.Cleanup.testAndCleanup;
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static org.junit.Assert.assertFalse;
@@ -38,9 +39,11 @@
import android.annotation.RequiresPermission;
import android.content.Context;
import android.net.ConnectivityManager;
+import android.net.InetAddresses;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.MulticastLock;
import android.text.format.DateUtils;
+import android.util.Log;
import com.android.net.module.util.HexDump;
import com.android.net.module.util.SharedLog;
@@ -594,6 +597,29 @@
}
}
+ @Test
+ public void testSendPacketWithMultiplePacketsWithDifferentAddresses() throws IOException {
+ mdnsClient.startDiscovery();
+ final byte[] buffer = new byte[10];
+ final DatagramPacket ipv4Packet = new DatagramPacket(buffer, 0 /* offset */, buffer.length,
+ InetAddresses.parseNumericAddress("192.0.2.1"), 0 /* port */);
+ final DatagramPacket ipv6Packet = new DatagramPacket(buffer, 0 /* offset */, buffer.length,
+ InetAddresses.parseNumericAddress("2001:db8::"), 0 /* port */);
+
+ // Send packets with IPv4 and IPv6 then verify wtf logs and sending has never been called.
+ // Override the default TerribleFailureHandler, as that handler might terminate the process
+ // (if we're on an eng build).
+ final AtomicBoolean hasFailed = new AtomicBoolean(false);
+ final Log.TerribleFailureHandler originalHandler =
+ Log.setWtfHandler((tag, what, system) -> hasFailed.set(true));
+ testAndCleanup(() -> {
+ mdnsClient.sendPacketRequestingMulticastResponse(List.of(ipv4Packet, ipv6Packet),
+ false /* onlyUseIpv6OnIpv6OnlyNetworks */);
+ assertTrue(hasFailed.get());
+ verify(mockMulticastSocket, never()).send(any());
+ }, () -> Log.setWtfHandler(originalHandler));
+ }
+
private DatagramPacket getTestDatagramPacket() {
return new DatagramPacket(buf, 0, 5,
new InetSocketAddress(MdnsConstants.getMdnsIPv4Address(), 5353 /* port */));
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/util/MdnsUtilsTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/util/MdnsUtilsTest.kt
index 009205e..cf88d05 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/util/MdnsUtilsTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/util/MdnsUtilsTest.kt
@@ -16,9 +16,12 @@
package com.android.server.connectivity.mdns.util
+import android.net.InetAddresses
import android.os.Build
import com.android.server.connectivity.mdns.MdnsConstants
import com.android.server.connectivity.mdns.MdnsConstants.FLAG_TRUNCATED
+import com.android.server.connectivity.mdns.MdnsConstants.IPV4_SOCKET_ADDR
+import com.android.server.connectivity.mdns.MdnsConstants.IPV6_SOCKET_ADDR
import com.android.server.connectivity.mdns.MdnsPacket
import com.android.server.connectivity.mdns.MdnsPacketReader
import com.android.server.connectivity.mdns.MdnsPointerRecord
@@ -193,4 +196,31 @@
}
return MdnsPacket(flags, questions, answers, emptyList(), emptyList())
}
+
+ @Test
+ fun testCheckAllPacketsWithSameAddress() {
+ val buffer = ByteArray(10)
+ val v4Packet = DatagramPacket(buffer, buffer.size, IPV4_SOCKET_ADDR)
+ val otherV4Packet = DatagramPacket(
+ buffer,
+ buffer.size,
+ InetAddresses.parseNumericAddress("192.0.2.1"),
+ 1234
+ )
+ val v6Packet = DatagramPacket(ByteArray(10), 10, IPV6_SOCKET_ADDR)
+ val otherV6Packet = DatagramPacket(
+ buffer,
+ buffer.size,
+ InetAddresses.parseNumericAddress("2001:db8::"),
+ 1234
+ )
+ assertTrue(MdnsUtils.checkAllPacketsWithSameAddress(listOf()))
+ assertTrue(MdnsUtils.checkAllPacketsWithSameAddress(listOf(v4Packet)))
+ assertTrue(MdnsUtils.checkAllPacketsWithSameAddress(listOf(v4Packet, v4Packet)))
+ assertFalse(MdnsUtils.checkAllPacketsWithSameAddress(listOf(v4Packet, otherV4Packet)))
+ assertTrue(MdnsUtils.checkAllPacketsWithSameAddress(listOf(v6Packet)))
+ assertTrue(MdnsUtils.checkAllPacketsWithSameAddress(listOf(v6Packet, v6Packet)))
+ assertFalse(MdnsUtils.checkAllPacketsWithSameAddress(listOf(v6Packet, otherV6Packet)))
+ assertFalse(MdnsUtils.checkAllPacketsWithSameAddress(listOf(v4Packet, v6Packet)))
+ }
}
diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
index ed72fd2..de56ae5 100644
--- a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
@@ -165,6 +165,7 @@
it[ConnectivityFlags.BACKGROUND_FIREWALL_CHAIN] = true
it[ConnectivityFlags.DELAY_DESTROY_SOCKETS] = true
it[ConnectivityFlags.USE_DECLARED_METHODS_FOR_CALLBACKS] = true
+ it[ConnectivityFlags.QUEUE_CALLBACKS_FOR_FROZEN_APPS] = true
}
fun setFeatureEnabled(flag: String, enabled: Boolean) = enabledFeatures.set(flag, enabled)
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
index 2f60d9a..e6f272b 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
@@ -111,7 +111,6 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserManager;
-import android.provider.Settings;
import android.util.Log;
import android.util.SparseArray;
@@ -139,6 +138,7 @@
import java.time.Clock;
import java.time.DateTimeException;
import java.time.Instant;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -210,7 +210,6 @@
private final ThreadPersistentSettings mPersistentSettings;
private final UserManager mUserManager;
private boolean mUserRestricted;
- private boolean mAirplaneModeOn;
private boolean mForceStopOtDaemonEnabled;
private BorderRouterConfigurationParcel mBorderRouterConfig;
@@ -344,8 +343,8 @@
final String modelName = resources.getString(R.string.config_thread_model_name);
final String vendorName = resources.getString(R.string.config_thread_vendor_name);
final String vendorOui = resources.getString(R.string.config_thread_vendor_oui);
- final boolean managedByGoogle =
- resources.getBoolean(R.bool.config_thread_managed_by_google_home);
+ final String[] vendorSpecificTxts =
+ resources.getStringArray(R.array.config_thread_mdns_vendor_specific_txts);
if (!modelName.isEmpty()) {
if (modelName.getBytes(UTF_8).length > MAX_MODEL_NAME_UTF8_BYTES) {
@@ -375,19 +374,44 @@
meshcopTxts.modelName = modelName;
meshcopTxts.vendorName = vendorName;
meshcopTxts.vendorOui = HexEncoding.decode(vendorOui.replace("-", "").replace(":", ""));
- meshcopTxts.nonStandardTxtEntries = List.of(makeManagedByGoogleTxtAttr(managedByGoogle));
+ meshcopTxts.nonStandardTxtEntries = makeVendorSpecificTxtAttrs(vendorSpecificTxts);
return meshcopTxts;
}
/**
- * Creates a DNS-SD TXT entry for indicating whether Thread on this device is managed by Google.
+ * Parses vendor-specific TXT entries from "=" separated strings into list of {@link
+ * DnsTxtAttribute}.
*
- * @return TXT entry "vgh=1" if {@code managedByGoogle} is {@code true}; otherwise, "vgh=0"
+ * @throws IllegalArgumentsException if invalid TXT entries are found in {@code vendorTxts}
*/
- private static DnsTxtAttribute makeManagedByGoogleTxtAttr(boolean managedByGoogle) {
- final byte[] value = (managedByGoogle ? "1" : "0").getBytes(UTF_8);
- return new DnsTxtAttribute("vgh", value);
+ @VisibleForTesting
+ static List<DnsTxtAttribute> makeVendorSpecificTxtAttrs(String[] vendorTxts) {
+ List<DnsTxtAttribute> txts = new ArrayList<>();
+ for (String txt : vendorTxts) {
+ String[] kv = txt.split("=", 2 /* limit */); // Split with only the first '='
+ if (kv.length < 1) {
+ throw new IllegalArgumentException(
+ "Invalid vendor-specific TXT is found in resources: " + txt);
+ }
+
+ if (kv[0].length() < 2) {
+ throw new IllegalArgumentException(
+ "Invalid vendor-specific TXT key \""
+ + kv[0]
+ + "\": it must contain at least 2 characters");
+ }
+
+ if (!kv[0].startsWith("v")) {
+ throw new IllegalArgumentException(
+ "Invalid vendor-specific TXT key \""
+ + kv[0]
+ + "\": it doesn't start with \"v\"");
+ }
+
+ txts.add(new DnsTxtAttribute(kv[0], (kv.length >= 2 ? kv[1] : "").getBytes(UTF_8)));
+ }
+ return txts;
}
private void onOtDaemonDied() {
@@ -420,8 +444,6 @@
requestThreadNetwork();
mUserRestricted = isThreadUserRestricted();
registerUserRestrictionsReceiver();
- mAirplaneModeOn = isAirplaneModeOn();
- registerAirplaneModeReceiver();
maybeInitializeOtDaemon();
});
}
@@ -503,15 +525,6 @@
// the otDaemon set enabled state operation succeeded or not, so that it can recover
// to the desired value after reboot.
mPersistentSettings.put(ThreadPersistentSettings.THREAD_ENABLED.key, isEnabled);
-
- // Remember whether the user wanted to keep Thread enabled in airplane mode. If once
- // the user disabled Thread again in airplane mode, the persistent settings state is
- // reset (so that Thread will be auto-disabled again when airplane mode is turned on).
- // This behavior is consistent with Wi-Fi and bluetooth.
- if (mAirplaneModeOn) {
- mPersistentSettings.put(
- ThreadPersistentSettings.THREAD_ENABLED_IN_AIRPLANE_MODE.key, isEnabled);
- }
}
try {
@@ -598,10 +611,12 @@
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- mHandler.post(() -> onUserRestrictionsChanged(isThreadUserRestricted()));
+ onUserRestrictionsChanged(isThreadUserRestricted());
}
},
- new IntentFilter(UserManager.ACTION_USER_RESTRICTIONS_CHANGED));
+ new IntentFilter(UserManager.ACTION_USER_RESTRICTIONS_CHANGED),
+ null /* broadcastPermission */,
+ mHandler);
}
private void onUserRestrictionsChanged(boolean newUserRestrictedState) {
@@ -648,72 +663,13 @@
return mUserManager.hasUserRestriction(DISALLOW_THREAD_NETWORK);
}
- private void registerAirplaneModeReceiver() {
- mContext.registerReceiver(
- new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- mHandler.post(() -> onAirplaneModeChanged(isAirplaneModeOn()));
- }
- },
- new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED));
- }
-
- private void onAirplaneModeChanged(boolean newAirplaneModeOn) {
- checkOnHandlerThread();
- if (mAirplaneModeOn == newAirplaneModeOn) {
- return;
- }
- Log.i(TAG, "Airplane mode changed: " + mAirplaneModeOn + " -> " + newAirplaneModeOn);
- mAirplaneModeOn = newAirplaneModeOn;
-
- final boolean shouldEnableThread = shouldEnableThread();
- final IOperationReceiver receiver =
- new IOperationReceiver.Stub() {
- @Override
- public void onSuccess() {
- Log.d(
- TAG,
- (shouldEnableThread ? "Enabled" : "Disabled")
- + " Thread due to airplane mode change");
- }
-
- @Override
- public void onError(int errorCode, String errorMessage) {
- Log.e(
- TAG,
- "Failed to "
- + (shouldEnableThread ? "enable" : "disable")
- + " Thread for airplane mode change");
- }
- };
- // Do not save the user restriction state to persistent settings so that the user
- // configuration won't be overwritten
- setEnabledInternal(
- shouldEnableThread, false /* persist */, new OperationReceiverWrapper(receiver));
- }
-
- /** Returns {@code true} if Airplane mode has been turned on. */
- private boolean isAirplaneModeOn() {
- return Settings.Global.getInt(
- mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0)
- == 1;
- }
-
/**
* Returns {@code true} if Thread should be enabled based on current settings, runtime user
- * restriction and airplane mode state.
+ * restriction state.
*/
private boolean shouldEnableThread() {
- final boolean enabledInAirplaneMode =
- mPersistentSettings.get(ThreadPersistentSettings.THREAD_ENABLED_IN_AIRPLANE_MODE);
-
return !mForceStopOtDaemonEnabled
&& !mUserRestricted
- // FIXME(b/340744397): Note that here we need to call `isAirplaneModeOn()` to get
- // the latest state of airplane mode but can't use `mIsAirplaneMode`. This is for
- // avoiding the race conditions described in b/340744397
- && (!isAirplaneModeOn() || enabledInAirplaneMode)
&& mPersistentSettings.get(ThreadPersistentSettings.THREAD_ENABLED);
}
diff --git a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
index 41f34ff..22e7a98 100644
--- a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
+++ b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
@@ -45,7 +45,6 @@
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail;
-
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.content.Context;
@@ -859,7 +858,6 @@
assertThat(txtMap.get("rv")).isNotNull();
assertThat(txtMap.get("tv")).isNotNull();
assertThat(txtMap.get("sb")).isNotNull();
- assertThat(new String(txtMap.get("vgh"))).isIn(List.of("0", "1"));
}
@Test
@@ -886,7 +884,6 @@
assertThat(txtMap.get("tv")).isNotNull();
assertThat(txtMap.get("sb")).isNotNull();
assertThat(txtMap.get("id").length).isEqualTo(16);
- assertThat(new String(txtMap.get("vgh"))).isIn(List.of("0", "1"));
}
@Test
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 6e2369f..eaf11b1 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
@@ -26,7 +26,6 @@
import static android.net.thread.ThreadNetworkManager.PERMISSION_THREAD_NETWORK_PRIVILEGED;
import static com.android.server.thread.ThreadNetworkCountryCode.DEFAULT_COUNTRY_CODE;
-import static com.android.server.thread.ThreadPersistentSettings.THREAD_ENABLED_IN_AIRPLANE_MODE;
import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_INVALID_STATE;
import static com.google.common.io.BaseEncoding.base16;
@@ -49,6 +48,8 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -68,7 +69,6 @@
import android.os.SystemClock;
import android.os.UserManager;
import android.os.test.TestLooper;
-import android.provider.Settings;
import android.util.AtomicFile;
import androidx.test.annotation.UiThreadTest;
@@ -96,11 +96,11 @@
import org.mockito.MockitoAnnotations;
import org.mockito.MockitoSession;
-import java.nio.charset.StandardCharsets;
import java.time.Clock;
import java.time.DateTimeException;
import java.time.Instant;
import java.time.ZoneId;
+import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicReference;
@@ -150,7 +150,6 @@
private static final byte[] TEST_VENDOR_OUI_BYTES = new byte[] {(byte) 0xAC, (byte) 0xDE, 0x48};
private static final String TEST_VENDOR_NAME = "test vendor";
private static final String TEST_MODEL_NAME = "test model";
- private static final boolean TEST_VGH_VALUE = false;
@Mock private ConnectivityManager mMockConnectivityManager;
@Mock private NetworkAgent mMockNetworkAgent;
@@ -193,8 +192,6 @@
when(mMockUserManager.hasUserRestriction(eq(DISALLOW_THREAD_NETWORK))).thenReturn(false);
- Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0);
-
when(mConnectivityResources.get()).thenReturn(mResources);
when(mResources.getBoolean(eq(R.bool.config_thread_default_enabled))).thenReturn(true);
when(mResources.getString(eq(R.string.config_thread_vendor_name)))
@@ -203,8 +200,8 @@
.thenReturn(TEST_VENDOR_OUI);
when(mResources.getString(eq(R.string.config_thread_model_name)))
.thenReturn(TEST_MODEL_NAME);
- when(mResources.getBoolean(eq(R.bool.config_thread_managed_by_google_home)))
- .thenReturn(TEST_VGH_VALUE);
+ when(mResources.getStringArray(eq(R.array.config_thread_mdns_vendor_specific_txts)))
+ .thenReturn(new String[] {});
final AtomicFile storageFile = new AtomicFile(tempFolder.newFile("thread_settings.xml"));
mPersistentSettings = new ThreadPersistentSettings(storageFile, mConnectivityResources);
@@ -247,8 +244,8 @@
.thenReturn(TEST_VENDOR_OUI);
when(mResources.getString(eq(R.string.config_thread_model_name)))
.thenReturn(TEST_MODEL_NAME);
- when(mResources.getBoolean(eq(R.bool.config_thread_managed_by_google_home)))
- .thenReturn(true);
+ when(mResources.getStringArray(eq(R.array.config_thread_mdns_vendor_specific_txts)))
+ .thenReturn(new String[] {"vt=test"});
mService.initialize();
mTestLooper.dispatchAll();
@@ -258,19 +255,7 @@
assertThat(meshcopTxts.vendorOui).isEqualTo(TEST_VENDOR_OUI_BYTES);
assertThat(meshcopTxts.modelName).isEqualTo(TEST_MODEL_NAME);
assertThat(meshcopTxts.nonStandardTxtEntries)
- .containsExactly(new DnsTxtAttribute("vgh", "1".getBytes(StandardCharsets.UTF_8)));
- }
-
- @Test
- public void getMeshcopTxtAttributes_managedByGoogleIsFalse_vghIsZero() {
- when(mResources.getBoolean(eq(R.bool.config_thread_managed_by_google_home)))
- .thenReturn(false);
-
- MeshcopTxtAttributes meshcopTxts =
- ThreadNetworkControllerService.getMeshcopTxtAttributes(mResources);
-
- assertThat(meshcopTxts.nonStandardTxtEntries)
- .containsExactly(new DnsTxtAttribute("vgh", "0".getBytes(StandardCharsets.UTF_8)));
+ .containsExactly(new DnsTxtAttribute("vt", "test".getBytes(UTF_8)));
}
@Test
@@ -343,6 +328,61 @@
}
@Test
+ public void makeVendorSpecificTxtAttrs_validTxts_returnsParsedTxtAttrs() {
+ String[] txts = new String[] {"va=123", "vb=", "vc"};
+
+ List<DnsTxtAttribute> attrs = mService.makeVendorSpecificTxtAttrs(txts);
+
+ assertThat(attrs)
+ .containsExactly(
+ new DnsTxtAttribute("va", "123".getBytes(UTF_8)),
+ new DnsTxtAttribute("vb", new byte[] {}),
+ new DnsTxtAttribute("vc", new byte[] {}));
+ }
+
+ @Test
+ public void makeVendorSpecificTxtAttrs_txtKeyNotStartWithV_throwsIllegalArgument() {
+ String[] txts = new String[] {"abc=123"};
+
+ assertThrows(
+ IllegalArgumentException.class, () -> mService.makeVendorSpecificTxtAttrs(txts));
+ }
+
+ @Test
+ public void makeVendorSpecificTxtAttrs_txtIsTooShort_throwsIllegalArgument() {
+ String[] txtEmptyKey = new String[] {"=123"};
+ String[] txtSingleCharKey = new String[] {"v=456"};
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mService.makeVendorSpecificTxtAttrs(txtEmptyKey));
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mService.makeVendorSpecificTxtAttrs(txtSingleCharKey));
+ }
+
+ @Test
+ public void makeVendorSpecificTxtAttrs_txtValueIsEmpty_parseSuccess() {
+ String[] txts = new String[] {"va=", "vb"};
+
+ List<DnsTxtAttribute> attrs = mService.makeVendorSpecificTxtAttrs(txts);
+
+ assertThat(attrs)
+ .containsExactly(
+ new DnsTxtAttribute("va", new byte[] {}),
+ new DnsTxtAttribute("vb", new byte[] {}));
+ }
+
+ @Test
+ public void makeVendorSpecificTxtAttrs_multipleEquals_splittedByTheFirstEqual() {
+ String[] txts = new String[] {"va=abc=def=123"};
+
+ List<DnsTxtAttribute> attrs = mService.makeVendorSpecificTxtAttrs(txts);
+
+ assertThat(attrs).containsExactly(new DnsTxtAttribute("va", "abc=def=123".getBytes(UTF_8)));
+ }
+
+ @Test
public void join_otDaemonRemoteFailure_returnsInternalError() throws Exception {
mService.initialize();
final IOperationReceiver mockReceiver = mock(IOperationReceiver.class);
@@ -437,100 +477,6 @@
assertThat(failure.getErrorCode()).isEqualTo(ERROR_FAILED_PRECONDITION);
}
- @Test
- public void airplaneMode_initWithAirplaneModeOn_otDaemonNotStarted() {
- Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
-
- mService.initialize();
- mTestLooper.dispatchAll();
-
- assertThat(mFakeOtDaemon.isInitialized()).isFalse();
- }
-
- @Test
- public void airplaneMode_initWithAirplaneModeOff_threadIsEnabled() {
- Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0);
-
- mService.initialize();
- mTestLooper.dispatchAll();
-
- assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(STATE_ENABLED);
- }
-
- @Test
- public void airplaneMode_changesFromOffToOn_stateIsDisabled() {
- Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0);
- AtomicReference<BroadcastReceiver> receiverRef =
- captureBroadcastReceiver(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- mService.initialize();
- mTestLooper.dispatchAll();
-
- Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
- receiverRef.get().onReceive(mContext, new Intent());
- mTestLooper.dispatchAll();
-
- assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(STATE_DISABLED);
- }
-
- @Test
- public void airplaneMode_changesFromOnToOff_stateIsEnabled() {
- Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
- AtomicReference<BroadcastReceiver> receiverRef =
- captureBroadcastReceiver(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- mService.initialize();
- mTestLooper.dispatchAll();
-
- Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0);
- receiverRef.get().onReceive(mContext, new Intent());
- mTestLooper.dispatchAll();
-
- assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(STATE_ENABLED);
- }
-
- @Test
- public void airplaneMode_setEnabledWhenAirplaneModeIsOn_WillNotAutoDisableSecondTime() {
- Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
- AtomicReference<BroadcastReceiver> receiverRef =
- captureBroadcastReceiver(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- CompletableFuture<Void> setEnabledFuture = new CompletableFuture<>();
- mService.initialize();
-
- mService.setEnabled(true, newOperationReceiver(setEnabledFuture));
- mTestLooper.dispatchAll();
- Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0);
- receiverRef.get().onReceive(mContext, new Intent());
- mTestLooper.dispatchAll();
- Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
- receiverRef.get().onReceive(mContext, new Intent());
- mTestLooper.dispatchAll();
-
- assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(STATE_ENABLED);
- assertThat(mPersistentSettings.get(THREAD_ENABLED_IN_AIRPLANE_MODE)).isTrue();
- }
-
- @Test
- public void airplaneMode_setDisabledWhenAirplaneModeIsOn_WillAutoDisableSecondTime() {
- Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
- AtomicReference<BroadcastReceiver> receiverRef =
- captureBroadcastReceiver(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- CompletableFuture<Void> setEnabledFuture = new CompletableFuture<>();
- mService.initialize();
- mService.setEnabled(true, newOperationReceiver(setEnabledFuture));
- mTestLooper.dispatchAll();
-
- mService.setEnabled(false, newOperationReceiver(setEnabledFuture));
- mTestLooper.dispatchAll();
- Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0);
- receiverRef.get().onReceive(mContext, new Intent());
- mTestLooper.dispatchAll();
- Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
- receiverRef.get().onReceive(mContext, new Intent());
- mTestLooper.dispatchAll();
-
- assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(STATE_DISABLED);
- assertThat(mPersistentSettings.get(THREAD_ENABLED_IN_AIRPLANE_MODE)).isFalse();
- }
-
private AtomicReference<BroadcastReceiver> captureBroadcastReceiver(String action) {
AtomicReference<BroadcastReceiver> receiverRef = new AtomicReference<>();
@@ -542,7 +488,9 @@
.when(mContext)
.registerReceiver(
any(BroadcastReceiver.class),
- argThat(actualIntentFilter -> actualIntentFilter.hasAction(action)));
+ argThat(actualIntentFilter -> actualIntentFilter.hasAction(action)),
+ any(),
+ any());
return receiverRef;
}