diff options
author | Jeff Sharkey <jsharkey@android.com> | 2012-07-26 18:32:30 -0700 |
---|---|---|
committer | Jeff Sharkey <jsharkey@android.com> | 2012-08-08 16:23:41 -0700 |
commit | fb878b66b9456f8fee2bcb1076263852d207949d (patch) | |
tree | 2f2d3f1de5c8de8e04c4a84d5c443471837adf58 | |
parent | 088f29f55eebc6862a4cb5dddeaefacf24f74d95 (diff) | |
download | frameworks_base-fb878b66b9456f8fee2bcb1076263852d207949d.zip frameworks_base-fb878b66b9456f8fee2bcb1076263852d207949d.tar.gz frameworks_base-fb878b66b9456f8fee2bcb1076263852d207949d.tar.bz2 |
Isolate NetworkStateTracker creation, test.
Change ConnectivityService to use a factory when creating
NetworkStateTrackers, which gives us a good place to inject mocks
for testing. Add initial tests to verify that network routes are
added and removed as networks changed.
Change-Id: I11cbc61a84c2ed4afa2670036295b1494eab26e1
3 files changed, 328 insertions, 71 deletions
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index ad63424..206be68 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -20,6 +20,13 @@ import static android.Manifest.permission.MANAGE_NETWORK_POLICY; import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE; +import static android.net.ConnectivityManager.TYPE_BLUETOOTH; +import static android.net.ConnectivityManager.TYPE_DUMMY; +import static android.net.ConnectivityManager.TYPE_ETHERNET; +import static android.net.ConnectivityManager.TYPE_MOBILE; +import static android.net.ConnectivityManager.TYPE_WIFI; +import static android.net.ConnectivityManager.TYPE_WIMAX; +import static android.net.ConnectivityManager.getNetworkTypeName; import static android.net.ConnectivityManager.isNetworkTypeValid; import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL; import static android.net.NetworkPolicyManager.RULE_REJECT_METERED; @@ -86,14 +93,13 @@ import com.android.server.connectivity.Vpn; import com.android.server.net.BaseNetworkObserver; import com.google.android.collect.Lists; import com.google.android.collect.Sets; + import dalvik.system.DexClassLoader; + import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; import java.lang.reflect.Constructor; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.lang.reflect.InvocationTargetException; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; @@ -317,6 +323,14 @@ public class ConnectivityService extends IConnectivityManager.Stub { public ConnectivityService(Context context, INetworkManagementService netd, INetworkStatsService statsService, INetworkPolicyManager policyManager) { + // Currently, omitting a NetworkFactory will create one internally + // TODO: create here when we have cleaner WiMAX support + this(context, netd, statsService, policyManager, null); + } + + public ConnectivityService(Context context, INetworkManagementService netd, + INetworkStatsService statsService, INetworkPolicyManager policyManager, + NetworkFactory netFactory) { if (DBG) log("ConnectivityService starting up"); HandlerThread handlerThread = new HandlerThread("ConnectivityServiceThread"); @@ -324,6 +338,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { mHandler = new InternalHandler(handlerThread.getLooper()); mTrackerHandler = new NetworkStateTrackerHandler(handlerThread.getLooper()); + if (netFactory == null) { + netFactory = new DefaultNetworkFactory(context, mTrackerHandler); + } + // setup our unique device name if (TextUtils.isEmpty(SystemProperties.get("net.hostname"))) { String id = Settings.Secure.getString(context.getContentResolver(), @@ -462,59 +480,27 @@ public class ConnectivityService extends IConnectivityManager.Stub { mTestMode = SystemProperties.get("cm.test.mode").equals("true") && SystemProperties.get("ro.build.type").equals("eng"); - /* - * Create the network state trackers for Wi-Fi and mobile - * data. Maybe this could be done with a factory class, - * but it's not clear that it's worth it, given that - * the number of different network types is not going - * to change very often. - */ - for (int netType : mPriorityList) { - switch (mNetConfigs[netType].radio) { - case ConnectivityManager.TYPE_WIFI: - mNetTrackers[netType] = new WifiStateTracker( - netType, mNetConfigs[netType].name); - mNetTrackers[netType].startMonitoring(context, mTrackerHandler); - break; - case ConnectivityManager.TYPE_MOBILE: - mNetTrackers[netType] = new MobileDataStateTracker(netType, - mNetConfigs[netType].name); - mNetTrackers[netType].startMonitoring(context, mTrackerHandler); - break; - case ConnectivityManager.TYPE_DUMMY: - mNetTrackers[netType] = new DummyDataStateTracker(netType, - mNetConfigs[netType].name); - mNetTrackers[netType].startMonitoring(context, mTrackerHandler); - break; - case ConnectivityManager.TYPE_BLUETOOTH: - mNetTrackers[netType] = BluetoothTetheringDataTracker.getInstance(); - mNetTrackers[netType].startMonitoring(context, mTrackerHandler); - break; - case ConnectivityManager.TYPE_WIMAX: - mNetTrackers[netType] = makeWimaxStateTracker(); - if (mNetTrackers[netType]!= null) { - mNetTrackers[netType].startMonitoring(context, mTrackerHandler); - } - break; - case ConnectivityManager.TYPE_ETHERNET: - mNetTrackers[netType] = EthernetDataTracker.getInstance(); - mNetTrackers[netType].startMonitoring(context, mTrackerHandler); - break; - default: - loge("Trying to create a DataStateTracker for an unknown radio type " + - mNetConfigs[netType].radio); + + // Create and start trackers for hard-coded networks + for (int targetNetworkType : mPriorityList) { + final NetworkConfig config = mNetConfigs[targetNetworkType]; + final NetworkStateTracker tracker; + try { + tracker = netFactory.createTracker(targetNetworkType, config); + mNetTrackers[targetNetworkType] = tracker; + } catch (IllegalArgumentException e) { + Slog.e(TAG, "Problem creating " + getNetworkTypeName(targetNetworkType) + + " tracker: " + e); continue; } - mCurrentLinkProperties[netType] = null; - if (mNetTrackers[netType] != null && mNetConfigs[netType].isDefault()) { - mNetTrackers[netType].reconnect(); + + tracker.startMonitoring(context, mTrackerHandler); + if (config.isDefault()) { + tracker.reconnect(); } } - IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); - INetworkManagementService nmService = INetworkManagementService.Stub.asInterface(b); - - mTethering = new Tethering(mContext, nmService, statsService, this, mHandler.getLooper()); + mTethering = new Tethering(mContext, mNetd, statsService, this, mHandler.getLooper()); mTetheringConfigValid = ((mTethering.getTetherableUsbRegexs().length != 0 || mTethering.getTetherableWifiRegexs().length != 0 || mTethering.getTetherableBluetoothRegexs().length != 0) && @@ -523,9 +509,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { mVpn = new Vpn(mContext, new VpnCallback()); try { - nmService.registerObserver(mTethering); - nmService.registerObserver(mVpn); - nmService.registerObserver(mDataActivityObserver); + mNetd.registerObserver(mTethering); + mNetd.registerObserver(mVpn); + mNetd.registerObserver(mDataActivityObserver); } catch (RemoteException e) { loge("Error registering observer :" + e); } @@ -540,7 +526,53 @@ public class ConnectivityService extends IConnectivityManager.Stub { loadGlobalProxy(); } - private NetworkStateTracker makeWimaxStateTracker() { + /** + * Factory that creates {@link NetworkStateTracker} instances using given + * {@link NetworkConfig}. + */ + public interface NetworkFactory { + public NetworkStateTracker createTracker(int targetNetworkType, NetworkConfig config); + } + + private static class DefaultNetworkFactory implements NetworkFactory { + private final Context mContext; + private final Handler mTrackerHandler; + + public DefaultNetworkFactory(Context context, Handler trackerHandler) { + mContext = context; + mTrackerHandler = trackerHandler; + } + + @Override + public NetworkStateTracker createTracker(int targetNetworkType, NetworkConfig config) { + switch (config.radio) { + case TYPE_WIFI: + return new WifiStateTracker(targetNetworkType, config.name); + case TYPE_MOBILE: + return new MobileDataStateTracker(targetNetworkType, config.name); + case TYPE_DUMMY: + return new DummyDataStateTracker(targetNetworkType, config.name); + case TYPE_BLUETOOTH: + return BluetoothTetheringDataTracker.getInstance(); + case TYPE_WIMAX: + return makeWimaxStateTracker(mContext, mTrackerHandler); + case TYPE_ETHERNET: + return EthernetDataTracker.getInstance(); + default: + throw new IllegalArgumentException( + "Trying to create a NetworkStateTracker for an unknown radio type: " + + config.radio); + } + } + } + + /** + * Loads external WiMAX library and registers as system service, returning a + * {@link NetworkStateTracker} for WiMAX. Caller is still responsible for + * invoking {@link NetworkStateTracker#startMonitoring(Context, Handler)}. + */ + private static NetworkStateTracker makeWimaxStateTracker( + Context context, Handler trackerHandler) { // Initialize Wimax DexClassLoader wimaxClassLoader; Class wimaxStateTrackerClass = null; @@ -554,25 +586,25 @@ public class ConnectivityService extends IConnectivityManager.Stub { NetworkStateTracker wimaxStateTracker = null; - boolean isWimaxEnabled = mContext.getResources().getBoolean( + boolean isWimaxEnabled = context.getResources().getBoolean( com.android.internal.R.bool.config_wimaxEnabled); if (isWimaxEnabled) { try { - wimaxJarLocation = mContext.getResources().getString( + wimaxJarLocation = context.getResources().getString( com.android.internal.R.string.config_wimaxServiceJarLocation); - wimaxLibLocation = mContext.getResources().getString( + wimaxLibLocation = context.getResources().getString( com.android.internal.R.string.config_wimaxNativeLibLocation); - wimaxManagerClassName = mContext.getResources().getString( + wimaxManagerClassName = context.getResources().getString( com.android.internal.R.string.config_wimaxManagerClassname); - wimaxServiceClassName = mContext.getResources().getString( + wimaxServiceClassName = context.getResources().getString( com.android.internal.R.string.config_wimaxServiceClassname); - wimaxStateTrackerClassName = mContext.getResources().getString( + wimaxStateTrackerClassName = context.getResources().getString( com.android.internal.R.string.config_wimaxStateTrackerClassname); log("wimaxJarLocation: " + wimaxJarLocation); wimaxClassLoader = new DexClassLoader(wimaxJarLocation, - new ContextWrapper(mContext).getCacheDir().getAbsolutePath(), + new ContextWrapper(context).getCacheDir().getAbsolutePath(), wimaxLibLocation, ClassLoader.getSystemClassLoader()); try { @@ -593,13 +625,13 @@ public class ConnectivityService extends IConnectivityManager.Stub { Constructor wmxStTrkrConst = wimaxStateTrackerClass.getConstructor (new Class[] {Context.class, Handler.class}); - wimaxStateTracker = (NetworkStateTracker)wmxStTrkrConst.newInstance(mContext, - mTrackerHandler); + wimaxStateTracker = (NetworkStateTracker) wmxStTrkrConst.newInstance( + context, trackerHandler); Constructor wmxSrvConst = wimaxServiceClass.getDeclaredConstructor (new Class[] {Context.class, wimaxStateTrackerClass}); wmxSrvConst.setAccessible(true); - IBinder svcInvoker = (IBinder)wmxSrvConst.newInstance(mContext, wimaxStateTracker); + IBinder svcInvoker = (IBinder)wmxSrvConst.newInstance(context, wimaxStateTracker); wmxSrvConst.setAccessible(false); ServiceManager.addService(WimaxManagerConstants.WIMAX_SERVICE, svcInvoker); @@ -1873,6 +1905,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { // snapshot isFailover, because sendConnectedBroadcast() resets it boolean isFailover = info.isFailover(); final NetworkStateTracker thisNet = mNetTrackers[type]; + final String thisIface = thisNet.getLinkProperties().getInterfaceName(); // if this is a default net and other default is running // kill the one not preferred @@ -1931,10 +1964,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { sendConnectedBroadcastDelayed(info, getConnectivityChangeDelay()); // notify battery stats service about this network - final String iface = thisNet.getLinkProperties().getInterfaceName(); - if (iface != null) { + if (thisIface != null) { try { - BatteryStatsService.getService().noteNetworkInterfaceType(iface, type); + BatteryStatsService.getService().noteNetworkInterfaceType(thisIface, type); } catch (RemoteException e) { // ignored; service lives in system_server } @@ -2924,11 +2956,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } - private void log(String s) { + private static void log(String s) { Slog.d(TAG, s); } - private void loge(String s) { + private static void loge(String s) { Slog.e(TAG, s); } diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk index 295f324..81a2c14 100644 --- a/services/tests/servicestests/Android.mk +++ b/services/tests/servicestests/Android.mk @@ -9,7 +9,8 @@ LOCAL_SRC_FILES := $(call all-java-files-under, src) LOCAL_STATIC_JAVA_LIBRARIES := \ easymocklib \ - guava + guava \ + littlemock LOCAL_JAVA_LIBRARIES := android.test.runner services diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java new file mode 100644 index 0000000..93ea6a2 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java @@ -0,0 +1,224 @@ +/* + * 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_IMMEDIATE; +import static android.net.ConnectivityManager.TYPE_MOBILE; +import static android.net.ConnectivityManager.TYPE_WIFI; +import static android.net.ConnectivityManager.getNetworkTypeName; +import static android.net.NetworkStateTracker.EVENT_STATE_CHANGED; +import static com.google.testing.littlemock.LittleMock.anyInt; +import static com.google.testing.littlemock.LittleMock.createCaptor; +import static com.google.testing.littlemock.LittleMock.doNothing; +import static com.google.testing.littlemock.LittleMock.doReturn; +import static com.google.testing.littlemock.LittleMock.doThrow; +import static com.google.testing.littlemock.LittleMock.eq; +import static com.google.testing.littlemock.LittleMock.isA; +import static com.google.testing.littlemock.LittleMock.mock; +import static com.google.testing.littlemock.LittleMock.reset; +import static com.google.testing.littlemock.LittleMock.verify; + +import android.content.Context; +import android.net.INetworkPolicyManager; +import android.net.INetworkStatsService; +import android.net.LinkProperties; +import android.net.NetworkConfig; +import android.net.NetworkInfo; +import android.net.NetworkInfo.DetailedState; +import android.net.NetworkStateTracker; +import android.net.RouteInfo; +import android.os.Handler; +import android.os.INetworkManagementService; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.LargeTest; +import android.util.Log; +import android.util.LogPrinter; + +import com.google.testing.littlemock.ArgumentCaptor; + +import java.net.InetAddress; +import java.util.concurrent.Future; + +/** + * Tests for {@link ConnectivityService}. + */ +@LargeTest +public class ConnectivityServiceTest extends AndroidTestCase { + private static final String TAG = "ConnectivityServiceTest"; + + private static final String MOBILE_IFACE = "rmnet3"; + private static final String WIFI_IFACE = "wlan6"; + + private static final RouteInfo MOBILE_ROUTE_V4 = RouteInfo.makeHostRoute(parse("10.0.0.33")); + private static final RouteInfo MOBILE_ROUTE_V6 = RouteInfo.makeHostRoute(parse("fd00::33")); + + private static final RouteInfo WIFI_ROUTE_V4 = RouteInfo.makeHostRoute( + parse("192.168.0.66"), parse("192.168.0.1")); + private static final RouteInfo WIFI_ROUTE_V6 = RouteInfo.makeHostRoute( + parse("fd00::66"), parse("fd00::")); + + private INetworkManagementService mNetManager; + private INetworkStatsService mStatsService; + private INetworkPolicyManager mPolicyService; + private ConnectivityService.NetworkFactory mNetFactory; + + private BroadcastInterceptingContext mServiceContext; + private ConnectivityService mService; + + private MockNetwork mMobile; + private MockNetwork mWifi; + + private Handler mTrackerHandler; + + private static class MockNetwork { + public NetworkStateTracker tracker; + public NetworkInfo info; + public LinkProperties link; + + public MockNetwork(int type) { + tracker = mock(NetworkStateTracker.class); + info = new NetworkInfo(type, -1, getNetworkTypeName(type), null); + link = new LinkProperties(); + } + + public void doReturnDefaults() { + // TODO: eventually CS should make defensive copies + doReturn(new NetworkInfo(info)).when(tracker).getNetworkInfo(); + doReturn(new LinkProperties(link)).when(tracker).getLinkProperties(); + + // fallback to default TCP buffers + doReturn("").when(tracker).getTcpBufferSizesPropName(); + } + } + + @Override + public void setUp() throws Exception { + super.setUp(); + + mServiceContext = new BroadcastInterceptingContext(getContext()); + + mNetManager = mock(INetworkManagementService.class); + mStatsService = mock(INetworkStatsService.class); + mPolicyService = mock(INetworkPolicyManager.class); + mNetFactory = mock(ConnectivityService.NetworkFactory.class); + + mMobile = new MockNetwork(TYPE_MOBILE); + mWifi = new MockNetwork(TYPE_WIFI); + + // omit most network trackers + doThrow(new IllegalArgumentException("Not supported in test environment")) + .when(mNetFactory).createTracker(anyInt(), isA(NetworkConfig.class)); + + doReturn(mMobile.tracker) + .when(mNetFactory).createTracker(eq(TYPE_MOBILE), isA(NetworkConfig.class)); + doReturn(mWifi.tracker) + .when(mNetFactory).createTracker(eq(TYPE_WIFI), isA(NetworkConfig.class)); + + final ArgumentCaptor<Handler> trackerHandler = createCaptor(); + doNothing().when(mMobile.tracker) + .startMonitoring(isA(Context.class), trackerHandler.capture()); + + mService = new ConnectivityService( + mServiceContext, mNetManager, mStatsService, mPolicyService, mNetFactory); + mService.systemReady(); + + mTrackerHandler = trackerHandler.getValue(); + mTrackerHandler.getLooper().setMessageLogging(new LogPrinter(Log.INFO, TAG)); + } + + @Override + public void tearDown() throws Exception { + super.tearDown(); + } + + public void testMobileConnectedAddedRoutes() throws Exception { + Future<?> nextConnBroadcast; + + // bring up mobile network + mMobile.info.setDetailedState(DetailedState.CONNECTED, null, null); + mMobile.link.setInterfaceName(MOBILE_IFACE); + mMobile.link.addRoute(MOBILE_ROUTE_V4); + mMobile.link.addRoute(MOBILE_ROUTE_V6); + mMobile.doReturnDefaults(); + + nextConnBroadcast = mServiceContext.nextBroadcastIntent(CONNECTIVITY_ACTION_IMMEDIATE); + mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mMobile.info).sendToTarget(); + nextConnBroadcast.get(); + + // verify that both routes were added and DNS was flushed + verify(mNetManager).addRoute(eq(MOBILE_IFACE), eq(MOBILE_ROUTE_V4)); + verify(mNetManager).addRoute(eq(MOBILE_IFACE), eq(MOBILE_ROUTE_V6)); + verify(mNetManager).flushInterfaceDnsCache(MOBILE_IFACE); + + } + + public void testMobileWifiHandoff() throws Exception { + Future<?> nextConnBroadcast; + + // bring up mobile network + mMobile.info.setDetailedState(DetailedState.CONNECTED, null, null); + mMobile.link.setInterfaceName(MOBILE_IFACE); + mMobile.link.addRoute(MOBILE_ROUTE_V4); + mMobile.link.addRoute(MOBILE_ROUTE_V6); + mMobile.doReturnDefaults(); + + nextConnBroadcast = mServiceContext.nextBroadcastIntent(CONNECTIVITY_ACTION_IMMEDIATE); + mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mMobile.info).sendToTarget(); + nextConnBroadcast.get(); + + reset(mNetManager); + + // now bring up wifi network + mWifi.info.setDetailedState(DetailedState.CONNECTED, null, null); + mWifi.link.setInterfaceName(WIFI_IFACE); + mWifi.link.addRoute(WIFI_ROUTE_V4); + mWifi.link.addRoute(WIFI_ROUTE_V6); + mWifi.doReturnDefaults(); + + // expect that mobile will be torn down + doReturn(true).when(mMobile.tracker).teardown(); + + nextConnBroadcast = mServiceContext.nextBroadcastIntent(CONNECTIVITY_ACTION_IMMEDIATE); + mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mWifi.info).sendToTarget(); + nextConnBroadcast.get(); + + // verify that wifi routes added, and teardown requested + verify(mNetManager).addRoute(eq(WIFI_IFACE), eq(WIFI_ROUTE_V4)); + verify(mNetManager).addRoute(eq(WIFI_IFACE), eq(WIFI_ROUTE_V6)); + verify(mNetManager).flushInterfaceDnsCache(WIFI_IFACE); + verify(mMobile.tracker).teardown(); + + reset(mNetManager, mMobile.tracker); + + // tear down mobile network, as requested + mMobile.info.setDetailedState(DetailedState.DISCONNECTED, null, null); + mMobile.link.clear(); + mMobile.doReturnDefaults(); + + nextConnBroadcast = mServiceContext.nextBroadcastIntent(CONNECTIVITY_ACTION_IMMEDIATE); + mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mMobile.info).sendToTarget(); + nextConnBroadcast.get(); + + verify(mNetManager).removeRoute(eq(MOBILE_IFACE), eq(MOBILE_ROUTE_V4)); + verify(mNetManager).removeRoute(eq(MOBILE_IFACE), eq(MOBILE_ROUTE_V6)); + + } + + private static InetAddress parse(String addr) { + return InetAddress.parseNumericAddress(addr); + } +} |