/* * Copyright (C) 2012 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 static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.TYPE_MOBILE; import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.ConnectivityManager.getNetworkTypeName; import static android.net.NetworkCapabilities.*; import static org.mockito.Mockito.mock; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; import android.content.IntentFilter; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.ConnectivityManager.PacketKeepalive; import android.net.ConnectivityManager.PacketKeepaliveCallback; import android.net.INetworkPolicyManager; import android.net.INetworkStatsService; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkAgent; import android.net.NetworkCapabilities; import android.net.NetworkConfig; import android.net.NetworkFactory; import android.net.NetworkInfo; import android.net.NetworkInfo.DetailedState; import android.net.NetworkMisc; import android.net.NetworkRequest; import android.net.RouteInfo; import android.os.ConditionVariable; import android.os.Handler; import android.os.HandlerThread; import android.os.INetworkManagementService; import android.os.Looper; import android.os.Message; import android.os.MessageQueue; import android.os.MessageQueue.IdleHandler; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.LargeTest; import android.util.Log; import android.util.LogPrinter; import com.android.server.connectivity.NetworkAgentInfo; import com.android.server.connectivity.NetworkMonitor; import java.net.InetAddress; import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; /** * Tests for {@link ConnectivityService}. * * Build, install and run with: * runtest frameworks-services -c com.android.server.ConnectivityServiceTest */ public class ConnectivityServiceTest extends AndroidTestCase { private static final String TAG = "ConnectivityServiceTest"; private static final int TIMEOUT_MS = 500; private BroadcastInterceptingContext mServiceContext; private WrappedConnectivityService mService; private ConnectivityManager mCm; private MockNetworkAgent mWiFiNetworkAgent; private MockNetworkAgent mCellNetworkAgent; private class MockContext extends BroadcastInterceptingContext { MockContext(Context base) { super(base); } @Override public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { // PendingIntents sent by the AlarmManager are not intercepted by // BroadcastInterceptingContext so we must really register the receiver. // This shouldn't effect the real NetworkMonitors as the action contains a random token. if (filter.getAction(0).startsWith("android.net.netmon.lingerExpired")) { return getBaseContext().registerReceiver(receiver, filter); } else { return super.registerReceiver(receiver, filter); } } @Override public Object getSystemService (String name) { if (name == Context.CONNECTIVITY_SERVICE) return mCm; return super.getSystemService(name); } } /** * A subclass of HandlerThread that allows callers to wait for it to become idle. waitForIdle * will return immediately if the handler is already idle. */ private class IdleableHandlerThread extends HandlerThread { private IdleHandler mIdleHandler; public IdleableHandlerThread(String name) { super(name); } public void waitForIdle(int timeoutMs) { final ConditionVariable cv = new ConditionVariable(); final MessageQueue queue = getLooper().getQueue(); synchronized (queue) { if (queue.isIdle()) { return; } assertNull("BUG: only one idle handler allowed", mIdleHandler); mIdleHandler = new IdleHandler() { public boolean queueIdle() { cv.open(); mIdleHandler = null; return false; // Remove the handler. } }; queue.addIdleHandler(mIdleHandler); } if (!cv.block(timeoutMs)) { fail("HandlerThread " + getName() + " did not become idle after " + timeoutMs + " ms"); queue.removeIdleHandler(mIdleHandler); } } } // Tests that IdleableHandlerThread works as expected. public void testIdleableHandlerThread() { final int attempts = 50; // Causes the test to take about 200ms on bullhead-eng. // Tests that waitForIdle returns immediately if the service is already idle. for (int i = 0; i < attempts; i++) { mService.waitForIdle(); } // Bring up a network that we can use to send messages to ConnectivityService. ConditionVariable cv = waitForConnectivityBroadcasts(1); mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); waitFor(cv); Network n = mWiFiNetworkAgent.getNetwork(); assertNotNull(n); // Tests that calling waitForIdle waits for messages to be processed. for (int i = 0; i < attempts; i++) { mWiFiNetworkAgent.setSignalStrength(i); mService.waitForIdle(); assertEquals(i, mCm.getNetworkCapabilities(n).getSignalStrength()); } // Ensure that not calling waitForIdle causes a race condition. for (int i = 0; i < attempts; i++) { mWiFiNetworkAgent.setSignalStrength(i); if (i != mCm.getNetworkCapabilities(n).getSignalStrength()) { // We hit a race condition, as expected. Pass the test. return; } } // No race? There is a bug in this test. fail("expected race condition at least once in " + attempts + " attempts"); } private class MockNetworkAgent { private final WrappedNetworkMonitor mWrappedNetworkMonitor; private final NetworkInfo mNetworkInfo; private final NetworkCapabilities mNetworkCapabilities; private final IdleableHandlerThread mHandlerThread; private final ConditionVariable mDisconnected = new ConditionVariable(); private int mScore; private NetworkAgent mNetworkAgent; private int mStartKeepaliveError = PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED; private int mStopKeepaliveError = PacketKeepalive.NO_KEEPALIVE; private Integer mExpectedKeepaliveSlot = null; MockNetworkAgent(int transport) { final int type = transportToLegacyType(transport); final String typeName = ConnectivityManager.getNetworkTypeName(type); mNetworkInfo = new NetworkInfo(type, 0, typeName, "Mock"); mNetworkCapabilities = new NetworkCapabilities(); mNetworkCapabilities.addTransportType(transport); switch (transport) { case TRANSPORT_WIFI: mScore = 60; break; case TRANSPORT_CELLULAR: mScore = 50; break; default: throw new UnsupportedOperationException("unimplemented network type"); } mHandlerThread = new IdleableHandlerThread("Mock-" + typeName); mHandlerThread.start(); mNetworkAgent = new NetworkAgent(mHandlerThread.getLooper(), mServiceContext, "Mock-" + typeName, mNetworkInfo, mNetworkCapabilities, new LinkProperties(), mScore, new NetworkMisc()) { @Override public void unwanted() { mDisconnected.open(); } @Override public void startPacketKeepalive(Message msg) { int slot = msg.arg1; if (mExpectedKeepaliveSlot != null) { assertEquals((int) mExpectedKeepaliveSlot, slot); } onPacketKeepaliveEvent(slot, mStartKeepaliveError); } @Override public void stopPacketKeepalive(Message msg) { onPacketKeepaliveEvent(msg.arg1, mStopKeepaliveError); } }; // Waits for the NetworkAgent to be registered, which includes the creation of the // NetworkMonitor. mService.waitForIdle(); mWrappedNetworkMonitor = mService.getLastCreatedWrappedNetworkMonitor(); } public void waitForIdle(int timeoutMs) { mHandlerThread.waitForIdle(timeoutMs); } public void waitForIdle() { waitForIdle(TIMEOUT_MS); } public void adjustScore(int change) { mScore += change; mNetworkAgent.sendNetworkScore(mScore); } public void addCapability(int capability) { mNetworkCapabilities.addCapability(capability); mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities); } public void setSignalStrength(int signalStrength) { mNetworkCapabilities.setSignalStrength(signalStrength); mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities); } public void connectWithoutInternet() { mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, null); mNetworkAgent.sendNetworkInfo(mNetworkInfo); } /** * Transition this NetworkAgent to CONNECTED state with NET_CAPABILITY_INTERNET. * @param validated Indicate if network should pretend to be validated. */ public void connect(boolean validated) { assertEquals(mNetworkInfo.getDetailedState(), DetailedState.IDLE); assertFalse(mNetworkCapabilities.hasCapability(NET_CAPABILITY_INTERNET)); NetworkCallback callback = null; final ConditionVariable validatedCv = new ConditionVariable(); if (validated) { mWrappedNetworkMonitor.gen204ProbeResult = 204; NetworkRequest request = new NetworkRequest.Builder() .addTransportType(mNetworkCapabilities.getTransportTypes()[0]) .build(); callback = new NetworkCallback() { public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) { if (network.equals(getNetwork()) && networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) { validatedCv.open(); } } }; mCm.registerNetworkCallback(request, callback); } addCapability(NET_CAPABILITY_INTERNET); connectWithoutInternet(); if (validated) { // Wait for network to validate. waitFor(validatedCv); mWrappedNetworkMonitor.gen204ProbeResult = 500; } if (callback != null) mCm.unregisterNetworkCallback(callback); } public void connectWithCaptivePortal() { mWrappedNetworkMonitor.gen204ProbeResult = 200; connect(false); waitFor(new Criteria() { public boolean get() { NetworkCapabilities caps = mCm.getNetworkCapabilities(getNetwork()); return caps != null && caps.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL);} }); mWrappedNetworkMonitor.gen204ProbeResult = 500; } public void disconnect() { mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, null); mNetworkAgent.sendNetworkInfo(mNetworkInfo); } public Network getNetwork() { return new Network(mNetworkAgent.netId); } public ConditionVariable getDisconnectedCV() { return mDisconnected; } public WrappedNetworkMonitor getWrappedNetworkMonitor() { return mWrappedNetworkMonitor; } public void sendLinkProperties(LinkProperties lp) { mNetworkAgent.sendLinkProperties(lp); } public void setStartKeepaliveError(int error) { mStartKeepaliveError = error; } public void setStopKeepaliveError(int error) { mStopKeepaliveError = error; } public void setExpectedKeepaliveSlot(Integer slot) { mExpectedKeepaliveSlot = slot; } } /** * A NetworkFactory that allows tests to wait until any in-flight NetworkRequest add or remove * operations have been processed. Before ConnectivityService can add or remove any requests, * the factory must be told to expect those operations by calling expectAddRequests or * expectRemoveRequests. */ private static class MockNetworkFactory extends NetworkFactory { private final ConditionVariable mNetworkStartedCV = new ConditionVariable(); private final ConditionVariable mNetworkStoppedCV = new ConditionVariable(); private final AtomicBoolean mNetworkStarted = new AtomicBoolean(false); // Used to expect that requests be removed or added on a separate thread, without sleeping. // Callers can call either expectAddRequests() or expectRemoveRequests() exactly once, then // cause some other thread to add or remove requests, then call waitForRequests(). We can // either expect requests to be added or removed, but not both, because CountDownLatch can // only count in one direction. private CountDownLatch mExpectations; // Whether we are currently expecting requests to be added or removed. Valid only if // mExpectations is non-null. private boolean mExpectingAdditions; public MockNetworkFactory(Looper looper, Context context, String logTag, NetworkCapabilities filter) { super(looper, context, logTag, filter); } public int getMyRequestCount() { return getRequestCount(); } protected void startNetwork() { mNetworkStarted.set(true); mNetworkStartedCV.open(); } protected void stopNetwork() { mNetworkStarted.set(false); mNetworkStoppedCV.open(); } public boolean getMyStartRequested() { return mNetworkStarted.get(); } public ConditionVariable getNetworkStartedCV() { mNetworkStartedCV.close(); return mNetworkStartedCV; } public ConditionVariable getNetworkStoppedCV() { mNetworkStoppedCV.close(); return mNetworkStoppedCV; } @Override protected void handleAddRequest(NetworkRequest request, int score) { // If we're expecting anything, we must be expecting additions. if (mExpectations != null && !mExpectingAdditions) { fail("Can't add requests while expecting requests to be removed"); } // Add the request. super.handleAddRequest(request, score); // Reduce the number of request additions we're waiting for. if (mExpectingAdditions) { assertTrue("Added more requests than expected", mExpectations.getCount() > 0); mExpectations.countDown(); } } @Override protected void handleRemoveRequest(NetworkRequest request) { // If we're expecting anything, we must be expecting removals. if (mExpectations != null && mExpectingAdditions) { fail("Can't remove requests while expecting requests to be added"); } // Remove the request. super.handleRemoveRequest(request); // Reduce the number of request removals we're waiting for. if (!mExpectingAdditions) { assertTrue("Removed more requests than expected", mExpectations.getCount() > 0); mExpectations.countDown(); } } private void assertNoExpectations() { if (mExpectations != null) { fail("Can't add expectation, " + mExpectations.getCount() + " already pending"); } } // Expects that count requests will be added. public void expectAddRequests(final int count) { assertNoExpectations(); mExpectingAdditions = true; mExpectations = new CountDownLatch(count); } // Expects that count requests will be removed. public void expectRemoveRequests(final int count) { assertNoExpectations(); mExpectingAdditions = false; mExpectations = new CountDownLatch(count); } // Waits for the expected request additions or removals to happen within a timeout. public void waitForRequests() throws InterruptedException { assertNotNull("Nothing to wait for", mExpectations); mExpectations.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); final long count = mExpectations.getCount(); final String msg = count + " requests still not " + (mExpectingAdditions ? "added" : "removed") + " after " + TIMEOUT_MS + " ms"; assertEquals(msg, 0, count); mExpectations = null; } public void waitForNetworkRequests(final int count) throws InterruptedException { waitForRequests(); assertEquals(count, getMyRequestCount()); } } // NetworkMonitor implementation allowing overriding of Internet connectivity probe result. private class WrappedNetworkMonitor extends NetworkMonitor { // HTTP response code fed back to NetworkMonitor for Internet connectivity probe. public int gen204ProbeResult = 500; public WrappedNetworkMonitor(Context context, Handler handler, NetworkAgentInfo networkAgentInfo, NetworkRequest defaultRequest) { super(context, handler, networkAgentInfo, defaultRequest); } @Override protected int isCaptivePortal() { return gen204ProbeResult; } } private class WrappedConnectivityService extends ConnectivityService { private WrappedNetworkMonitor mLastCreatedNetworkMonitor; public WrappedConnectivityService(Context context, INetworkManagementService netManager, INetworkStatsService statsService, INetworkPolicyManager policyManager) { super(context, netManager, statsService, policyManager); } @Override protected HandlerThread createHandlerThread() { return new IdleableHandlerThread("WrappedConnectivityService"); } @Override protected int getDefaultTcpRwnd() { // Prevent wrapped ConnectivityService from trying to write to SystemProperties. return 0; } @Override protected int reserveNetId() { while (true) { final int netId = super.reserveNetId(); // Don't overlap test NetIDs with real NetIDs as binding sockets to real networks // can have odd side-effects, like network validations succeeding. final Network[] networks = ConnectivityManager.from(getContext()).getAllNetworks(); boolean overlaps = false; for (Network network : networks) { if (netId == network.netId) { overlaps = true; break; } } if (overlaps) continue; return netId; } } @Override public NetworkMonitor createNetworkMonitor(Context context, Handler handler, NetworkAgentInfo nai, NetworkRequest defaultRequest) { final WrappedNetworkMonitor monitor = new WrappedNetworkMonitor(context, handler, nai, defaultRequest); mLastCreatedNetworkMonitor = monitor; return monitor; } public WrappedNetworkMonitor getLastCreatedWrappedNetworkMonitor() { return mLastCreatedNetworkMonitor; } public void waitForIdle(int timeoutMs) { ((IdleableHandlerThread) mHandlerThread).waitForIdle(timeoutMs); } public void waitForIdle() { waitForIdle(TIMEOUT_MS); } } private interface Criteria { public boolean get(); } /** * Wait up to 500ms for {@code criteria.get()} to become true, polling. * Fails if 500ms goes by before {@code criteria.get()} to become true. */ static private void waitFor(Criteria criteria) { int delays = 0; while (!criteria.get()) { try { Thread.sleep(100); } catch (InterruptedException e) { } if (++delays == 5) fail(); } } /** * Wait up to TIMEOUT_MS for {@code conditionVariable} to open. * Fails if TIMEOUT_MS goes by before {@code conditionVariable} opens. */ static private void waitFor(ConditionVariable conditionVariable) { assertTrue(conditionVariable.block(TIMEOUT_MS)); } @Override public void setUp() throws Exception { super.setUp(); mServiceContext = new MockContext(getContext()); mService = new WrappedConnectivityService(mServiceContext, mock(INetworkManagementService.class), mock(INetworkStatsService.class), mock(INetworkPolicyManager.class)); mService.systemReady(); mCm = new ConnectivityManager(getContext(), mService); } private int transportToLegacyType(int transport) { switch (transport) { case TRANSPORT_WIFI: return TYPE_WIFI; case TRANSPORT_CELLULAR: return TYPE_MOBILE; default: throw new IllegalStateException("Unknown transport" + transport); } } private void verifyActiveNetwork(int transport) { // Test getActiveNetworkInfo() assertNotNull(mCm.getActiveNetworkInfo()); assertEquals(transportToLegacyType(transport), mCm.getActiveNetworkInfo().getType()); // Test getActiveNetwork() assertNotNull(mCm.getActiveNetwork()); switch (transport) { case TRANSPORT_WIFI: assertEquals(mCm.getActiveNetwork(), mWiFiNetworkAgent.getNetwork()); break; case TRANSPORT_CELLULAR: assertEquals(mCm.getActiveNetwork(), mCellNetworkAgent.getNetwork()); break; default: throw new IllegalStateException("Unknown transport" + transport); } // Test getNetworkInfo(Network) assertNotNull(mCm.getNetworkInfo(mCm.getActiveNetwork())); assertEquals(transportToLegacyType(transport), mCm.getNetworkInfo(mCm.getActiveNetwork()).getType()); // Test getNetworkCapabilities(Network) assertNotNull(mCm.getNetworkCapabilities(mCm.getActiveNetwork())); assertTrue(mCm.getNetworkCapabilities(mCm.getActiveNetwork()).hasTransport(transport)); } private void verifyNoNetwork() { // Test getActiveNetworkInfo() assertNull(mCm.getActiveNetworkInfo()); // Test getActiveNetwork() assertNull(mCm.getActiveNetwork()); // Test getAllNetworks() assertEquals(0, mCm.getAllNetworks().length); } /** * Return a ConditionVariable that opens when {@code count} numbers of CONNECTIVITY_ACTION * broadcasts are received. */ private ConditionVariable waitForConnectivityBroadcasts(final int count) { final ConditionVariable cv = new ConditionVariable(); mServiceContext.registerReceiver(new BroadcastReceiver() { private int remaining = count; public void onReceive(Context context, Intent intent) { if (--remaining == 0) { cv.open(); mServiceContext.unregisterReceiver(this); } } }, new IntentFilter(CONNECTIVITY_ACTION)); return cv; } @LargeTest public void testLingering() throws Exception { // Decrease linger timeout to the minimum allowed by AlarmManagerService. NetworkMonitor.SetDefaultLingerTime(5000); verifyNoNetwork(); mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); assertNull(mCm.getActiveNetworkInfo()); assertNull(mCm.getActiveNetwork()); // Test bringing up validated cellular. ConditionVariable cv = waitForConnectivityBroadcasts(1); mCellNetworkAgent.connect(true); waitFor(cv); verifyActiveNetwork(TRANSPORT_CELLULAR); assertEquals(2, mCm.getAllNetworks().length); assertTrue(mCm.getAllNetworks()[0].equals(mCm.getActiveNetwork()) || mCm.getAllNetworks()[1].equals(mCm.getActiveNetwork())); assertTrue(mCm.getAllNetworks()[0].equals(mWiFiNetworkAgent.getNetwork()) || mCm.getAllNetworks()[1].equals(mWiFiNetworkAgent.getNetwork())); // Test bringing up validated WiFi. cv = waitForConnectivityBroadcasts(2); mWiFiNetworkAgent.connect(true); waitFor(cv); verifyActiveNetwork(TRANSPORT_WIFI); assertEquals(2, mCm.getAllNetworks().length); assertTrue(mCm.getAllNetworks()[0].equals(mCm.getActiveNetwork()) || mCm.getAllNetworks()[1].equals(mCm.getActiveNetwork())); assertTrue(mCm.getAllNetworks()[0].equals(mCellNetworkAgent.getNetwork()) || mCm.getAllNetworks()[1].equals(mCellNetworkAgent.getNetwork())); // Test cellular linger timeout. try { Thread.sleep(6000); } catch (InterruptedException e) { } verifyActiveNetwork(TRANSPORT_WIFI); assertEquals(1, mCm.getAllNetworks().length); assertEquals(mCm.getAllNetworks()[0], mCm.getActiveNetwork()); // Test WiFi disconnect. cv = waitForConnectivityBroadcasts(1); mWiFiNetworkAgent.disconnect(); waitFor(cv); verifyNoNetwork(); } @LargeTest public void testValidatedCellularOutscoresUnvalidatedWiFi() throws Exception { // Test bringing up unvalidated WiFi mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); ConditionVariable cv = waitForConnectivityBroadcasts(1); mWiFiNetworkAgent.connect(false); waitFor(cv); verifyActiveNetwork(TRANSPORT_WIFI); // Test bringing up unvalidated cellular mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(false); mService.waitForIdle(); verifyActiveNetwork(TRANSPORT_WIFI); // Test cellular disconnect. mCellNetworkAgent.disconnect(); mService.waitForIdle(); verifyActiveNetwork(TRANSPORT_WIFI); // Test bringing up validated cellular mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); cv = waitForConnectivityBroadcasts(2); mCellNetworkAgent.connect(true); waitFor(cv); verifyActiveNetwork(TRANSPORT_CELLULAR); // Test cellular disconnect. cv = waitForConnectivityBroadcasts(2); mCellNetworkAgent.disconnect(); waitFor(cv); verifyActiveNetwork(TRANSPORT_WIFI); // Test WiFi disconnect. cv = waitForConnectivityBroadcasts(1); mWiFiNetworkAgent.disconnect(); waitFor(cv); verifyNoNetwork(); } @LargeTest public void testUnvalidatedWifiOutscoresUnvalidatedCellular() throws Exception { // Test bringing up unvalidated cellular. mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); ConditionVariable cv = waitForConnectivityBroadcasts(1); mCellNetworkAgent.connect(false); waitFor(cv); verifyActiveNetwork(TRANSPORT_CELLULAR); // Test bringing up unvalidated WiFi. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); cv = waitForConnectivityBroadcasts(2); mWiFiNetworkAgent.connect(false); waitFor(cv); verifyActiveNetwork(TRANSPORT_WIFI); // Test WiFi disconnect. cv = waitForConnectivityBroadcasts(2); mWiFiNetworkAgent.disconnect(); waitFor(cv); verifyActiveNetwork(TRANSPORT_CELLULAR); // Test cellular disconnect. cv = waitForConnectivityBroadcasts(1); mCellNetworkAgent.disconnect(); waitFor(cv); verifyNoNetwork(); } @LargeTest public void testUnlingeringDoesNotValidate() throws Exception { // Test bringing up unvalidated WiFi. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); ConditionVariable cv = waitForConnectivityBroadcasts(1); mWiFiNetworkAgent.connect(false); waitFor(cv); verifyActiveNetwork(TRANSPORT_WIFI); assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); // Test bringing up validated cellular. mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); cv = waitForConnectivityBroadcasts(2); mCellNetworkAgent.connect(true); waitFor(cv); verifyActiveNetwork(TRANSPORT_CELLULAR); assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); // Test cellular disconnect. cv = waitForConnectivityBroadcasts(2); mCellNetworkAgent.disconnect(); waitFor(cv); verifyActiveNetwork(TRANSPORT_WIFI); // Unlingering a network should not cause it to be marked as validated. assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); } @LargeTest public void testCellularOutscoresWeakWifi() throws Exception { // Test bringing up validated cellular. mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); ConditionVariable cv = waitForConnectivityBroadcasts(1); mCellNetworkAgent.connect(true); waitFor(cv); verifyActiveNetwork(TRANSPORT_CELLULAR); // Test bringing up validated WiFi. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); cv = waitForConnectivityBroadcasts(2); mWiFiNetworkAgent.connect(true); waitFor(cv); verifyActiveNetwork(TRANSPORT_WIFI); // Test WiFi getting really weak. cv = waitForConnectivityBroadcasts(2); mWiFiNetworkAgent.adjustScore(-11); waitFor(cv); verifyActiveNetwork(TRANSPORT_CELLULAR); // Test WiFi restoring signal strength. cv = waitForConnectivityBroadcasts(2); mWiFiNetworkAgent.adjustScore(11); waitFor(cv); verifyActiveNetwork(TRANSPORT_WIFI); mCellNetworkAgent.disconnect(); mWiFiNetworkAgent.disconnect(); } @LargeTest public void testReapingNetwork() throws Exception { // Test bringing up WiFi without NET_CAPABILITY_INTERNET. // Expect it to be torn down immediately because it satisfies no requests. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); ConditionVariable cv = mWiFiNetworkAgent.getDisconnectedCV(); mWiFiNetworkAgent.connectWithoutInternet(); waitFor(cv); // Test bringing up cellular without NET_CAPABILITY_INTERNET. // Expect it to be torn down immediately because it satisfies no requests. mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); cv = mCellNetworkAgent.getDisconnectedCV(); mCellNetworkAgent.connectWithoutInternet(); waitFor(cv); // Test bringing up validated WiFi. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); cv = waitForConnectivityBroadcasts(1); mWiFiNetworkAgent.connect(true); waitFor(cv); verifyActiveNetwork(TRANSPORT_WIFI); // Test bringing up unvalidated cellular. // Expect it to be torn down because it could never be the highest scoring network // satisfying the default request even if it validated. mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); cv = mCellNetworkAgent.getDisconnectedCV(); mCellNetworkAgent.connect(false); waitFor(cv); verifyActiveNetwork(TRANSPORT_WIFI); cv = mWiFiNetworkAgent.getDisconnectedCV(); mWiFiNetworkAgent.disconnect(); waitFor(cv); } @LargeTest public void testCellularFallback() throws Exception { // Test bringing up validated cellular. mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); ConditionVariable cv = waitForConnectivityBroadcasts(1); mCellNetworkAgent.connect(true); waitFor(cv); verifyActiveNetwork(TRANSPORT_CELLULAR); // Test bringing up validated WiFi. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); cv = waitForConnectivityBroadcasts(2); mWiFiNetworkAgent.connect(true); waitFor(cv); verifyActiveNetwork(TRANSPORT_WIFI); // Reevaluate WiFi (it'll instantly fail DNS). cv = waitForConnectivityBroadcasts(2); assertTrue(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); mCm.reportBadNetwork(mWiFiNetworkAgent.getNetwork()); // Should quickly fall back to Cellular. waitFor(cv); assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); verifyActiveNetwork(TRANSPORT_CELLULAR); // Reevaluate cellular (it'll instantly fail DNS). cv = waitForConnectivityBroadcasts(2); assertTrue(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); mCm.reportBadNetwork(mCellNetworkAgent.getNetwork()); // Should quickly fall back to WiFi. waitFor(cv); assertFalse(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); verifyActiveNetwork(TRANSPORT_WIFI); mCellNetworkAgent.disconnect(); mWiFiNetworkAgent.disconnect(); } @LargeTest public void testWiFiFallback() throws Exception { // Test bringing up unvalidated WiFi. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); ConditionVariable cv = waitForConnectivityBroadcasts(1); mWiFiNetworkAgent.connect(false); waitFor(cv); verifyActiveNetwork(TRANSPORT_WIFI); // Test bringing up validated cellular. mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); cv = waitForConnectivityBroadcasts(2); mCellNetworkAgent.connect(true); waitFor(cv); verifyActiveNetwork(TRANSPORT_CELLULAR); // Reevaluate cellular (it'll instantly fail DNS). cv = waitForConnectivityBroadcasts(2); assertTrue(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); mCm.reportBadNetwork(mCellNetworkAgent.getNetwork()); // Should quickly fall back to WiFi. waitFor(cv); assertFalse(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); verifyActiveNetwork(TRANSPORT_WIFI); mCellNetworkAgent.disconnect(); mWiFiNetworkAgent.disconnect(); } enum CallbackState { NONE, AVAILABLE, LOSING, LOST } /** * Utility NetworkCallback for testing. The caller must explicitly test for all the callbacks * this class receives, by calling expectCallback() exactly once each time a callback is * received. assertNoCallback may be called at any time. */ private class TestNetworkCallback extends NetworkCallback { private final ConditionVariable mConditionVariable = new ConditionVariable(); private CallbackState mLastCallback = CallbackState.NONE; public void onAvailable(Network network) { assertEquals(CallbackState.NONE, mLastCallback); mLastCallback = CallbackState.AVAILABLE; mConditionVariable.open(); } public void onLosing(Network network, int maxMsToLive) { assertEquals(CallbackState.NONE, mLastCallback); mLastCallback = CallbackState.LOSING; mConditionVariable.open(); } public void onLost(Network network) { assertEquals(CallbackState.NONE, mLastCallback); mLastCallback = CallbackState.LOST; mConditionVariable.open(); } void expectCallback(CallbackState state) { waitFor(mConditionVariable); assertEquals(state, mLastCallback); mLastCallback = CallbackState.NONE; mConditionVariable.close(); } void assertNoCallback() { assertEquals(CallbackState.NONE, mLastCallback); } } @LargeTest public void testStateChangeNetworkCallbacks() throws Exception { final TestNetworkCallback wifiNetworkCallback = new TestNetworkCallback(); final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback(); final NetworkRequest wifiRequest = new NetworkRequest.Builder() .addTransportType(TRANSPORT_WIFI).build(); final NetworkRequest cellRequest = new NetworkRequest.Builder() .addTransportType(TRANSPORT_CELLULAR).build(); mCm.registerNetworkCallback(wifiRequest, wifiNetworkCallback); mCm.registerNetworkCallback(cellRequest, cellNetworkCallback); // Test unvalidated networks ConditionVariable cv = waitForConnectivityBroadcasts(1); mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(false); cellNetworkCallback.expectCallback(CallbackState.AVAILABLE); wifiNetworkCallback.assertNoCallback(); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); waitFor(cv); // This should not trigger spurious onAvailable() callbacks, b/21762680. mCellNetworkAgent.adjustScore(-1); mService.waitForIdle(); wifiNetworkCallback.assertNoCallback(); cellNetworkCallback.assertNoCallback(); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); cv = waitForConnectivityBroadcasts(2); mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); wifiNetworkCallback.expectCallback(CallbackState.AVAILABLE); cellNetworkCallback.assertNoCallback(); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); waitFor(cv); cv = waitForConnectivityBroadcasts(2); mWiFiNetworkAgent.disconnect(); wifiNetworkCallback.expectCallback(CallbackState.LOST); cellNetworkCallback.assertNoCallback(); waitFor(cv); cv = waitForConnectivityBroadcasts(1); mCellNetworkAgent.disconnect(); cellNetworkCallback.expectCallback(CallbackState.LOST); wifiNetworkCallback.assertNoCallback(); waitFor(cv); // Test validated networks mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); cellNetworkCallback.expectCallback(CallbackState.AVAILABLE); wifiNetworkCallback.assertNoCallback(); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); // This should not trigger spurious onAvailable() callbacks, b/21762680. mCellNetworkAgent.adjustScore(-1); mService.waitForIdle(); wifiNetworkCallback.assertNoCallback(); cellNetworkCallback.assertNoCallback(); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); wifiNetworkCallback.expectCallback(CallbackState.AVAILABLE); cellNetworkCallback.expectCallback(CallbackState.LOSING); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); mWiFiNetworkAgent.disconnect(); wifiNetworkCallback.expectCallback(CallbackState.LOST); cellNetworkCallback.assertNoCallback(); mCellNetworkAgent.disconnect(); cellNetworkCallback.expectCallback(CallbackState.LOST); wifiNetworkCallback.assertNoCallback(); } private void tryNetworkFactoryRequests(int capability) throws Exception { // Verify NOT_RESTRICTED is set appropriately final NetworkCapabilities nc = new NetworkRequest.Builder().addCapability(capability) .build().networkCapabilities; if (capability == NET_CAPABILITY_CBS || capability == NET_CAPABILITY_DUN || capability == NET_CAPABILITY_EIMS || capability == NET_CAPABILITY_FOTA || capability == NET_CAPABILITY_IA || capability == NET_CAPABILITY_IMS || capability == NET_CAPABILITY_RCS || capability == NET_CAPABILITY_XCAP) { assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)); } else { assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)); } NetworkCapabilities filter = new NetworkCapabilities(); filter.addCapability(capability); final HandlerThread handlerThread = new HandlerThread("testNetworkFactoryRequests"); handlerThread.start(); final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(), mServiceContext, "testFactory", filter); testFactory.setScoreFilter(40); ConditionVariable cv = testFactory.getNetworkStartedCV(); testFactory.expectAddRequests(1); testFactory.register(); testFactory.waitForNetworkRequests(1); int expectedRequestCount = 1; NetworkCallback networkCallback = null; // For non-INTERNET capabilities we cannot rely on the default request being present, so // add one. if (capability != NET_CAPABILITY_INTERNET) { assertFalse(testFactory.getMyStartRequested()); NetworkRequest request = new NetworkRequest.Builder().addCapability(capability).build(); networkCallback = new NetworkCallback(); testFactory.expectAddRequests(1); mCm.requestNetwork(request, networkCallback); expectedRequestCount++; testFactory.waitForNetworkRequests(expectedRequestCount); } waitFor(cv); assertEquals(expectedRequestCount, testFactory.getMyRequestCount()); assertTrue(testFactory.getMyStartRequested()); // Now bring in a higher scored network. MockNetworkAgent testAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); // Rather than create a validated network which complicates things by registering it's // own NetworkRequest during startup, just bump up the score to cancel out the // unvalidated penalty. testAgent.adjustScore(40); cv = testFactory.getNetworkStoppedCV(); // When testAgent connects, ConnectivityService will re-send us all current requests with // the new score. There are expectedRequestCount such requests, and we must wait for all of // them. testFactory.expectAddRequests(expectedRequestCount); testAgent.connect(false); testAgent.addCapability(capability); waitFor(cv); testFactory.waitForNetworkRequests(expectedRequestCount); assertFalse(testFactory.getMyStartRequested()); // Bring in a bunch of requests. testFactory.expectAddRequests(10); assertEquals(expectedRequestCount, testFactory.getMyRequestCount()); ConnectivityManager.NetworkCallback[] networkCallbacks = new ConnectivityManager.NetworkCallback[10]; for (int i = 0; i< networkCallbacks.length; i++) { networkCallbacks[i] = new ConnectivityManager.NetworkCallback(); NetworkRequest.Builder builder = new NetworkRequest.Builder(); builder.addCapability(capability); mCm.requestNetwork(builder.build(), networkCallbacks[i]); } testFactory.waitForNetworkRequests(10 + expectedRequestCount); assertFalse(testFactory.getMyStartRequested()); // Remove the requests. testFactory.expectRemoveRequests(10); for (int i = 0; i < networkCallbacks.length; i++) { mCm.unregisterNetworkCallback(networkCallbacks[i]); } testFactory.waitForNetworkRequests(expectedRequestCount); assertFalse(testFactory.getMyStartRequested()); // Drop the higher scored network. cv = testFactory.getNetworkStartedCV(); testAgent.disconnect(); waitFor(cv); assertEquals(expectedRequestCount, testFactory.getMyRequestCount()); assertTrue(testFactory.getMyStartRequested()); testFactory.unregister(); if (networkCallback != null) mCm.unregisterNetworkCallback(networkCallback); handlerThread.quit(); } @LargeTest public void testNetworkFactoryRequests() throws Exception { tryNetworkFactoryRequests(NET_CAPABILITY_MMS); tryNetworkFactoryRequests(NET_CAPABILITY_SUPL); tryNetworkFactoryRequests(NET_CAPABILITY_DUN); tryNetworkFactoryRequests(NET_CAPABILITY_FOTA); tryNetworkFactoryRequests(NET_CAPABILITY_IMS); tryNetworkFactoryRequests(NET_CAPABILITY_CBS); tryNetworkFactoryRequests(NET_CAPABILITY_WIFI_P2P); tryNetworkFactoryRequests(NET_CAPABILITY_IA); tryNetworkFactoryRequests(NET_CAPABILITY_RCS); tryNetworkFactoryRequests(NET_CAPABILITY_XCAP); tryNetworkFactoryRequests(NET_CAPABILITY_EIMS); tryNetworkFactoryRequests(NET_CAPABILITY_NOT_METERED); tryNetworkFactoryRequests(NET_CAPABILITY_INTERNET); tryNetworkFactoryRequests(NET_CAPABILITY_TRUSTED); tryNetworkFactoryRequests(NET_CAPABILITY_NOT_VPN); // Skipping VALIDATED and CAPTIVE_PORTAL as they're disallowed. } @LargeTest public void testNoMutableNetworkRequests() throws Exception { PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, new Intent("a"), 0); NetworkRequest.Builder builder = new NetworkRequest.Builder(); builder.addCapability(NET_CAPABILITY_VALIDATED); try { mCm.requestNetwork(builder.build(), new NetworkCallback()); fail(); } catch (IllegalArgumentException expected) {} try { mCm.requestNetwork(builder.build(), pendingIntent); fail(); } catch (IllegalArgumentException expected) {} builder = new NetworkRequest.Builder(); builder.addCapability(NET_CAPABILITY_CAPTIVE_PORTAL); try { mCm.requestNetwork(builder.build(), new NetworkCallback()); fail(); } catch (IllegalArgumentException expected) {} try { mCm.requestNetwork(builder.build(), pendingIntent); fail(); } catch (IllegalArgumentException expected) {} } @LargeTest public void testMMSonWiFi() throws Exception { // Test bringing up cellular without MMS NetworkRequest gets reaped mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.addCapability(NET_CAPABILITY_MMS); ConditionVariable cv = mCellNetworkAgent.getDisconnectedCV(); mCellNetworkAgent.connectWithoutInternet(); waitFor(cv); waitFor(new Criteria() { public boolean get() { return mCm.getAllNetworks().length == 0; } }); verifyNoNetwork(); // Test bringing up validated WiFi. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); cv = waitForConnectivityBroadcasts(1); mWiFiNetworkAgent.connect(true); waitFor(cv); verifyActiveNetwork(TRANSPORT_WIFI); // Register MMS NetworkRequest NetworkRequest.Builder builder = new NetworkRequest.Builder(); builder.addCapability(NetworkCapabilities.NET_CAPABILITY_MMS); final TestNetworkCallback networkCallback = new TestNetworkCallback(); mCm.requestNetwork(builder.build(), networkCallback); // Test bringing up unvalidated cellular with MMS mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.addCapability(NET_CAPABILITY_MMS); mCellNetworkAgent.connectWithoutInternet(); networkCallback.expectCallback(CallbackState.AVAILABLE); verifyActiveNetwork(TRANSPORT_WIFI); // Test releasing NetworkRequest disconnects cellular with MMS cv = mCellNetworkAgent.getDisconnectedCV(); mCm.unregisterNetworkCallback(networkCallback); waitFor(cv); verifyActiveNetwork(TRANSPORT_WIFI); } @LargeTest public void testMMSonCell() throws Exception { // Test bringing up cellular without MMS mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); ConditionVariable cv = waitForConnectivityBroadcasts(1); mCellNetworkAgent.connect(false); waitFor(cv); verifyActiveNetwork(TRANSPORT_CELLULAR); // Register MMS NetworkRequest NetworkRequest.Builder builder = new NetworkRequest.Builder(); builder.addCapability(NetworkCapabilities.NET_CAPABILITY_MMS); final TestNetworkCallback networkCallback = new TestNetworkCallback(); mCm.requestNetwork(builder.build(), networkCallback); // Test bringing up MMS cellular network MockNetworkAgent mmsNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mmsNetworkAgent.addCapability(NET_CAPABILITY_MMS); mmsNetworkAgent.connectWithoutInternet(); networkCallback.expectCallback(CallbackState.AVAILABLE); verifyActiveNetwork(TRANSPORT_CELLULAR); // Test releasing MMS NetworkRequest does not disconnect main cellular NetworkAgent cv = mmsNetworkAgent.getDisconnectedCV(); mCm.unregisterNetworkCallback(networkCallback); waitFor(cv); verifyActiveNetwork(TRANSPORT_CELLULAR); } @LargeTest public void testCaptivePortal() { final TestNetworkCallback captivePortalCallback = new TestNetworkCallback(); final NetworkRequest captivePortalRequest = new NetworkRequest.Builder() .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build(); mCm.registerNetworkCallback(captivePortalRequest, captivePortalCallback); final TestNetworkCallback validatedCallback = new TestNetworkCallback(); final NetworkRequest validatedRequest = new NetworkRequest.Builder() .addCapability(NET_CAPABILITY_VALIDATED).build(); mCm.registerNetworkCallback(validatedRequest, validatedCallback); // Bring up a network with a captive portal. // Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connectWithCaptivePortal(); captivePortalCallback.expectCallback(CallbackState.AVAILABLE); // Take down network. // Expect onLost callback. mWiFiNetworkAgent.disconnect(); captivePortalCallback.expectCallback(CallbackState.LOST); // Bring up a network with a captive portal. // Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connectWithCaptivePortal(); captivePortalCallback.expectCallback(CallbackState.AVAILABLE); // Make captive portal disappear then revalidate. // Expect onLost callback because network no longer provides NET_CAPABILITY_CAPTIVE_PORTAL. mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 204; mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true); captivePortalCallback.expectCallback(CallbackState.LOST); // Expect NET_CAPABILITY_VALIDATED onAvailable callback. validatedCallback.expectCallback(CallbackState.AVAILABLE); // Break network connectivity. // Expect NET_CAPABILITY_VALIDATED onLost callback. mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 500; mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false); validatedCallback.expectCallback(CallbackState.LOST); } private static class TestKeepaliveCallback extends PacketKeepaliveCallback { public static enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR }; private class CallbackValue { public CallbackType callbackType; public int error; public CallbackValue(CallbackType type) { this.callbackType = type; this.error = PacketKeepalive.SUCCESS; assertTrue("onError callback must have error", type != CallbackType.ON_ERROR); } public CallbackValue(CallbackType type, int error) { this.callbackType = type; this.error = error; assertEquals("error can only be set for onError", type, CallbackType.ON_ERROR); } @Override public boolean equals(Object o) { return o instanceof CallbackValue && this.callbackType == ((CallbackValue) o).callbackType && this.error == ((CallbackValue) o).error; } @Override public String toString() { return String.format("%s(%s, %d)", getClass().getSimpleName(), callbackType, error); } } private LinkedBlockingQueue mCallbacks = new LinkedBlockingQueue<>(); @Override public void onStarted() { mCallbacks.add(new CallbackValue(CallbackType.ON_STARTED)); } @Override public void onStopped() { mCallbacks.add(new CallbackValue(CallbackType.ON_STOPPED)); } @Override public void onError(int error) { mCallbacks.add(new CallbackValue(CallbackType.ON_ERROR, error)); } private void expectCallback(CallbackValue callbackValue) { try { assertEquals( callbackValue, mCallbacks.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS)); } catch (InterruptedException e) { fail(callbackValue.callbackType + " callback not seen after " + TIMEOUT_MS + " ms"); } } public void expectStarted() { expectCallback(new CallbackValue(CallbackType.ON_STARTED)); } public void expectStopped() { expectCallback(new CallbackValue(CallbackType.ON_STOPPED)); } public void expectError(int error) { expectCallback(new CallbackValue(CallbackType.ON_ERROR, error)); } } private Network connectKeepaliveNetwork(LinkProperties lp) { // Ensure the network is disconnected before we do anything. if (mWiFiNetworkAgent != null) { assertNull(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork())); } mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); ConditionVariable cv = waitForConnectivityBroadcasts(1); mWiFiNetworkAgent.connect(true); waitFor(cv); verifyActiveNetwork(TRANSPORT_WIFI); mWiFiNetworkAgent.sendLinkProperties(lp); mService.waitForIdle(); return mWiFiNetworkAgent.getNetwork(); } public void testPacketKeepalives() throws Exception { InetAddress myIPv4 = InetAddress.getByName("192.0.2.129"); InetAddress notMyIPv4 = InetAddress.getByName("192.0.2.35"); InetAddress myIPv6 = InetAddress.getByName("2001:db8::1"); InetAddress dstIPv4 = InetAddress.getByName("8.8.8.8"); InetAddress dstIPv6 = InetAddress.getByName("2001:4860:4860::8888"); LinkProperties lp = new LinkProperties(); lp.setInterfaceName("wlan12"); lp.addLinkAddress(new LinkAddress(myIPv6, 64)); lp.addLinkAddress(new LinkAddress(myIPv4, 25)); lp.addRoute(new RouteInfo(InetAddress.getByName("fe80::1234"))); lp.addRoute(new RouteInfo(InetAddress.getByName("192.0.2.254"))); Network notMyNet = new Network(61234); Network myNet = connectKeepaliveNetwork(lp); TestKeepaliveCallback callback = new TestKeepaliveCallback(); PacketKeepalive ka; // Attempt to start keepalives with invalid parameters and check for errors. ka = mCm.startNattKeepalive(notMyNet, 25, callback, myIPv4, 1234, dstIPv4); callback.expectError(PacketKeepalive.ERROR_INVALID_NETWORK); ka = mCm.startNattKeepalive(myNet, 19, callback, notMyIPv4, 1234, dstIPv4); callback.expectError(PacketKeepalive.ERROR_INVALID_INTERVAL); ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 1234, dstIPv6); callback.expectError(PacketKeepalive.ERROR_INVALID_IP_ADDRESS); ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv6, 1234, dstIPv4); callback.expectError(PacketKeepalive.ERROR_INVALID_IP_ADDRESS); ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv6, 1234, dstIPv6); callback.expectError(PacketKeepalive.ERROR_INVALID_IP_ADDRESS); // NAT-T is IPv4-only. ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 123456, dstIPv4); callback.expectError(PacketKeepalive.ERROR_INVALID_PORT); ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 123456, dstIPv4); callback.expectError(PacketKeepalive.ERROR_INVALID_PORT); ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 12345, dstIPv4); callback.expectError(PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED); ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 12345, dstIPv4); callback.expectError(PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED); // Check that a started keepalive can be stopped. mWiFiNetworkAgent.setStartKeepaliveError(PacketKeepalive.SUCCESS); ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 12345, dstIPv4); callback.expectStarted(); mWiFiNetworkAgent.setStopKeepaliveError(PacketKeepalive.SUCCESS); ka.stop(); callback.expectStopped(); // Check that deleting the IP address stops the keepalive. LinkProperties bogusLp = new LinkProperties(lp); ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 12345, dstIPv4); callback.expectStarted(); bogusLp.removeLinkAddress(new LinkAddress(myIPv4, 25)); bogusLp.addLinkAddress(new LinkAddress(notMyIPv4, 25)); mWiFiNetworkAgent.sendLinkProperties(bogusLp); callback.expectError(PacketKeepalive.ERROR_INVALID_IP_ADDRESS); mWiFiNetworkAgent.sendLinkProperties(lp); // Check that a started keepalive is stopped correctly when the network disconnects. ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 12345, dstIPv4); callback.expectStarted(); mWiFiNetworkAgent.disconnect(); callback.expectError(PacketKeepalive.ERROR_INVALID_NETWORK); // ... and that stopping it after that has no adverse effects. assertNull(mCm.getNetworkCapabilities(myNet)); ka.stop(); // Reconnect. myNet = connectKeepaliveNetwork(lp); mWiFiNetworkAgent.setStartKeepaliveError(PacketKeepalive.SUCCESS); // Check things work as expected when the keepalive is stopped and the network disconnects. ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 12345, dstIPv4); callback.expectStarted(); ka.stop(); mWiFiNetworkAgent.disconnect(); mService.waitForIdle(); callback.expectStopped(); // Reconnect. myNet = connectKeepaliveNetwork(lp); mWiFiNetworkAgent.setStartKeepaliveError(PacketKeepalive.SUCCESS); // Check that keepalive slots start from 1 and increment. The first one gets slot 1. mWiFiNetworkAgent.setExpectedKeepaliveSlot(1); ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 12345, dstIPv4); callback.expectStarted(); // The second one gets slot 2. mWiFiNetworkAgent.setExpectedKeepaliveSlot(2); TestKeepaliveCallback callback2 = new TestKeepaliveCallback(); PacketKeepalive ka2 = mCm.startNattKeepalive(myNet, 25, callback2, myIPv4, 6789, dstIPv4); callback2.expectStarted(); // Now stop the first one and create a third. This also gets slot 1. ka.stop(); callback.expectStopped(); mWiFiNetworkAgent.setExpectedKeepaliveSlot(1); TestKeepaliveCallback callback3 = new TestKeepaliveCallback(); PacketKeepalive ka3 = mCm.startNattKeepalive(myNet, 25, callback3, myIPv4, 9876, dstIPv4); callback3.expectStarted(); ka2.stop(); callback2.expectStopped(); ka3.stop(); callback3.expectStopped(); } }