From aa0c7d00cf1e79b4887ac3870af0bd9535176a21 Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Thu, 16 Apr 2020 16:49:33 +0000 Subject: [PATCH 1/4] Support changing the NAT64 prefix without removing it. This cannot (currently) happen with DNS64 detection, but it can happen with the PREF64 option. Bug: 150648313 Test: atest ConnectivityServiceTest Nat464XlatTest --rerun-until-failure 100 Merged-In: I789fe9d46d3ac5d074ae697d23013f24a9e0246d Change-Id: I789fe9d46d3ac5d074ae697d23013f24a9e0246d --- .../server/connectivity/Nat464Xlat.java | 34 +++++++++++++++---- .../server/ConnectivityServiceTest.java | 25 ++++++++++++-- 2 files changed, 50 insertions(+), 9 deletions(-) diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java index 741cb5b41e..e6b2d2678f 100644 --- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java +++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java @@ -81,6 +81,9 @@ public class Nat464Xlat extends BaseNetworkObserver { RUNNING, // start() called, and the stacked iface is known to be up. } + /** NAT64 prefix currently in use. Only valid in STARTING or RUNNING states. */ + private IpPrefix mNat64PrefixInUse; + /** NAT64 prefix (if any) discovered from DNS via RFC 7050. */ private IpPrefix mNat64PrefixFromDns; private String mBaseIface; private String mIface; @@ -178,9 +181,10 @@ public class Nat464Xlat extends BaseNetworkObserver { return; } + mNat64PrefixInUse = getNat64Prefix(); String addrStr = null; try { - addrStr = mNetd.clatdStart(baseIface, getNat64Prefix().toString()); + addrStr = mNetd.clatdStart(baseIface, mNat64PrefixInUse.toString()); } catch (RemoteException | ServiceSpecificException e) { Slog.e(TAG, "Error starting clatd on " + baseIface + ": " + e); } @@ -211,12 +215,13 @@ public class Nat464Xlat extends BaseNetworkObserver { } catch (RemoteException | IllegalStateException e) { Slog.e(TAG, "Error unregistering clatd observer on " + mBaseIface + ": " + e); } + mNat64PrefixInUse = null; mIface = null; mBaseIface = null; if (requiresClat(mNetwork)) { mState = State.DISCOVERING; } else { - stopPrefixDiscovery(); + stopPrefixDiscovery(); // Enters IDLE state. } } @@ -274,19 +279,32 @@ public class Nat464Xlat extends BaseNetworkObserver { private void startPrefixDiscovery() { try { mDnsResolver.startPrefix64Discovery(getNetId()); - mState = State.DISCOVERING; } catch (RemoteException | ServiceSpecificException e) { Slog.e(TAG, "Error starting prefix discovery on netId " + getNetId() + ": " + e); } + mState = State.DISCOVERING; } private void stopPrefixDiscovery() { try { mDnsResolver.stopPrefix64Discovery(getNetId()); - mState = State.IDLE; } catch (RemoteException | ServiceSpecificException e) { Slog.e(TAG, "Error stopping prefix discovery on netId " + getNetId() + ": " + e); } + mState = State.IDLE; + } + + private void maybeHandleNat64PrefixChange() { + final IpPrefix newPrefix = getNat64Prefix(); + if (!Objects.equals(mNat64PrefixInUse, newPrefix)) { + Slog.d(TAG, "NAT64 prefix changed from " + mNat64PrefixInUse + " to " + + newPrefix); + stop(); + // It's safe to call update here, even though this method is called from update, because + // stop() is guaranteed to have moved out of STARTING and RUNNING, which are the only + // states in which this method can be called. + update(); + } } /** @@ -325,11 +343,11 @@ public class Nat464Xlat extends BaseNetworkObserver { // Stop clatd and go back into DISCOVERING or idle. if (!shouldStartClat(mNetwork)) { stop(); + break; } + // Only necessary while clat is actually started. + maybeHandleNat64PrefixChange(); break; - // TODO: support the NAT64 prefix changing after it's been discovered. There is - // no need to support this at the moment because it cannot happen without - // changes to the Dns64Configuration code in netd. } } @@ -347,6 +365,8 @@ public class Nat464Xlat extends BaseNetworkObserver { * has no idea that 464xlat is running on top of it. */ public void fixupLinkProperties(@NonNull LinkProperties oldLp, @NonNull LinkProperties lp) { + // This must be done even if clatd is not running, because otherwise shouldStartClat would + // never return true. lp.setNat64Prefix(getNat64Prefix()); if (!isRunning()) { diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index b864e37f9e..dad0363a80 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -5969,6 +5969,9 @@ public class ConnectivityServiceTest { final LinkAddress myIpv6 = new LinkAddress("2001:db8:1::1/64"); final String kNat64PrefixString = "2001:db8:64:64:64:64::"; final IpPrefix kNat64Prefix = new IpPrefix(InetAddress.getByName(kNat64PrefixString), 96); + final String kOtherNat64PrefixString = "64:ff9b::"; + final IpPrefix kOtherNat64Prefix = new IpPrefix( + InetAddress.getByName(kOtherNat64PrefixString), 96); final RouteInfo defaultRoute = new RouteInfo((IpPrefix) null, myIpv6.getAddress(), MOBILE_IFNAME); final RouteInfo ipv6Subnet = new RouteInfo(myIpv6, null, MOBILE_IFNAME); @@ -6082,6 +6085,24 @@ public class ConnectivityServiceTest { } reset(mMockNetd); + // Change the NAT64 prefix without first removing it. + // Expect clatd to be stopped and started with the new prefix. + mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, true /* added */, + kOtherNat64PrefixString, 96); + networkCallback.expectLinkPropertiesThat(mCellNetworkAgent, + (lp) -> lp.getStackedLinks().size() == 0); + verify(mMockNetd, times(1)).clatdStop(MOBILE_IFNAME); + assertRoutesRemoved(cellNetId, stackedDefault); + + verify(mMockNetd, times(1)).clatdStart(MOBILE_IFNAME, kOtherNat64Prefix.toString()); + networkCallback.expectLinkPropertiesThat(mCellNetworkAgent, + (lp) -> lp.getNat64Prefix().equals(kOtherNat64Prefix)); + clat.interfaceLinkStateChanged(CLAT_PREFIX + MOBILE_IFNAME, true); + networkCallback.expectLinkPropertiesThat(mCellNetworkAgent, + (lp) -> lp.getStackedLinks().size() == 1); + assertRoutesAdded(cellNetId, stackedDefault); + reset(mMockNetd); + // Add ipv4 address, expect that clatd and prefix discovery are stopped and stacked // linkproperties are cleaned up. cellLp.addLinkAddress(myIpv4); @@ -6096,7 +6117,7 @@ public class ConnectivityServiceTest { networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); LinkProperties actualLpAfterIpv4 = mCm.getLinkProperties(mCellNetworkAgent.getNetwork()); LinkProperties expected = new LinkProperties(cellLp); - expected.setNat64Prefix(kNat64Prefix); + expected.setNat64Prefix(kOtherNat64Prefix); assertEquals(expected, actualLpAfterIpv4); assertEquals(0, actualLpAfterIpv4.getStackedLinks().size()); assertRoutesRemoved(cellNetId, stackedDefault); @@ -6115,7 +6136,7 @@ public class ConnectivityServiceTest { // Stopping prefix discovery causes netd to tell us that the NAT64 prefix is gone. mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, false /* added */, - kNat64PrefixString, 96); + kOtherNat64PrefixString, 96); networkCallback.expectLinkPropertiesThat(mCellNetworkAgent, (lp) -> lp.getNat64Prefix() == null); From 5559f1edd908ade95c8dc2928b4c6f433ac4383e Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Thu, 16 Apr 2020 16:49:56 +0000 Subject: [PATCH 2/4] Add a function to process LinkProperties coming from an agent. There are tasks that need to be performed when receiving LinkProperties directly from a NetworkAgent (either at registration time or in subsequent updates). Currently, the only example of such a task is calling ensureDirectlyConnectedRoutes. This is currently done in handleUpdateLinkProperties, which is often unnecessary, because that method iscalled in many other cases than when receiving properties directly from an agent. Ensuring directly connected routes only needs to be done when receiving LinkProperties from the agent, because ConnectivityService does not directly manipulate routes. This CL does not do much except remove these superfluous calls and add the method. A future CL will add more code to the method. Bug: 150648313 Test: atest ConnectivityServiceTest Merged-In: Ibeeb5f79e8afd3350c935934713d7882f2e0281f Change-Id: Ibeeb5f79e8afd3350c935934713d7882f2e0281f --- .../android/server/ConnectivityService.java | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 1a58d6047b..001db3bd6a 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -2722,7 +2722,9 @@ public class ConnectivityService extends IConnectivityManager.Stub break; } case NetworkAgent.EVENT_NETWORK_PROPERTIES_CHANGED: { - handleUpdateLinkProperties(nai, (LinkProperties) msg.obj); + LinkProperties newLp = (LinkProperties) msg.obj; + processLinkPropertiesFromAgent(nai, newLp); + handleUpdateLinkProperties(nai, newLp); break; } case NetworkAgent.EVENT_NETWORK_INFO_CHANGED: { @@ -5817,7 +5819,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } LinkProperties lp = new LinkProperties(linkProperties); - lp.ensureDirectlyConnectedRoutes(); + // TODO: Instead of passing mDefaultRequest, provide an API to determine whether a Network // satisfies mDefaultRequest. final NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities); @@ -5825,8 +5827,11 @@ public class ConnectivityService extends IConnectivityManager.Stub new Network(mNetIdManager.reserveNetId()), new NetworkInfo(networkInfo), lp, nc, currentScore, mContext, mTrackerHandler, new NetworkAgentConfig(networkAgentConfig), this, mNetd, mDnsResolver, mNMS, providerId); - // Make sure the network capabilities reflect what the agent info says. + + // Make sure the LinkProperties and NetworkCapabilities reflect what the agent info says. nai.getAndSetNetworkCapabilities(mixInCapabilities(nai, nc)); + processLinkPropertiesFromAgent(nai, nai.linkProperties); + final String extraInfo = networkInfo.getExtraInfo(); final String name = TextUtils.isEmpty(extraInfo) ? nai.networkCapabilities.getSsid() : extraInfo; @@ -5864,6 +5869,10 @@ public class ConnectivityService extends IConnectivityManager.Stub updateUids(nai, null, nai.networkCapabilities); } + private void processLinkPropertiesFromAgent(NetworkAgentInfo nai, LinkProperties lp) { + lp.ensureDirectlyConnectedRoutes(); + } + private void updateLinkProperties(NetworkAgentInfo networkAgent, LinkProperties newLp, @NonNull LinkProperties oldLp) { int netId = networkAgent.network.netId; @@ -6391,13 +6400,13 @@ public class ConnectivityService extends IConnectivityManager.Stub // Ignore updates for disconnected networks return; } - // newLp is already a defensive copy. - newLp.ensureDirectlyConnectedRoutes(); if (VDBG || DDBG) { log("Update of LinkProperties for " + nai.toShortString() + "; created=" + nai.created + "; everConnected=" + nai.everConnected); } + // TODO: eliminate this defensive copy after confirming that updateLinkProperties does not + // modify its oldLp parameter. updateLinkProperties(nai, newLp, new LinkProperties(nai.linkProperties)); } From 4e54d05cbc98d81f561c3903d6bc4e480652f62d Mon Sep 17 00:00:00 2001 From: Bruce Chen Date: Fri, 17 Apr 2020 04:28:04 +0000 Subject: [PATCH 3/4] Add testTransportTypesEqual to dnsmanager test cases A test that ensures that NetworkCapabilities.TRANSPORT_* is eaual to IDnsResolver.TRANSPORT_* for every possible value of each. Bug: 153267602 Test: atest FrameworksNetTests Merged-In: I6b23ccc6ce1659fdfd9573dfcd895f2c20fa9417 Change-Id: I3dd4ed0d1fcceca9c8aec9b3e6769603e4fa913b (cherry picked from commit 5f28e6f881e0ea52e8e96c1207654ce44b0d05a1) --- .../server/connectivity/DnsManagerTest.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/net/java/com/android/server/connectivity/DnsManagerTest.java b/tests/net/java/com/android/server/connectivity/DnsManagerTest.java index a392ae3f13..0a603b8e4b 100644 --- a/tests/net/java/com/android/server/connectivity/DnsManagerTest.java +++ b/tests/net/java/com/android/server/connectivity/DnsManagerTest.java @@ -18,6 +18,8 @@ package com.android.server.connectivity; import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF; import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; +import static android.net.NetworkCapabilities.MAX_TRANSPORT; +import static android.net.NetworkCapabilities.MIN_TRANSPORT; import static android.net.NetworkCapabilities.TRANSPORT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.provider.Settings.Global.PRIVATE_DNS_DEFAULT_MODE; @@ -30,6 +32,7 @@ import static com.android.testutils.MiscAssertsKt.assertFieldCountEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.reset; @@ -44,16 +47,19 @@ import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; +import android.net.NetworkCapabilities; import android.net.ResolverOptionsParcel; import android.net.ResolverParamsParcel; import android.net.RouteInfo; import android.net.shared.PrivateDnsConfig; import android.provider.Settings; import android.test.mock.MockContentResolver; +import android.util.SparseArray; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.util.MessageUtils; import com.android.internal.util.test.FakeSettingsProvider; import org.junit.Before; @@ -354,4 +360,23 @@ public class DnsManagerTest { expectedParams.resolverOptions = new ResolverOptionsParcel(); assertResolverParamsEquals(actualParams, expectedParams); } + + @Test + public void testTransportTypesEqual() throws Exception { + SparseArray ncTransTypes = MessageUtils.findMessageNames( + new Class[] { NetworkCapabilities.class }, new String[]{ "TRANSPORT_" }); + SparseArray dnsTransTypes = MessageUtils.findMessageNames( + new Class[] { IDnsResolver.class }, new String[]{ "TRANSPORT_" }); + assertEquals(0, MIN_TRANSPORT); + assertEquals(MAX_TRANSPORT + 1, ncTransTypes.size()); + // TRANSPORT_UNKNOWN in IDnsResolver is defined to -1 and only for resolver. + assertEquals("TRANSPORT_UNKNOWN", dnsTransTypes.get(-1)); + assertEquals(ncTransTypes.size(), dnsTransTypes.size() - 1); + for (int i = MIN_TRANSPORT; i < MAX_TRANSPORT; i++) { + String name = ncTransTypes.get(i, null); + assertNotNull("Could not find NetworkCapabilies.TRANSPORT_* constant equal to " + + i, name); + assertEquals(name, dnsTransTypes.get(i)); + } + } } From b9626ecd404e088e2a62af2b468b4c4de83345cd Mon Sep 17 00:00:00 2001 From: Aaron Huang Date: Fri, 17 Apr 2020 05:11:01 +0000 Subject: [PATCH 4/4] This contains a squash of two changes >>>>>>>>>>>>>>>>>>>>>> aosp/1284588 Adjust permission of NetworkProvider related API - Allow an app holds NETWORK_SETTINGS to acess registerNetworkProvier() and unregisterNetworkProvider(). - To access declareNetworkRequestUnfulfillable(), allow an app holds MANAGE_TEST_NETWORKS to declare a unfulfillable request that contains TRANSPORT_TEST transport. This makes easier to write cts to test. >>>>>>>>>>>>>>>>>>>>>> aosp/1285957 Add cts test for NetworkProvider It will skip whole tests on Q device since NetworkProvider class is introduced in R. Result on Q device would be: [1/1] android.net.NetworkProviderTest#skippedClassForDevSdkMismatch: IGNORED (3ms) >>>>>>>>>>>>>>>>>>>>>> Bug: 153614605 Bug: 153613690 Bug: 153612373 Test: atest FrameworksNetTests atest CtsNetTestCases:android.net.NetworkProviderTest Test: atest CtsNetTestCasesLatestSdk:android.net.NetworkProviderTest Change-Id: Ib6f42b8f0e94e8c2715a030587e065864edff25b Merged-In: Ic9809e731aa811a51c2f82d189372169d99a5ed9 Merged-In: If7bfc7fae503e3497c37754697d0b148ff4cab3b (cherry picked from commit 10138d42a8f3892fcdb129a39409efe42873f6fe) --- .../android/server/ConnectivityService.java | 24 ++- .../java/android/net/NetworkProviderTest.kt | 158 ++++++++++++++++++ 2 files changed, 179 insertions(+), 3 deletions(-) create mode 100644 tests/net/common/java/android/net/NetworkProviderTest.kt diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 001db3bd6a..f1ea5d0184 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -2093,6 +2093,20 @@ public class ConnectivityService extends IConnectivityManager.Stub NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK); } + private void enforceNetworkFactoryOrSettingsPermission() { + enforceAnyPermissionOf( + android.Manifest.permission.NETWORK_SETTINGS, + android.Manifest.permission.NETWORK_FACTORY, + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK); + } + + private void enforceNetworkFactoryOrTestNetworksPermission() { + enforceAnyPermissionOf( + android.Manifest.permission.MANAGE_TEST_NETWORKS, + android.Manifest.permission.NETWORK_FACTORY, + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK); + } + private boolean checkSettingsPermission() { return checkAnyPermissionOf( android.Manifest.permission.NETWORK_SETTINGS, @@ -5683,7 +5697,7 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public int registerNetworkProvider(Messenger messenger, String name) { - enforceNetworkFactoryPermission(); + enforceNetworkFactoryOrSettingsPermission(); NetworkProviderInfo npi = new NetworkProviderInfo(name, messenger, null /* asyncChannel */, nextNetworkProviderId(), () -> unregisterNetworkProvider(messenger)); @@ -5693,7 +5707,7 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public void unregisterNetworkProvider(Messenger messenger) { - enforceNetworkFactoryPermission(); + enforceNetworkFactoryOrSettingsPermission(); mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_NETWORK_PROVIDER, messenger)); } @@ -5713,7 +5727,11 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public void declareNetworkRequestUnfulfillable(NetworkRequest request) { - enforceNetworkFactoryPermission(); + if (request.hasTransport(TRANSPORT_TEST)) { + enforceNetworkFactoryOrTestNetworksPermission(); + } else { + enforceNetworkFactoryPermission(); + } mHandler.post(() -> handleReleaseNetworkRequest(request, Binder.getCallingUid(), true)); } diff --git a/tests/net/common/java/android/net/NetworkProviderTest.kt b/tests/net/common/java/android/net/NetworkProviderTest.kt new file mode 100644 index 0000000000..4601c4bf4a --- /dev/null +++ b/tests/net/common/java/android/net/NetworkProviderTest.kt @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net + +import android.app.Instrumentation +import android.content.Context +import android.net.NetworkCapabilities.TRANSPORT_TEST +import android.os.Build +import android.os.HandlerThread +import android.os.Looper +import android.net.NetworkProviderTest.TestNetworkCallback.CallbackEntry.OnUnavailable +import android.net.NetworkProviderTest.TestNetworkProvider.CallbackEntry.OnNetworkRequested +import android.net.NetworkProviderTest.TestNetworkProvider.CallbackEntry.OnNetworkRequestWithdrawn +import androidx.test.InstrumentationRegistry +import com.android.testutils.ArrayTrackRecord +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo +import com.android.testutils.DevSdkIgnoreRunner +import java.util.UUID +import kotlin.test.assertEquals +import kotlin.test.assertNotEquals +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +private const val DEFAULT_TIMEOUT_MS = 5000L +private val instrumentation: Instrumentation + get() = InstrumentationRegistry.getInstrumentation() +private val context: Context get() = InstrumentationRegistry.getContext() +private val PROVIDER_NAME = "NetworkProviderTest" + +@RunWith(DevSdkIgnoreRunner::class) +@IgnoreUpTo(Build.VERSION_CODES.Q) +class NetworkProviderTest { + private val mCm = context.getSystemService(ConnectivityManager::class.java) + private val mHandlerThread = HandlerThread("${javaClass.simpleName} handler thread") + + @Before + fun setUp() { + instrumentation.getUiAutomation().adoptShellPermissionIdentity() + mHandlerThread.start() + } + + @After + fun tearDown() { + mHandlerThread.quitSafely() + instrumentation.getUiAutomation().dropShellPermissionIdentity() + } + + private class TestNetworkProvider(context: Context, looper: Looper) : + NetworkProvider(context, looper, PROVIDER_NAME) { + private val seenEvents = ArrayTrackRecord().newReadHead() + + sealed class CallbackEntry { + data class OnNetworkRequested( + val request: NetworkRequest, + val score: Int, + val id: Int + ) : CallbackEntry() + data class OnNetworkRequestWithdrawn(val request: NetworkRequest) : CallbackEntry() + } + + override fun onNetworkRequested(request: NetworkRequest, score: Int, id: Int) { + seenEvents.add(OnNetworkRequested(request, score, id)) + } + + override fun onNetworkRequestWithdrawn(request: NetworkRequest) { + seenEvents.add(OnNetworkRequestWithdrawn(request)) + } + + inline fun expectCallback( + crossinline predicate: (T) -> Boolean + ) = seenEvents.poll(DEFAULT_TIMEOUT_MS) { it is T && predicate(it) } + } + + private fun createNetworkProvider(): TestNetworkProvider { + return TestNetworkProvider(context, mHandlerThread.looper) + } + + @Test + fun testOnNetworkRequested() { + val provider = createNetworkProvider() + assertEquals(provider.getProviderId(), NetworkProvider.ID_NONE) + mCm.registerNetworkProvider(provider) + assertNotEquals(provider.getProviderId(), NetworkProvider.ID_NONE) + + val specifier = StringNetworkSpecifier(UUID.randomUUID().toString()) + val nr: NetworkRequest = NetworkRequest.Builder() + .addTransportType(TRANSPORT_TEST) + .setNetworkSpecifier(specifier) + .build() + val cb = ConnectivityManager.NetworkCallback() + mCm.requestNetwork(nr, cb) + provider.expectCallback() { + callback -> callback.request.getNetworkSpecifier() == specifier && + callback.request.hasTransport(TRANSPORT_TEST) + } + + mCm.unregisterNetworkCallback(cb) + provider.expectCallback() { + callback -> callback.request.getNetworkSpecifier() == specifier && + callback.request.hasTransport(TRANSPORT_TEST) + } + mCm.unregisterNetworkProvider(provider) + // Provider id should be ID_NONE after unregister network provider + assertEquals(provider.getProviderId(), NetworkProvider.ID_NONE) + // unregisterNetworkProvider should not crash even if it's called on an + // already unregistered provider. + mCm.unregisterNetworkProvider(provider) + } + + private class TestNetworkCallback : ConnectivityManager.NetworkCallback() { + private val seenEvents = ArrayTrackRecord().newReadHead() + sealed class CallbackEntry { + object OnUnavailable : CallbackEntry() + } + + override fun onUnavailable() { + seenEvents.add(OnUnavailable) + } + + inline fun expectCallback( + crossinline predicate: (T) -> Boolean + ) = seenEvents.poll(DEFAULT_TIMEOUT_MS) { it is T && predicate(it) } + } + + @Test + fun testDeclareNetworkRequestUnfulfillable() { + val provider = createNetworkProvider() + mCm.registerNetworkProvider(provider) + + val specifier = StringNetworkSpecifier(UUID.randomUUID().toString()) + val nr: NetworkRequest = NetworkRequest.Builder() + .addTransportType(TRANSPORT_TEST) + .setNetworkSpecifier(specifier) + .build() + + val cb = TestNetworkCallback() + mCm.requestNetwork(nr, cb) + provider.declareNetworkRequestUnfulfillable(nr) + cb.expectCallback() { nr.getNetworkSpecifier() == specifier } + mCm.unregisterNetworkProvider(provider) + } +} \ No newline at end of file