Merge "Add CTS for isUidNetworkingBlocked & isUidRestrictedOnMeteredNetworks"
diff --git a/Tethering/common/TetheringLib/Android.bp b/Tethering/common/TetheringLib/Android.bp
index fce4360..f652772 100644
--- a/Tethering/common/TetheringLib/Android.bp
+++ b/Tethering/common/TetheringLib/Android.bp
@@ -22,6 +22,7 @@
defaults: ["framework-module-defaults"],
impl_library_visibility: [
"//packages/modules/Connectivity/Tethering:__subpackages__",
+ "//packages/modules/Connectivity/tests:__subpackages__",
],
srcs: [":framework-tethering-srcs"],
diff --git a/Tethering/res/values/config.xml b/Tethering/res/values/config.xml
index 4391006..0412a49 100644
--- a/Tethering/res/values/config.xml
+++ b/Tethering/res/values/config.xml
@@ -193,4 +193,8 @@
state. -->
<!-- Config for showing upstream roaming notification. -->
<bool name="config_upstream_roaming_notification">false</bool>
+
+ <!-- Which USB function should be enabled when TETHERING_USB is requested. 0: RNDIS, 1: NCM.
+ -->
+ <integer translatable="false" name="config_tether_usb_functions">0</integer>
</resources>
diff --git a/Tethering/res/values/overlayable.xml b/Tethering/res/values/overlayable.xml
index 0ee7a99..91fbd7d 100644
--- a/Tethering/res/values/overlayable.xml
+++ b/Tethering/res/values/overlayable.xml
@@ -24,6 +24,7 @@
<item type="array" name="config_tether_wifi_p2p_regexs"/>
<item type="array" name="config_tether_bluetooth_regexs"/>
<item type="array" name="config_tether_dhcp_range"/>
+ <item type="integer" name="config_tether_usb_functions"/>
<!-- Use the BPF offload for tethering when the kernel has support. True by default.
If the device doesn't want to support tether BPF offload, this should be false.
Note that this setting could be overridden by device config.
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index e4fbd71..b52ec86 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -64,6 +64,7 @@
import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+import static com.android.networkstack.tethering.TetheringConfiguration.TETHER_FORCE_USB_FUNCTIONS;
import static com.android.networkstack.tethering.TetheringNotificationUpdater.DOWNSTREAM_NONE;
import static com.android.networkstack.tethering.UpstreamNetworkMonitor.isCellular;
@@ -77,6 +78,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.database.ContentObserver;
import android.hardware.usb.UsbManager;
import android.net.ConnectivityManager;
import android.net.EthernetManager;
@@ -248,6 +250,7 @@
private InterfaceSet mCurrentUpstreamIfaceSet;
private boolean mRndisEnabled; // track the RNDIS function enabled state
+ private boolean mNcmEnabled; // track the NCM function enabled state
// True iff. WiFi tethering should be started when soft AP is ready.
private boolean mWifiTetherRequested;
private Network mTetherUpstream;
@@ -259,6 +262,7 @@
private EthernetManager.TetheredInterfaceRequest mEthernetIfaceRequest;
private String mConfiguredEthernetIface;
private EthernetCallback mEthernetCallback;
+ private SettingsObserver mSettingsObserver;
public Tethering(TetheringDependencies deps) {
mLog.mark("Tethering.constructed");
@@ -310,6 +314,10 @@
mEntitlementMgr.reevaluateSimCardProvisioning(mConfig);
});
+ mSettingsObserver = new SettingsObserver(mHandler);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(TETHER_FORCE_USB_FUNCTIONS), false, mSettingsObserver);
+
mStateReceiver = new StateReceiver();
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
@@ -359,6 +367,28 @@
startStateMachineUpdaters();
}
+ private class SettingsObserver extends ContentObserver {
+ SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ mLog.i("OBSERVED Settings change");
+ final boolean isUsingNcm = mConfig.isUsingNcm();
+ updateConfiguration();
+ if (isUsingNcm != mConfig.isUsingNcm()) {
+ stopTetheringInternal(TETHERING_USB);
+ stopTetheringInternal(TETHERING_NCM);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ ContentObserver getSettingsObserverForTest() {
+ return mSettingsObserver;
+ }
+
/**
* Start to register callbacks.
* Call this function when tethering is ready to handle callback events.
@@ -490,24 +520,35 @@
}
}
+ // This method needs to exist because TETHERING_BLUETOOTH and TETHERING_WIGIG can't use
+ // enableIpServing.
+ private void startOrStopIpServer(final String iface, boolean enabled) {
+ // TODO: do not listen to USB interface state changes. USB tethering is driven only by
+ // USB_ACTION broadcasts.
+
+ if (enabled) {
+ ensureIpServerStarted(iface);
+ } else {
+ ensureIpServerStopped(iface);
+ }
+ }
+
void interfaceStatusChanged(String iface, boolean up) {
// Never called directly: only called from interfaceLinkStateChanged.
// See NetlinkHandler.cpp: notifyInterfaceChanged.
if (VDBG) Log.d(TAG, "interfaceStatusChanged " + iface + ", " + up);
- if (up) {
- maybeTrackNewInterface(iface);
- } else {
- if (ifaceNameToType(iface) == TETHERING_BLUETOOTH
- || ifaceNameToType(iface) == TETHERING_WIGIG) {
- stopTrackingInterface(iface);
- } else {
- // Ignore usb0 down after enabling RNDIS.
- // We will handle disconnect in interfaceRemoved.
- // Similarly, ignore interface down for WiFi. We monitor WiFi AP status
- // through the WifiManager.WIFI_AP_STATE_CHANGED_ACTION intent.
- if (VDBG) Log.d(TAG, "ignore interface down for " + iface);
- }
+
+ final int type = ifaceNameToType(iface);
+ if (!up && type != TETHERING_BLUETOOTH && type != TETHERING_WIGIG) {
+ // Ignore usb interface down after enabling RNDIS.
+ // We will handle disconnect in interfaceRemoved.
+ // Similarly, ignore interface down for WiFi. We monitor WiFi AP status
+ // through the WifiManager.WIFI_AP_STATE_CHANGED_ACTION intent.
+ if (VDBG) Log.d(TAG, "ignore interface down for " + iface);
+ return;
}
+
+ startOrStopIpServer(iface, up);
}
void interfaceLinkStateChanged(String iface, boolean up) {
@@ -535,12 +576,12 @@
void interfaceAdded(String iface) {
if (VDBG) Log.d(TAG, "interfaceAdded " + iface);
- maybeTrackNewInterface(iface);
+ startOrStopIpServer(iface, true /* enabled */);
}
void interfaceRemoved(String iface) {
if (VDBG) Log.d(TAG, "interfaceRemoved " + iface);
- stopTrackingInterface(iface);
+ startOrStopIpServer(iface, false /* enabled */);
}
void startTethering(final TetheringRequestParcel request, final IIntResultListener listener) {
@@ -568,12 +609,15 @@
void stopTethering(int type) {
mHandler.post(() -> {
- mActiveTetheringRequests.remove(type);
-
- enableTetheringInternal(type, false /* disabled */, null);
- mEntitlementMgr.stopProvisioningIfNeeded(type);
+ stopTetheringInternal(type);
});
}
+ void stopTetheringInternal(int type) {
+ mActiveTetheringRequests.remove(type);
+
+ enableTetheringInternal(type, false /* disabled */, null);
+ mEntitlementMgr.stopProvisioningIfNeeded(type);
+ }
/**
* Enables or disables tethering for the given type. If provisioning is required, it will
@@ -701,7 +745,7 @@
private void stopEthernetTethering() {
if (mConfiguredEthernetIface != null) {
- stopTrackingInterface(mConfiguredEthernetIface);
+ ensureIpServerStopped(mConfiguredEthernetIface);
mConfiguredEthernetIface = null;
}
if (mEthernetCallback != null) {
@@ -718,8 +762,7 @@
// Ethernet callback arrived after Ethernet tethering stopped. Ignore.
return;
}
- maybeTrackNewInterface(iface, TETHERING_ETHERNET);
- changeInterfaceState(iface, getRequestedState(TETHERING_ETHERNET));
+ enableIpServing(TETHERING_ETHERNET, iface, getRequestedState(TETHERING_ETHERNET));
mConfiguredEthernetIface = iface;
}
@@ -851,6 +894,13 @@
: IpServer.STATE_TETHERED;
}
+ private int getRequestedUsbType(boolean forNcmFunction) {
+ // TETHERING_NCM is only used if the device does not use NCM for regular USB tethering.
+ if (forNcmFunction && !mConfig.isUsingNcm()) return TETHERING_NCM;
+
+ return TETHERING_USB;
+ }
+
// TODO: Figure out how to update for local hotspot mode interfaces.
private void sendTetherStateChangedBroadcast() {
if (!isTetheringSupported()) return;
@@ -877,12 +927,14 @@
} else if (tetherState.lastState == IpServer.STATE_LOCAL_ONLY) {
localOnly.add(tetheringIface);
} else if (tetherState.lastState == IpServer.STATE_TETHERED) {
- if (cfg.isUsb(iface)) {
- downstreamTypesMask |= (1 << TETHERING_USB);
- } else if (cfg.isWifi(iface)) {
- downstreamTypesMask |= (1 << TETHERING_WIFI);
- } else if (cfg.isBluetooth(iface)) {
- downstreamTypesMask |= (1 << TETHERING_BLUETOOTH);
+ switch (type) {
+ case TETHERING_USB:
+ case TETHERING_WIFI:
+ case TETHERING_BLUETOOTH:
+ downstreamTypesMask |= (1 << type);
+ break;
+ default:
+ // Do nothing.
}
tethered.add(tetheringIface);
}
@@ -987,8 +1039,8 @@
final boolean rndisEnabled = intent.getBooleanExtra(USB_FUNCTION_RNDIS, false);
final boolean ncmEnabled = intent.getBooleanExtra(USB_FUNCTION_NCM, false);
- mLog.log(String.format("USB bcast connected:%s configured:%s rndis:%s",
- usbConnected, usbConfigured, rndisEnabled));
+ mLog.log(String.format("USB bcast connected:%s configured:%s rndis:%s ncm:%s",
+ usbConnected, usbConfigured, rndisEnabled, ncmEnabled));
// There are three types of ACTION_USB_STATE:
//
@@ -1005,19 +1057,18 @@
// functions are ready to use.
//
// For more explanation, see b/62552150 .
- if (!usbConnected && mRndisEnabled) {
+ if (!usbConnected && (mRndisEnabled || mNcmEnabled)) {
// Turn off tethering if it was enabled and there is a disconnect.
- tetherMatchingInterfaces(IpServer.STATE_AVAILABLE, TETHERING_USB);
+ disableUsbIpServing(TETHERING_USB);
mEntitlementMgr.stopProvisioningIfNeeded(TETHERING_USB);
} else if (usbConfigured && rndisEnabled) {
// Tether if rndis is enabled and usb is configured.
- final int state = getRequestedState(TETHERING_USB);
- tetherMatchingInterfaces(state, TETHERING_USB);
- } else if (usbConnected && ncmEnabled) {
- final int state = getRequestedState(TETHERING_NCM);
- tetherMatchingInterfaces(state, TETHERING_NCM);
+ enableUsbIpServing(false /* isNcm */);
+ } else if (usbConfigured && ncmEnabled) {
+ enableUsbIpServing(true /* isNcm */);
}
mRndisEnabled = usbConfigured && rndisEnabled;
+ mNcmEnabled = usbConfigured && ncmEnabled;
}
private void handleWifiApAction(Intent intent) {
@@ -1164,6 +1215,11 @@
}
}
+ private void enableIpServing(int tetheringType, String ifname, int ipServingMode) {
+ ensureIpServerStarted(ifname, tetheringType);
+ changeInterfaceState(ifname, ipServingMode);
+ }
+
private void disableWifiIpServingCommon(int tetheringType, String ifname, int apState) {
mLog.log("Canceling WiFi tethering request -"
+ " type=" + tetheringType
@@ -1224,7 +1280,7 @@
}
if (!TextUtils.isEmpty(ifname)) {
- maybeTrackNewInterface(ifname);
+ ensureIpServerStarted(ifname);
changeInterfaceState(ifname, ipServingMode);
} else {
mLog.e(String.format(
@@ -1239,18 +1295,17 @@
// - handles both enabling and disabling serving states
// - only tethers the first matching interface in listInterfaces()
// order of a given type
- private void tetherMatchingInterfaces(int requestedState, int interfaceType) {
- if (VDBG) {
- Log.d(TAG, "tetherMatchingInterfaces(" + requestedState + ", " + interfaceType + ")");
- }
-
+ private void enableUsbIpServing(boolean isNcm) {
+ final int interfaceType = getRequestedUsbType(isNcm);
+ final int requestedState = getRequestedState(interfaceType);
String[] ifaces = null;
try {
ifaces = mNetd.interfaceGetList();
} catch (RemoteException | ServiceSpecificException e) {
- Log.e(TAG, "Error listing Interfaces", e);
+ mLog.e("Cannot enableUsbIpServing due to error listing Interfaces" + e);
return;
}
+
String chosenIface = null;
if (ifaces != null) {
for (String iface : ifaces) {
@@ -1260,6 +1315,7 @@
}
}
}
+
if (chosenIface == null) {
Log.e(TAG, "could not find iface of type " + interfaceType);
return;
@@ -1268,6 +1324,33 @@
changeInterfaceState(chosenIface, requestedState);
}
+ private void disableUsbIpServing(int interfaceType) {
+ String[] ifaces = null;
+ try {
+ ifaces = mNetd.interfaceGetList();
+ } catch (RemoteException | ServiceSpecificException e) {
+ mLog.e("Cannot disableUsbIpServing due to error listing Interfaces" + e);
+ return;
+ }
+
+ String chosenIface = null;
+ if (ifaces != null) {
+ for (String iface : ifaces) {
+ if (ifaceNameToType(iface) == interfaceType) {
+ chosenIface = iface;
+ break;
+ }
+ }
+ }
+
+ if (chosenIface == null) {
+ Log.e(TAG, "could not find iface of type " + interfaceType);
+ return;
+ }
+
+ changeInterfaceState(chosenIface, IpServer.STATE_AVAILABLE);
+ }
+
private void changeInterfaceState(String ifname, int requestedState) {
final int result;
switch (requestedState) {
@@ -1320,13 +1403,21 @@
mLog.e("setUsbTethering: failed to get UsbManager!");
return TETHER_ERROR_SERVICE_UNAVAIL;
}
- usbManager.setCurrentFunctions(enable ? UsbManager.FUNCTION_RNDIS
- : UsbManager.FUNCTION_NONE);
+
+ final long usbFunction = mConfig.isUsingNcm()
+ ? UsbManager.FUNCTION_NCM : UsbManager.FUNCTION_RNDIS;
+ usbManager.setCurrentFunctions(enable ? usbFunction : UsbManager.FUNCTION_NONE);
+
return TETHER_ERROR_NO_ERROR;
}
private int setNcmTethering(boolean enable) {
if (VDBG) Log.d(TAG, "setNcmTethering(" + enable + ")");
+
+ // If TETHERING_USB is forced to use ncm function, TETHERING_NCM would no longer be
+ // available.
+ if (mConfig.isUsingNcm()) return TETHER_ERROR_SERVICE_UNAVAIL;
+
UsbManager usbManager = (UsbManager) mContext.getSystemService(Context.USB_SERVICE);
usbManager.setCurrentFunctions(enable ? UsbManager.FUNCTION_NCM : UsbManager.FUNCTION_NONE);
return TETHER_ERROR_NO_ERROR;
@@ -2437,7 +2528,7 @@
mTetherMainSM.sendMessage(which, state, 0, newLp);
}
- private void maybeTrackNewInterface(final String iface) {
+ private void ensureIpServerStarted(final String iface) {
// If we don't care about this type of interface, ignore.
final int interfaceType = ifaceNameToType(iface);
if (interfaceType == TETHERING_INVALID) {
@@ -2457,17 +2548,17 @@
return;
}
- maybeTrackNewInterface(iface, interfaceType);
+ ensureIpServerStarted(iface, interfaceType);
}
- private void maybeTrackNewInterface(final String iface, int interfaceType) {
+ private void ensureIpServerStarted(final String iface, int interfaceType) {
// If we have already started a TISM for this interface, skip.
if (mTetherStates.containsKey(iface)) {
mLog.log("active iface (" + iface + ") reported as added, ignoring");
return;
}
- mLog.log("adding TetheringInterfaceStateMachine for: " + iface);
+ mLog.log("adding IpServer for: " + iface);
final TetherState tetherState = new TetherState(
new IpServer(iface, mLooper, interfaceType, mLog, mNetd, mBpfCoordinator,
makeControlCallback(), mConfig.enableLegacyDhcpServer,
@@ -2477,14 +2568,12 @@
tetherState.ipServer.start();
}
- private void stopTrackingInterface(final String iface) {
+ private void ensureIpServerStopped(final String iface) {
final TetherState tetherState = mTetherStates.get(iface);
- if (tetherState == null) {
- mLog.log("attempting to remove unknown iface (" + iface + "), ignoring");
- return;
- }
+ if (tetherState == null) return;
+
tetherState.ipServer.stop();
- mLog.log("removing TetheringInterfaceStateMachine for: " + iface);
+ mLog.log("removing IpServer for: " + iface);
mTetherStates.remove(iface);
}
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
index 2beeeb8..31fcea4 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
@@ -23,11 +23,13 @@
import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.net.TetheringConfigurationParcel;
import android.net.util.SharedLog;
import android.provider.DeviceConfig;
+import android.provider.Settings;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
@@ -75,6 +77,12 @@
private static final String[] DEFAULT_IPV4_DNS = {"8.8.4.4", "8.8.8.8"};
+ @VisibleForTesting
+ public static final int TETHER_USB_RNDIS_FUNCTION = 0;
+
+ @VisibleForTesting
+ public static final int TETHER_USB_NCM_FUNCTION = 1;
+
/**
* Override enabling BPF offload configuration for tethering.
*/
@@ -104,7 +112,7 @@
* via resource overlays, and later noticed issues. To that end, it overrides
* config_tether_upstream_automatic when set to true.
*
- * This flag is enabled if !=0 and less than the module APK version: see
+ * This flag is enabled if !=0 and less than the module APEX version: see
* {@link DeviceConfigUtils#isFeatureEnabled}. It is also ignored after R, as later devices
* should just set config_tether_upstream_automatic to true instead.
*/
@@ -112,6 +120,13 @@
"tether_force_upstream_automatic_version";
/**
+ * Settings key to foce choosing usb functions for usb tethering.
+ *
+ * TODO: Remove this hard code string and make Settings#TETHER_FORCE_USB_FUNCTIONS as API.
+ */
+ public static final String TETHER_FORCE_USB_FUNCTIONS =
+ "tether_force_usb_functions";
+ /**
* Default value that used to periodic polls tether offload stats from tethering offload HAL
* to make the data warnings work.
*/
@@ -143,12 +158,17 @@
private final boolean mEnableWifiP2pDedicatedIp;
private final boolean mEnableSelectAllPrefixRange;
+ private final int mUsbTetheringFunction;
+ protected final ContentResolver mContentResolver;
public TetheringConfiguration(Context ctx, SharedLog log, int id) {
final SharedLog configLog = log.forSubComponent("config");
activeDataSubId = id;
Resources res = getResources(ctx, activeDataSubId);
+ mContentResolver = ctx.getContentResolver();
+
+ mUsbTetheringFunction = getUsbTetheringFunction(res);
tetherableUsbRegexs = getResourceStringArray(res, R.array.config_tether_usb_regexs);
tetherableNcmRegexs = getResourceStringArray(res, R.array.config_tether_ncm_regexs);
@@ -200,6 +220,11 @@
configLog.log(toString());
}
+ /** Check whether using ncm for usb tethering */
+ public boolean isUsingNcm() {
+ return mUsbTetheringFunction == TETHER_USB_NCM_FUNCTION;
+ }
+
/** Check whether input interface belong to usb.*/
public boolean isUsb(String iface) {
return matchesDownstreamRegexs(iface, tetherableUsbRegexs);
@@ -285,6 +310,9 @@
pw.print("mEnableSelectAllPrefixRange: ");
pw.println(mEnableSelectAllPrefixRange);
+
+ pw.print("mUsbTetheringFunction: ");
+ pw.println(isUsingNcm() ? "NCM" : "RNDIS");
}
/** Returns the string representation of this object.*/
@@ -350,6 +378,26 @@
return mEnableSelectAllPrefixRange;
}
+ private int getUsbTetheringFunction(Resources res) {
+ final int valueFromRes = getResourceInteger(res, R.integer.config_tether_usb_functions,
+ TETHER_USB_RNDIS_FUNCTION /* defaultValue */);
+ return getSettingsIntValue(TETHER_FORCE_USB_FUNCTIONS, valueFromRes);
+ }
+
+ private int getSettingsIntValue(final String name, final int defaultValue) {
+ final String value = getSettingsValue(name);
+ try {
+ return value != null ? Integer.parseInt(value) : defaultValue;
+ } catch (NumberFormatException e) {
+ return defaultValue;
+ }
+ }
+
+ @VisibleForTesting
+ protected String getSettingsValue(final String name) {
+ return Settings.Global.getString(mContentResolver, name);
+ }
+
private static Collection<Integer> getUpstreamIfaceTypes(Resources res, boolean dunRequired) {
final int[] ifaceTypes = res.getIntArray(R.array.config_tether_upstream_types);
final ArrayList<Integer> upstreamIfaceTypes = new ArrayList<>(ifaceTypes.length);
diff --git a/Tethering/tests/integration/Android.bp b/Tethering/tests/integration/Android.bp
index 351b9f4..e807613 100644
--- a/Tethering/tests/integration/Android.bp
+++ b/Tethering/tests/integration/Android.bp
@@ -51,7 +51,8 @@
defaults: ["TetheringIntegrationTestsDefaults"],
visibility: [
"//packages/modules/Connectivity/tests/cts/tethering",
- "//packages/modules/Connectivity/Tethering/tests/mts",
+ "//packages/modules/Connectivity/tests:__subpackages__",
+ "//packages/modules/Connectivity/Tethering/tests:__subpackages__",
]
}
@@ -104,7 +105,6 @@
"libnetworkstackutilsjni",
"libtetherutilsjni",
],
- jarjar_rules: ":TetheringTestsJarJarRules",
compile_multilib: "both",
manifest: "AndroidManifest_coverage.xml",
}
diff --git a/Tethering/tests/unit/Android.bp b/Tethering/tests/unit/Android.bp
index b4b3977..192a540 100644
--- a/Tethering/tests/unit/Android.bp
+++ b/Tethering/tests/unit/Android.bp
@@ -75,6 +75,7 @@
"libstaticjvmtiagent",
"libtetherutilsjni",
],
+ jarjar_rules: ":TetheringTestsJarJarRules",
}
// Library containing the unit tests. This is used by the coverage test target to pull in the
@@ -85,7 +86,8 @@
defaults: ["TetheringTestsDefaults"],
target_sdk_version: "30",
visibility: [
- "//packages/modules/Connectivity/Tethering/tests/integration",
+ "//packages/modules/Connectivity/tests:__subpackages__",
+ "//packages/modules/Connectivity/Tethering/tests:__subpackages__",
]
}
@@ -96,7 +98,6 @@
"device-tests",
"mts",
],
- jarjar_rules: ":TetheringTestsJarJarRules",
defaults: ["TetheringTestsDefaults"],
compile_multilib: "both",
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
index 5ae4b43..442be1e 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
@@ -226,7 +226,7 @@
mEnMgr = new WrappedEntitlementManager(mMockContext, new Handler(mLooper.getLooper()), mLog,
mPermissionChangeCallback);
mEnMgr.setOnUiEntitlementFailedListener(mEntitlementFailedListener);
- mConfig = new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+ mConfig = new FakeTetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
mEnMgr.setTetheringConfigurationFetcher(() -> {
return mConfig;
});
@@ -251,7 +251,7 @@
when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(mCarrierConfig);
mCarrierConfig.putBoolean(CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL, true);
mCarrierConfig.putBoolean(CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
- mConfig = new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+ mConfig = new FakeTetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
}
@Test
@@ -265,7 +265,7 @@
setupForRequiredProvisioning();
when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE))
.thenReturn(null);
- mConfig = new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+ mConfig = new FakeTetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
// Couldn't get the CarrierConfigManager, but still had a declared provisioning app.
// Therefore provisioning still be required.
assertTrue(mEnMgr.isTetherProvisioningRequired(mConfig));
@@ -275,7 +275,7 @@
public void toleratesCarrierConfigMissing() {
setupForRequiredProvisioning();
when(mCarrierConfigManager.getConfig()).thenReturn(null);
- mConfig = new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+ mConfig = new FakeTetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
// We still have a provisioning app configured, so still require provisioning.
assertTrue(mEnMgr.isTetherProvisioningRequired(mConfig));
}
@@ -293,11 +293,11 @@
setupForRequiredProvisioning();
when(mResources.getStringArray(R.array.config_mobile_hotspot_provision_app))
.thenReturn(null);
- mConfig = new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+ mConfig = new FakeTetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
assertFalse(mEnMgr.isTetherProvisioningRequired(mConfig));
when(mResources.getStringArray(R.array.config_mobile_hotspot_provision_app))
.thenReturn(new String[] {"malformedApp"});
- mConfig = new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+ mConfig = new FakeTetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
assertFalse(mEnMgr.isTetherProvisioningRequired(mConfig));
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/FakeTetheringConfiguration.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/FakeTetheringConfiguration.java
new file mode 100644
index 0000000..ac5c59d
--- /dev/null
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/FakeTetheringConfiguration.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2021 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.networkstack.tethering;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.net.util.SharedLog;
+
+/** FakeTetheringConfiguration is used to override static method for testing. */
+public class FakeTetheringConfiguration extends TetheringConfiguration {
+ FakeTetheringConfiguration(Context ctx, SharedLog log, int id) {
+ super(ctx, log, id);
+ }
+
+ @Override
+ protected String getDeviceConfigProperty(final String name) {
+ return null;
+ }
+
+ @Override
+ protected boolean isFeatureEnabled(Context ctx, String featureVersionFlag) {
+ return false;
+ }
+
+ @Override
+ protected Resources getResourcesForSubIdWrapper(Context ctx, int subId) {
+ return ctx.getResources();
+ }
+
+ @Override
+ protected String getSettingsValue(final String name) {
+ if (mContentResolver == null) return null;
+
+ return super.getSettingsValue(name);
+ }
+}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/MockTetheringService.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/MockTetheringService.java
index 071a290..4865e03 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/MockTetheringService.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/MockTetheringService.java
@@ -25,12 +25,14 @@
import android.net.ITetheringConnector;
import android.os.Binder;
import android.os.IBinder;
+import android.util.ArrayMap;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class MockTetheringService extends TetheringService {
private final Tethering mTethering = mock(Tethering.class);
+ private final ArrayMap<String, Integer> mMockedPermissions = new ArrayMap<>();
@Override
public IBinder onBind(Intent intent) {
@@ -51,6 +53,15 @@
return context.checkCallingOrSelfPermission(WRITE_SETTINGS) == PERMISSION_GRANTED;
}
+ @Override
+ public int checkCallingOrSelfPermission(String permission) {
+ final Integer mocked = mMockedPermissions.getOrDefault(permission, null);
+ if (mocked != null) {
+ return mocked;
+ }
+ return super.checkCallingOrSelfPermission(permission);
+ }
+
public Tethering getTethering() {
return mTethering;
}
@@ -68,5 +79,18 @@
public MockTetheringService getService() {
return MockTetheringService.this;
}
+
+ /**
+ * Mock a permission
+ * @param permission Permission to mock
+ * @param granted One of PackageManager.PERMISSION_*, or null to reset to default behavior
+ */
+ public void setPermission(String permission, Integer granted) {
+ if (granted == null) {
+ mMockedPermissions.remove(permission);
+ } else {
+ mMockedPermissions.put(permission, granted);
+ }
+ }
}
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
index a6433a6..0f940d8 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
@@ -26,6 +26,9 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.networkstack.tethering.TetheringConfiguration.TETHER_FORCE_USB_FUNCTIONS;
+import static com.android.networkstack.tethering.TetheringConfiguration.TETHER_USB_NCM_FUNCTION;
+import static com.android.networkstack.tethering.TetheringConfiguration.TETHER_USB_RNDIS_FUNCTION;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -35,6 +38,7 @@
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.pm.ModuleInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -42,12 +46,15 @@
import android.net.util.SharedLog;
import android.os.Build;
import android.provider.DeviceConfig;
+import android.provider.Settings;
import android.telephony.TelephonyManager;
+import android.test.mock.MockContentResolver;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.test.BroadcastInterceptingContext;
+import com.android.internal.util.test.FakeSettingsProvider;
import com.android.net.module.util.DeviceConfigUtils;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
@@ -78,6 +85,7 @@
private static final String TEST_PACKAGE_NAME = "com.android.tethering.test";
private static final String APEX_NAME = "com.android.tethering";
private static final long TEST_PACKAGE_VERSION = 1234L;
+ @Mock private ApplicationInfo mApplicationInfo;
@Mock private Context mContext;
@Mock private TelephonyManager mTelephonyManager;
@Mock private Resources mResources;
@@ -88,6 +96,7 @@
private boolean mHasTelephonyManager;
private boolean mEnableLegacyDhcpServer;
private MockitoSession mMockingSession;
+ private MockContentResolver mContentResolver;
private class MockTetheringConfiguration extends TetheringConfiguration {
MockTetheringConfiguration(Context ctx, SharedLog log, int id) {
@@ -106,6 +115,11 @@
}
@Override
+ public ApplicationInfo getApplicationInfo() {
+ return mApplicationInfo;
+ }
+
+ @Override
public Resources getResources() {
return mResources;
}
@@ -153,7 +167,8 @@
new String[0]);
when(mResources.getInteger(R.integer.config_tether_offload_poll_interval)).thenReturn(
TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS);
- when(mResources.getStringArray(R.array.config_tether_usb_regexs)).thenReturn(new String[0]);
+ when(mResources.getStringArray(R.array.config_tether_usb_regexs))
+ .thenReturn(new String[]{ "test_usb\\d" });
when(mResources.getStringArray(R.array.config_tether_wifi_regexs))
.thenReturn(new String[]{ "test_wlan\\d" });
when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs)).thenReturn(
@@ -171,12 +186,20 @@
mHasTelephonyManager = true;
mMockContext = new MockContext(mContext);
mEnableLegacyDhcpServer = false;
+
+ mContentResolver = new MockContentResolver(mMockContext);
+ mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+ when(mContext.getContentResolver()).thenReturn(mContentResolver);
+ // Call {@link #clearSettingsProvider()} before and after using FakeSettingsProvider.
+ FakeSettingsProvider.clearSettingsProvider();
}
@After
public void tearDown() throws Exception {
mMockingSession.finishMocking();
DeviceConfigUtils.resetPackageVersionCacheForTest();
+ // Call {@link #clearSettingsProvider()} before and after using FakeSettingsProvider.
+ FakeSettingsProvider.clearSettingsProvider();
}
private TetheringConfiguration getTetheringConfiguration(int... legacyTetherUpstreamTypes) {
@@ -539,4 +562,42 @@
assertEquals(value, new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID)
.chooseUpstreamAutomatically);
}
+
+ @Test
+ public void testUsbTetheringFunctions() throws Exception {
+ // Test default value. If both resource and settings is not configured, usingNcm is false.
+ assertIsUsingNcm(false /* usingNcm */);
+
+ when(mResources.getInteger(R.integer.config_tether_usb_functions)).thenReturn(
+ TETHER_USB_NCM_FUNCTION);
+ assertIsUsingNcm(true /* usingNcm */);
+
+ when(mResources.getInteger(R.integer.config_tether_usb_functions)).thenReturn(
+ TETHER_USB_RNDIS_FUNCTION);
+ assertIsUsingNcm(false /* usingNcm */);
+
+ setTetherForceUsbFunctions(TETHER_USB_RNDIS_FUNCTION);
+ assertIsUsingNcm(false /* usingNcm */);
+
+ setTetherForceUsbFunctions(TETHER_USB_NCM_FUNCTION);
+ assertIsUsingNcm(true /* usingNcm */);
+
+ // Test throws NumberFormatException.
+ setTetherForceUsbFunctions("WrongNumberFormat");
+ assertIsUsingNcm(false /* usingNcm */);
+ }
+
+ private void assertIsUsingNcm(boolean expected) {
+ final TetheringConfiguration cfg =
+ new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+ assertEquals(expected, cfg.isUsingNcm());
+ }
+
+ private void setTetherForceUsbFunctions(final String value) {
+ Settings.Global.putString(mContentResolver, TETHER_FORCE_USB_FUNCTIONS, value);
+ }
+
+ private void setTetherForceUsbFunctions(final int value) {
+ setTetherForceUsbFunctions(Integer.toString(value));
+ }
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
index 941cd78..1b52f6e 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
@@ -19,6 +19,7 @@
import static android.Manifest.permission.ACCESS_NETWORK_STATE;
import static android.Manifest.permission.TETHER_PRIVILEGED;
import static android.Manifest.permission.WRITE_SETTINGS;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.net.TetheringManager.TETHERING_WIFI;
import static android.net.TetheringManager.TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION;
import static android.net.TetheringManager.TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION;
@@ -50,6 +51,7 @@
import androidx.test.rule.ServiceTestRule;
import androidx.test.runner.AndroidJUnit4;
+import com.android.net.module.util.CollectionUtils;
import com.android.networkstack.tethering.MockTetheringService.MockTetheringConnector;
import org.junit.After;
@@ -70,6 +72,7 @@
@Rule public ServiceTestRule mServiceTestRule;
private Tethering mTethering;
private Intent mMockServiceIntent;
+ private MockTetheringConnector mMockConnector;
private ITetheringConnector mTetheringConnector;
private UiAutomation mUiAutomation;
@@ -109,10 +112,9 @@
mMockServiceIntent = new Intent(
InstrumentationRegistry.getTargetContext(),
MockTetheringService.class);
- final MockTetheringConnector mockConnector =
- (MockTetheringConnector) mServiceTestRule.bindService(mMockServiceIntent);
- mTetheringConnector = mockConnector.getTetheringConnector();
- final MockTetheringService service = mockConnector.getService();
+ mMockConnector = (MockTetheringConnector) mServiceTestRule.bindService(mMockServiceIntent);
+ mTetheringConnector = mMockConnector.getTetheringConnector();
+ final MockTetheringService service = mMockConnector.getService();
mTethering = service.getTethering();
}
@@ -144,12 +146,18 @@
private void runTetheringCall(final TestTetheringCall test, String... permissions)
throws Exception {
+ // Allow the test to run even if ACCESS_NETWORK_STATE was granted at the APK level
+ if (!CollectionUtils.contains(permissions, ACCESS_NETWORK_STATE)) {
+ mMockConnector.setPermission(ACCESS_NETWORK_STATE, PERMISSION_DENIED);
+ }
+
if (permissions.length > 0) mUiAutomation.adoptShellPermissionIdentity(permissions);
try {
when(mTethering.isTetheringSupported()).thenReturn(true);
test.runTetheringCall(new TestTetheringResult());
} finally {
mUiAutomation.dropShellPermissionIdentity();
+ mMockConnector.setPermission(ACCESS_NETWORK_STATE, null);
}
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 5235eb7..383fce1 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -46,6 +46,7 @@
import static android.net.TetheringManager.TETHERING_WIFI_P2P;
import static android.net.TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR;
import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL;
import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_IFACE;
import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_FAILED;
import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STARTED;
@@ -65,6 +66,9 @@
import static com.android.networkstack.tethering.TestConnectivityManager.BROADCAST_FIRST;
import static com.android.networkstack.tethering.TestConnectivityManager.CALLBACKS_FIRST;
import static com.android.networkstack.tethering.Tethering.UserRestrictionActionListener;
+import static com.android.networkstack.tethering.TetheringConfiguration.TETHER_FORCE_USB_FUNCTIONS;
+import static com.android.networkstack.tethering.TetheringConfiguration.TETHER_USB_NCM_FUNCTION;
+import static com.android.networkstack.tethering.TetheringConfiguration.TETHER_USB_RNDIS_FUNCTION;
import static com.android.networkstack.tethering.TetheringNotificationUpdater.DOWNSTREAM_NONE;
import static com.android.networkstack.tethering.UpstreamNetworkMonitor.EVENT_ON_CAPABILITIES;
import static com.android.testutils.TestPermissionUtil.runAsShell;
@@ -106,6 +110,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
+import android.database.ContentObserver;
import android.hardware.usb.UsbManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.EthernetManager;
@@ -178,7 +183,6 @@
import com.android.testutils.MiscAsserts;
import org.junit.After;
-import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -207,7 +211,7 @@
private static final String TEST_MOBILE_IFNAME = "test_rmnet_data0";
private static final String TEST_DUN_IFNAME = "test_dun0";
private static final String TEST_XLAT_MOBILE_IFNAME = "v4-test_rmnet_data0";
- private static final String TEST_USB_IFNAME = "test_rndis0";
+ private static final String TEST_RNDIS_IFNAME = "test_rndis0";
private static final String TEST_WIFI_IFNAME = "test_wlan0";
private static final String TEST_WLAN_IFNAME = "test_wlan1";
private static final String TEST_P2P_IFNAME = "test_p2p-p2p0-0";
@@ -217,6 +221,11 @@
private static final String TETHERING_NAME = "Tethering";
private static final String[] PROVISIONING_APP_NAME = {"some", "app"};
private static final String PROVISIONING_NO_UI_APP_NAME = "no_ui_app";
+ private static final String TEST_RNDIS_REGEX = "test_rndis\\d";
+ private static final String TEST_NCM_REGEX = "test_ncm\\d";
+ private static final String TEST_WIFI_REGEX = "test_wlan\\d";
+ private static final String TEST_P2P_REGEX = "test_p2p-p2p\\d-.*";
+ private static final String TEST_BT_REGEX = "test_pan\\d";
private static final int CELLULAR_NETID = 100;
private static final int WIFI_NETID = 101;
@@ -339,7 +348,7 @@
@Override
public InterfaceParams getInterfaceParams(String ifName) {
assertTrue("Non-mocked interface " + ifName,
- ifName.equals(TEST_USB_IFNAME)
+ ifName.equals(TEST_RNDIS_IFNAME)
|| ifName.equals(TEST_WLAN_IFNAME)
|| ifName.equals(TEST_WIFI_IFNAME)
|| ifName.equals(TEST_MOBILE_IFNAME)
@@ -349,7 +358,7 @@
|| ifName.equals(TEST_ETH_IFNAME)
|| ifName.equals(TEST_BT_IFNAME));
final String[] ifaces = new String[] {
- TEST_USB_IFNAME, TEST_WLAN_IFNAME, TEST_WIFI_IFNAME, TEST_MOBILE_IFNAME,
+ TEST_RNDIS_IFNAME, TEST_WLAN_IFNAME, TEST_WIFI_IFNAME, TEST_MOBILE_IFNAME,
TEST_DUN_IFNAME, TEST_P2P_IFNAME, TEST_NCM_IFNAME, TEST_ETH_IFNAME};
return new InterfaceParams(ifName, ArrayUtils.indexOf(ifaces, ifName) + IFINDEX_OFFSET,
MacAddress.ALL_ZEROS_ADDRESS);
@@ -373,28 +382,6 @@
}
}
- // MyTetheringConfiguration is used to override static method for testing.
- private class MyTetheringConfiguration extends TetheringConfiguration {
- MyTetheringConfiguration(Context ctx, SharedLog log, int id) {
- super(ctx, log, id);
- }
-
- @Override
- protected String getDeviceConfigProperty(final String name) {
- return null;
- }
-
- @Override
- protected boolean isFeatureEnabled(Context ctx, String featureVersionFlag) {
- return false;
- }
-
- @Override
- protected Resources getResourcesForSubIdWrapper(Context ctx, int subId) {
- return mResources;
- }
- }
-
public class MockTetheringDependencies extends TetheringDependencies {
StateMachine mUpstreamNetworkMonitorSM;
ArrayList<IpServer> mIpv6CoordinatorNotifyList;
@@ -456,7 +443,7 @@
@Override
public TetheringConfiguration generateTetheringConfiguration(Context ctx, SharedLog log,
int subId) {
- mConfig = spy(new MyTetheringConfiguration(ctx, log, subId));
+ mConfig = spy(new FakeTetheringConfiguration(ctx, log, subId));
return mConfig;
}
@@ -585,18 +572,13 @@
new Network(DUN_NETID));
}
- // See FakeSettingsProvider#clearSettingsProvider() that this needs to be called before and
- // after use.
+ // See FakeSettingsProvider#clearSettingsProvider() that this also needs to be called before
+ // use.
@BeforeClass
public static void setupOnce() {
FakeSettingsProvider.clearSettingsProvider();
}
- @AfterClass
- public static void tearDownOnce() {
- FakeSettingsProvider.clearSettingsProvider();
- }
-
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -606,7 +588,7 @@
false);
when(mNetd.interfaceGetList())
.thenReturn(new String[] {
- TEST_MOBILE_IFNAME, TEST_WLAN_IFNAME, TEST_USB_IFNAME, TEST_P2P_IFNAME,
+ TEST_MOBILE_IFNAME, TEST_WLAN_IFNAME, TEST_RNDIS_IFNAME, TEST_P2P_IFNAME,
TEST_NCM_IFNAME, TEST_ETH_IFNAME, TEST_BT_IFNAME});
when(mResources.getString(R.string.config_wifi_tether_enable)).thenReturn("");
mInterfaceConfiguration = new InterfaceConfigurationParcel();
@@ -659,17 +641,19 @@
supported ? 1 : 0);
when(mUserManager.hasUserRestriction(
UserManager.DISALLOW_CONFIG_TETHERING)).thenReturn(!supported);
+ when(mResources.getInteger(R.integer.config_tether_usb_functions)).thenReturn(
+ TetheringConfiguration.TETHER_USB_RNDIS_FUNCTION);
// Setup tetherable configuration.
when(mResources.getStringArray(R.array.config_tether_usb_regexs))
- .thenReturn(new String[] { "test_rndis\\d" });
+ .thenReturn(new String[] { TEST_RNDIS_REGEX});
when(mResources.getStringArray(R.array.config_tether_wifi_regexs))
- .thenReturn(new String[] { "test_wlan\\d" });
+ .thenReturn(new String[] { TEST_WIFI_REGEX });
when(mResources.getStringArray(R.array.config_tether_wifi_p2p_regexs))
- .thenReturn(new String[] { "test_p2p-p2p\\d-.*" });
+ .thenReturn(new String[] { TEST_P2P_REGEX });
when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs))
- .thenReturn(new String[] { "test_pan\\d" });
+ .thenReturn(new String[] { TEST_BT_REGEX });
when(mResources.getStringArray(R.array.config_tether_ncm_regexs))
- .thenReturn(new String[] { "test_ncm\\d" });
+ .thenReturn(new String[] { TEST_NCM_REGEX });
when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(
new int[] { TYPE_WIFI, TYPE_MOBILE_DUN });
when(mResources.getBoolean(R.bool.config_tether_upstream_automatic)).thenReturn(true);
@@ -705,6 +689,7 @@
@After
public void tearDown() {
mServiceContext.unregisterReceiver(mBroadcastReceiver);
+ FakeSettingsProvider.clearSettingsProvider();
}
private void sendWifiApStateChanged(int state) {
@@ -750,16 +735,18 @@
mLooper.dispatchAll();
}
- private void sendUsbBroadcast(boolean connected, boolean configured, boolean function,
- int type) {
+ private boolean tetherUsbFunctionMatches(int function, int enabledType) {
+ return function == enabledType;
+ }
+
+ private void sendUsbBroadcast(boolean connected, boolean configured, int function) {
final Intent intent = new Intent(UsbManager.ACTION_USB_STATE);
intent.putExtra(USB_CONNECTED, connected);
intent.putExtra(USB_CONFIGURED, configured);
- if (type == TETHERING_USB) {
- intent.putExtra(USB_FUNCTION_RNDIS, function);
- } else {
- intent.putExtra(USB_FUNCTION_NCM, function);
- }
+ intent.putExtra(USB_FUNCTION_RNDIS,
+ tetherUsbFunctionMatches(TETHER_USB_RNDIS_FUNCTION, function));
+ intent.putExtra(USB_FUNCTION_NCM,
+ tetherUsbFunctionMatches(TETHER_USB_NCM_FUNCTION, function));
mServiceContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
mLooper.dispatchAll();
}
@@ -834,11 +821,18 @@
final TetheringRequestParcel request = createTetheringRequestParcel(TETHERING_USB);
mTethering.startTethering(request, null);
mLooper.dispatchAll();
- verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_RNDIS);
+
assertEquals(1, mTethering.getActiveTetheringRequests().size());
assertEquals(request, mTethering.getActiveTetheringRequests().get(TETHERING_USB));
- mTethering.interfaceStatusChanged(TEST_USB_IFNAME, true);
+ if (mTethering.getTetheringConfiguration().isUsingNcm()) {
+ verify(mUsbManager).setCurrentFunctions(UsbManager.FUNCTION_NCM);
+ mTethering.interfaceStatusChanged(TEST_NCM_IFNAME, true);
+ } else {
+ verify(mUsbManager).setCurrentFunctions(UsbManager.FUNCTION_RNDIS);
+ mTethering.interfaceStatusChanged(TEST_RNDIS_IFNAME, true);
+ }
+
}
@Test
@@ -851,7 +845,7 @@
verifyNoMoreInteractions(mNetd);
// Pretend we then receive USB configured broadcast.
- sendUsbBroadcast(true, true, true, TETHERING_USB);
+ sendUsbBroadcast(true, true, TETHER_USB_RNDIS_FUNCTION);
// Now we should see the start of tethering mechanics (in this case:
// tetherMatchingInterfaces() which starts by fetching all interfaces).
verify(mNetd, times(1)).interfaceGetList();
@@ -923,16 +917,13 @@
*/
private void sendIPv6TetherUpdates(UpstreamNetworkState upstreamState) {
// IPv6TetheringCoordinator must have been notified of downstream
- verify(mIPv6TetheringCoordinator, times(1)).addActiveDownstream(
- argThat(sm -> sm.linkProperties().getInterfaceName().equals(TEST_USB_IFNAME)),
- eq(IpServer.STATE_TETHERED));
-
for (IpServer ipSrv : mTetheringDependencies.mIpv6CoordinatorNotifyList) {
UpstreamNetworkState ipv6OnlyState = buildMobileUpstreamState(false, true, false);
ipSrv.sendMessage(IpServer.CMD_IPV6_TETHER_UPDATE, 0, 0,
upstreamState.linkProperties.isIpv6Provisioned()
? ipv6OnlyState.linkProperties
: null);
+ break;
}
mLooper.dispatchAll();
}
@@ -940,7 +931,18 @@
private void runUsbTethering(UpstreamNetworkState upstreamState) {
initTetheringUpstream(upstreamState);
prepareUsbTethering();
- sendUsbBroadcast(true, true, true, TETHERING_USB);
+ if (mTethering.getTetheringConfiguration().isUsingNcm()) {
+ sendUsbBroadcast(true, true, TETHER_USB_NCM_FUNCTION);
+ verify(mIPv6TetheringCoordinator).addActiveDownstream(
+ argThat(sm -> sm.linkProperties().getInterfaceName().equals(TEST_NCM_IFNAME)),
+ eq(IpServer.STATE_TETHERED));
+ } else {
+ sendUsbBroadcast(true, true, TETHER_USB_RNDIS_FUNCTION);
+ verify(mIPv6TetheringCoordinator).addActiveDownstream(
+ argThat(sm -> sm.linkProperties().getInterfaceName().equals(TEST_RNDIS_IFNAME)),
+ eq(IpServer.STATE_TETHERED));
+ }
+
}
private void assertSetIfaceToDadProxy(final int numOfCalls, final String ifaceName) {
@@ -956,8 +958,8 @@
UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState();
runUsbTethering(upstreamState);
- verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
- verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+ verify(mNetd, times(1)).tetherAddForward(TEST_RNDIS_IFNAME, TEST_MOBILE_IFNAME);
+ verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_RNDIS_IFNAME, TEST_MOBILE_IFNAME);
sendIPv6TetherUpdates(upstreamState);
assertSetIfaceToDadProxy(0 /* numOfCalls */, "" /* ifaceName */);
@@ -983,8 +985,8 @@
UpstreamNetworkState upstreamState = buildMobileIPv6UpstreamState();
runUsbTethering(upstreamState);
- verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
- verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+ verify(mNetd, times(1)).tetherAddForward(TEST_RNDIS_IFNAME, TEST_MOBILE_IFNAME);
+ verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_RNDIS_IFNAME, TEST_MOBILE_IFNAME);
sendIPv6TetherUpdates(upstreamState);
// TODO: add interfaceParams to compare in verify.
@@ -998,8 +1000,8 @@
UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState();
runUsbTethering(upstreamState);
- verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
- verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+ verify(mNetd, times(1)).tetherAddForward(TEST_RNDIS_IFNAME, TEST_MOBILE_IFNAME);
+ verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_RNDIS_IFNAME, TEST_MOBILE_IFNAME);
verify(mRouterAdvertisementDaemon, times(1)).start();
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
any(), any());
@@ -1015,12 +1017,13 @@
UpstreamNetworkState upstreamState = buildMobile464xlatUpstreamState();
runUsbTethering(upstreamState);
- verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME);
- verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+ verify(mNetd, times(1)).tetherAddForward(TEST_RNDIS_IFNAME, TEST_XLAT_MOBILE_IFNAME);
+ verify(mNetd, times(1)).tetherAddForward(TEST_RNDIS_IFNAME, TEST_MOBILE_IFNAME);
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
any(), any());
- verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME);
- verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+ verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_RNDIS_IFNAME,
+ TEST_XLAT_MOBILE_IFNAME);
+ verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_RNDIS_IFNAME, TEST_MOBILE_IFNAME);
sendIPv6TetherUpdates(upstreamState);
assertSetIfaceToDadProxy(1 /* numOfCalls */, TEST_MOBILE_IFNAME /* ifaceName */);
@@ -1030,14 +1033,20 @@
@Test
public void workingMobileUsbTethering_v6Then464xlat() throws Exception {
+ when(mResources.getInteger(R.integer.config_tether_usb_functions)).thenReturn(
+ TetheringConfiguration.TETHER_USB_NCM_FUNCTION);
+ when(mResources.getStringArray(R.array.config_tether_usb_regexs))
+ .thenReturn(new String[] {TEST_NCM_REGEX});
+ sendConfigurationChanged();
+
// Setup IPv6
UpstreamNetworkState upstreamState = buildMobileIPv6UpstreamState();
runUsbTethering(upstreamState);
- verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+ verify(mNetd, times(1)).tetherAddForward(TEST_NCM_IFNAME, TEST_MOBILE_IFNAME);
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
any(), any());
- verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+ verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_NCM_IFNAME, TEST_MOBILE_IFNAME);
// Then 464xlat comes up
upstreamState = buildMobile464xlatUpstreamState();
@@ -1052,11 +1061,12 @@
mLooper.dispatchAll();
// Forwarding is added for 464xlat
- verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME);
- verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME);
+ verify(mNetd, times(1)).tetherAddForward(TEST_NCM_IFNAME, TEST_XLAT_MOBILE_IFNAME);
+ verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_NCM_IFNAME,
+ TEST_XLAT_MOBILE_IFNAME);
// Forwarding was not re-added for v6 (still times(1))
- verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
- verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+ verify(mNetd, times(1)).tetherAddForward(TEST_NCM_IFNAME, TEST_MOBILE_IFNAME);
+ verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_NCM_IFNAME, TEST_MOBILE_IFNAME);
// DHCP not restarted on downstream (still times(1))
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
any(), any());
@@ -1093,7 +1103,7 @@
// Start USB tethering with no current upstream.
prepareUsbTethering();
- sendUsbBroadcast(true, true, true, TETHERING_USB);
+ sendUsbBroadcast(true, true, TETHER_USB_RNDIS_FUNCTION);
inOrder.verify(mUpstreamNetworkMonitor).startObserveAllNetworks();
inOrder.verify(mUpstreamNetworkMonitor).setTryCell(true);
@@ -1308,7 +1318,7 @@
// Start USB tethering with no current upstream.
prepareUsbTethering();
- sendUsbBroadcast(true, true, true, TETHERING_USB);
+ sendUsbBroadcast(true, true, TETHER_USB_RNDIS_FUNCTION);
inOrder.verify(mUpstreamNetworkMonitor).startObserveAllNetworks();
inOrder.verify(mUpstreamNetworkMonitor).setTryCell(true);
ArgumentCaptor<NetworkCallback> captor = ArgumentCaptor.forClass(NetworkCallback.class);
@@ -1342,7 +1352,7 @@
private void runNcmTethering() {
prepareNcmTethering();
- sendUsbBroadcast(true, true, true, TETHERING_NCM);
+ sendUsbBroadcast(true, true, TETHER_USB_NCM_FUNCTION);
}
@Test
@@ -1622,7 +1632,7 @@
// Start usb tethering and check that usb interface is tethered.
final UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState();
runUsbTethering(upstreamState);
- assertContains(Arrays.asList(mTethering.getTetheredIfaces()), TEST_USB_IFNAME);
+ assertContains(Arrays.asList(mTethering.getTetheredIfaces()), TEST_RNDIS_IFNAME);
assertTrue(mTethering.isTetheringActive());
assertEquals(0, mTethering.getActiveTetheringRequests().size());
@@ -1862,7 +1872,7 @@
callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_FAILED);
runStopUSBTethering();
callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED);
- reset(mUsbManager);
+ reset(mUsbManager, mIPv6TetheringCoordinator);
// 2. Offload fail if no OffloadControl.
initOffloadConfiguration(true /* offloadConfig */, false /* offloadControl */,
0 /* defaultDisabled */);
@@ -1870,7 +1880,7 @@
callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_FAILED);
runStopUSBTethering();
callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED);
- reset(mUsbManager);
+ reset(mUsbManager, mIPv6TetheringCoordinator);
// 3. Offload fail if disabled by settings.
initOffloadConfiguration(true /* offloadConfig */, true /* offloadControl */,
1 /* defaultDisabled */);
@@ -1883,8 +1893,10 @@
private void runStopUSBTethering() {
mTethering.stopTethering(TETHERING_USB);
mLooper.dispatchAll();
- mTethering.interfaceRemoved(TEST_USB_IFNAME);
+ mTethering.interfaceRemoved(TEST_RNDIS_IFNAME);
+ sendUsbBroadcast(true, true, -1 /* function */);
mLooper.dispatchAll();
+ verify(mUsbManager).setCurrentFunctions(UsbManager.FUNCTION_NONE);
}
private void initOffloadConfiguration(final boolean offloadConfig,
@@ -2059,29 +2071,29 @@
// Start Tethering.
final UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState();
runUsbTethering(upstreamState);
- assertContains(Arrays.asList(mTethering.getTetheredIfaces()), TEST_USB_IFNAME);
+ assertContains(Arrays.asList(mTethering.getTetheredIfaces()), TEST_RNDIS_IFNAME);
// Data saver is ON.
setDataSaverEnabled(true);
// Verify that tethering should be disabled.
verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_NONE);
- mTethering.interfaceRemoved(TEST_USB_IFNAME);
+ mTethering.interfaceRemoved(TEST_RNDIS_IFNAME);
mLooper.dispatchAll();
assertEquals(mTethering.getTetheredIfaces(), new String[0]);
- reset(mUsbManager);
+ reset(mUsbManager, mIPv6TetheringCoordinator);
runUsbTethering(upstreamState);
// Verify that user can start tethering again without turning OFF data saver.
- assertContains(Arrays.asList(mTethering.getTetheredIfaces()), TEST_USB_IFNAME);
+ assertContains(Arrays.asList(mTethering.getTetheredIfaces()), TEST_RNDIS_IFNAME);
// If data saver is keep ON with change event, tethering should not be OFF this time.
setDataSaverEnabled(true);
verify(mUsbManager, times(0)).setCurrentFunctions(UsbManager.FUNCTION_NONE);
- assertContains(Arrays.asList(mTethering.getTetheredIfaces()), TEST_USB_IFNAME);
+ assertContains(Arrays.asList(mTethering.getTetheredIfaces()), TEST_RNDIS_IFNAME);
// If data saver is turned OFF, it should not change tethering.
setDataSaverEnabled(false);
verify(mUsbManager, times(0)).setCurrentFunctions(UsbManager.FUNCTION_NONE);
- assertContains(Arrays.asList(mTethering.getTetheredIfaces()), TEST_USB_IFNAME);
+ assertContains(Arrays.asList(mTethering.getTetheredIfaces()), TEST_RNDIS_IFNAME);
}
private static <T> void assertContains(Collection<T> collection, T element) {
@@ -2141,8 +2153,8 @@
verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_RNDIS);
// Expect that when USB comes up, the DHCP server is configured with the requested address.
- mTethering.interfaceStatusChanged(TEST_USB_IFNAME, true);
- sendUsbBroadcast(true, true, true, TETHERING_USB);
+ mTethering.interfaceStatusChanged(TEST_RNDIS_IFNAME, true);
+ sendUsbBroadcast(true, true, TETHER_USB_RNDIS_FUNCTION);
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
any(), any());
verify(mNetd).interfaceSetCfg(argThat(cfg -> serverAddr.equals(cfg.ipv4Addr)));
@@ -2150,6 +2162,12 @@
@Test
public void testRequestStaticIp() throws Exception {
+ when(mResources.getInteger(R.integer.config_tether_usb_functions)).thenReturn(
+ TetheringConfiguration.TETHER_USB_NCM_FUNCTION);
+ when(mResources.getStringArray(R.array.config_tether_usb_regexs))
+ .thenReturn(new String[] {TEST_NCM_REGEX});
+ sendConfigurationChanged();
+
final LinkAddress serverLinkAddr = new LinkAddress("192.168.0.123/24");
final LinkAddress clientLinkAddr = new LinkAddress("192.168.0.42/24");
final String serverAddr = "192.168.0.123";
@@ -2159,9 +2177,9 @@
mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB,
serverLinkAddr, clientLinkAddr, false, CONNECTIVITY_SCOPE_GLOBAL), null);
mLooper.dispatchAll();
- verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_RNDIS);
- mTethering.interfaceStatusChanged(TEST_USB_IFNAME, true);
- sendUsbBroadcast(true, true, true, TETHERING_USB);
+ verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_NCM);
+ mTethering.interfaceStatusChanged(TEST_NCM_IFNAME, true);
+ sendUsbBroadcast(true, true, TETHER_USB_NCM_FUNCTION);
verify(mNetd).interfaceSetCfg(argThat(cfg -> serverAddr.equals(cfg.ipv4Addr)));
verify(mIpServerDependencies, times(1)).makeDhcpServer(any(), dhcpParamsCaptor.capture(),
any());
@@ -2328,7 +2346,7 @@
wifiNetwork, TEST_WIFI_IFNAME, TRANSPORT_WIFI);
// verify turn off usb tethering
verify(mUsbManager).setCurrentFunctions(UsbManager.FUNCTION_NONE);
- mTethering.interfaceRemoved(TEST_USB_IFNAME);
+ mTethering.interfaceRemoved(TEST_RNDIS_IFNAME);
mLooper.dispatchAll();
// verify restart usb tethering
verify(mUsbManager).setCurrentFunctions(UsbManager.FUNCTION_RNDIS);
@@ -2369,7 +2387,7 @@
verify(mUsbManager).setCurrentFunctions(UsbManager.FUNCTION_NONE);
// verify turn off ethernet tethering
verify(mockRequest).release();
- mTethering.interfaceRemoved(TEST_USB_IFNAME);
+ mTethering.interfaceRemoved(TEST_RNDIS_IFNAME);
ethCallback.onUnavailable();
mLooper.dispatchAll();
// verify restart usb tethering
@@ -2382,14 +2400,15 @@
reset(mUsbManager, mEm);
when(mNetd.interfaceGetList())
.thenReturn(new String[] {
- TEST_MOBILE_IFNAME, TEST_WLAN_IFNAME, TEST_USB_IFNAME, TEST_P2P_IFNAME,
+ TEST_MOBILE_IFNAME, TEST_WLAN_IFNAME, TEST_RNDIS_IFNAME, TEST_P2P_IFNAME,
TEST_NCM_IFNAME, TEST_ETH_IFNAME});
- mTethering.interfaceStatusChanged(TEST_USB_IFNAME, true);
- sendUsbBroadcast(true, true, true, TETHERING_USB);
- assertContains(Arrays.asList(mTethering.getTetherableIfacesForTest()), TEST_USB_IFNAME);
+ mTethering.interfaceStatusChanged(TEST_RNDIS_IFNAME, true);
+ sendUsbBroadcast(true, true, TETHER_USB_RNDIS_FUNCTION);
+ assertContains(Arrays.asList(mTethering.getTetherableIfacesForTest()), TEST_RNDIS_IFNAME);
assertContains(Arrays.asList(mTethering.getTetherableIfacesForTest()), TEST_ETH_IFNAME);
- assertEquals(TETHER_ERROR_IFACE_CFG_ERROR, mTethering.getLastErrorForTest(TEST_USB_IFNAME));
+ assertEquals(TETHER_ERROR_IFACE_CFG_ERROR, mTethering.getLastErrorForTest(
+ TEST_RNDIS_IFNAME));
assertEquals(TETHER_ERROR_IFACE_CFG_ERROR, mTethering.getLastErrorForTest(TEST_ETH_IFNAME));
}
@@ -2580,6 +2599,46 @@
reset(mBluetoothAdapter, mBluetoothPan);
}
+ @Test
+ public void testUsbTetheringWithNcmFunction() throws Exception {
+ when(mResources.getInteger(R.integer.config_tether_usb_functions)).thenReturn(
+ TetheringConfiguration.TETHER_USB_NCM_FUNCTION);
+ when(mResources.getStringArray(R.array.config_tether_usb_regexs))
+ .thenReturn(new String[] {TEST_NCM_REGEX});
+ sendConfigurationChanged();
+
+ // If TETHERING_USB is forced to use ncm function, TETHERING_NCM would no longer be
+ // available.
+ final ResultListener ncmResult = new ResultListener(TETHER_ERROR_SERVICE_UNAVAIL);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_NCM), ncmResult);
+ mLooper.dispatchAll();
+ ncmResult.assertHasResult();
+
+ final UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState();
+ runUsbTethering(upstreamState);
+
+ verify(mNetd).interfaceGetList();
+ verify(mNetd).tetherAddForward(TEST_NCM_IFNAME, TEST_MOBILE_IFNAME);
+ verify(mNetd).ipfwdAddInterfaceForward(TEST_NCM_IFNAME, TEST_MOBILE_IFNAME);
+
+ verify(mRouterAdvertisementDaemon).start();
+ verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS)).startWithCallbacks(
+ any(), any());
+ sendIPv6TetherUpdates(upstreamState);
+ assertSetIfaceToDadProxy(1 /* numOfCalls */, TEST_MOBILE_IFNAME /* ifaceName */);
+ verify(mRouterAdvertisementDaemon).buildNewRa(any(), notNull());
+ verify(mNetd).tetherApplyDnsInterfaces();
+
+ Settings.Global.putInt(mContentResolver, TETHER_FORCE_USB_FUNCTIONS,
+ TETHER_USB_RNDIS_FUNCTION);
+ final ContentObserver observer = mTethering.getSettingsObserverForTest();
+ observer.onChange(false /* selfChange */);
+ mLooper.dispatchAll();
+ // stop TETHERING_USB and TETHERING_NCM
+ verify(mUsbManager, times(2)).setCurrentFunctions(UsbManager.FUNCTION_NONE);
+ mTethering.interfaceRemoved(TEST_NCM_IFNAME);
+ sendUsbBroadcast(true, true, -1 /* function */);
+ }
// TODO: Test that a request for hotspot mode doesn't interfere with an
// already operating tethering mode interface.
}
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
new file mode 100644
index 0000000..ff90e78
--- /dev/null
+++ b/core/java/android/net/ConnectivityManager.java
@@ -0,0 +1,2416 @@
+/*
+ * Copyright (C) 2008 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 static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.net.NetworkUtils;
+import android.os.Binder;
+import android.os.Build.VERSION_CODES;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.INetworkActivityListener;
+import android.os.INetworkManagementService;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.provider.Settings;
+import android.telephony.TelephonyManager;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.telephony.ITelephony;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.util.Protocol;
+
+import java.net.InetAddress;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.HashMap;
+
+/**
+ * Class that answers queries about the state of network connectivity. It also
+ * notifies applications when network connectivity changes. Get an instance
+ * of this class by calling
+ * {@link android.content.Context#getSystemService(String) Context.getSystemService(Context.CONNECTIVITY_SERVICE)}.
+ * <p>
+ * The primary responsibilities of this class are to:
+ * <ol>
+ * <li>Monitor network connections (Wi-Fi, GPRS, UMTS, etc.)</li>
+ * <li>Send broadcast intents when network connectivity changes</li>
+ * <li>Attempt to "fail over" to another network when connectivity to a network
+ * is lost</li>
+ * <li>Provide an API that allows applications to query the coarse-grained or fine-grained
+ * state of the available networks</li>
+ * <li>Provide an API that allows applications to request and select networks for their data
+ * traffic</li>
+ * </ol>
+ */
+public class ConnectivityManager {
+ private static final String TAG = "ConnectivityManager";
+
+ /**
+ * A change in network connectivity has occurred. A default connection has either
+ * been established or lost. The NetworkInfo for the affected network is
+ * sent as an extra; it should be consulted to see what kind of
+ * connectivity event occurred.
+ * <p/>
+ * If this is a connection that was the result of failing over from a
+ * disconnected network, then the FAILOVER_CONNECTION boolean extra is
+ * set to true.
+ * <p/>
+ * For a loss of connectivity, if the connectivity manager is attempting
+ * to connect (or has already connected) to another network, the
+ * NetworkInfo for the new network is also passed as an extra. This lets
+ * any receivers of the broadcast know that they should not necessarily
+ * tell the user that no data traffic will be possible. Instead, the
+ * receiver should expect another broadcast soon, indicating either that
+ * the failover attempt succeeded (and so there is still overall data
+ * connectivity), or that the failover attempt failed, meaning that all
+ * connectivity has been lost.
+ * <p/>
+ * For a disconnect event, the boolean extra EXTRA_NO_CONNECTIVITY
+ * is set to {@code true} if there are no connected networks at all.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String CONNECTIVITY_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";
+
+ /**
+ * Identical to {@link #CONNECTIVITY_ACTION} broadcast, but sent without any
+ * applicable {@link Settings.Global#CONNECTIVITY_CHANGE_DELAY}.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String CONNECTIVITY_ACTION_IMMEDIATE =
+ "android.net.conn.CONNECTIVITY_CHANGE_IMMEDIATE";
+
+ /**
+ * The lookup key for a {@link NetworkInfo} object. Retrieve with
+ * {@link android.content.Intent#getParcelableExtra(String)}.
+ *
+ * @deprecated Since {@link NetworkInfo} can vary based on UID, applications
+ * should always obtain network information through
+ * {@link #getActiveNetworkInfo()} or
+ * {@link #getAllNetworkInfo()}.
+ * @see #EXTRA_NETWORK_TYPE
+ */
+ @Deprecated
+ public static final String EXTRA_NETWORK_INFO = "networkInfo";
+
+ /**
+ * Network type which triggered a {@link #CONNECTIVITY_ACTION} broadcast.
+ * Can be used with {@link #getNetworkInfo(int)} to get {@link NetworkInfo}
+ * state based on the calling application.
+ *
+ * @see android.content.Intent#getIntExtra(String, int)
+ */
+ public static final String EXTRA_NETWORK_TYPE = "networkType";
+
+ /**
+ * The lookup key for a boolean that indicates whether a connect event
+ * is for a network to which the connectivity manager was failing over
+ * following a disconnect on another network.
+ * Retrieve it with {@link android.content.Intent#getBooleanExtra(String,boolean)}.
+ */
+ public static final String EXTRA_IS_FAILOVER = "isFailover";
+ /**
+ * The lookup key for a {@link NetworkInfo} object. This is supplied when
+ * there is another network that it may be possible to connect to. Retrieve with
+ * {@link android.content.Intent#getParcelableExtra(String)}.
+ */
+ public static final String EXTRA_OTHER_NETWORK_INFO = "otherNetwork";
+ /**
+ * The lookup key for a boolean that indicates whether there is a
+ * complete lack of connectivity, i.e., no network is available.
+ * Retrieve it with {@link android.content.Intent#getBooleanExtra(String,boolean)}.
+ */
+ public static final String EXTRA_NO_CONNECTIVITY = "noConnectivity";
+ /**
+ * The lookup key for a string that indicates why an attempt to connect
+ * to a network failed. The string has no particular structure. It is
+ * intended to be used in notifications presented to users. Retrieve
+ * it with {@link android.content.Intent#getStringExtra(String)}.
+ */
+ public static final String EXTRA_REASON = "reason";
+ /**
+ * The lookup key for a string that provides optionally supplied
+ * extra information about the network state. The information
+ * may be passed up from the lower networking layers, and its
+ * meaning may be specific to a particular network type. Retrieve
+ * it with {@link android.content.Intent#getStringExtra(String)}.
+ */
+ public static final String EXTRA_EXTRA_INFO = "extraInfo";
+ /**
+ * The lookup key for an int that provides information about
+ * our connection to the internet at large. 0 indicates no connection,
+ * 100 indicates a great connection. Retrieve it with
+ * {@link android.content.Intent#getIntExtra(String, int)}.
+ * {@hide}
+ */
+ public static final String EXTRA_INET_CONDITION = "inetCondition";
+
+ /**
+ * Broadcast action to indicate the change of data activity status
+ * (idle or active) on a network in a recent period.
+ * The network becomes active when data transmission is started, or
+ * idle if there is no data transmission for a period of time.
+ * {@hide}
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_DATA_ACTIVITY_CHANGE = "android.net.conn.DATA_ACTIVITY_CHANGE";
+ /**
+ * The lookup key for an enum that indicates the network device type on which this data activity
+ * change happens.
+ * {@hide}
+ */
+ public static final String EXTRA_DEVICE_TYPE = "deviceType";
+ /**
+ * The lookup key for a boolean that indicates the device is active or not. {@code true} means
+ * it is actively sending or receiving data and {@code false} means it is idle.
+ * {@hide}
+ */
+ public static final String EXTRA_IS_ACTIVE = "isActive";
+ /**
+ * The lookup key for a long that contains the timestamp (nanos) of the radio state change.
+ * {@hide}
+ */
+ public static final String EXTRA_REALTIME_NS = "tsNanos";
+
+ /**
+ * Broadcast Action: The setting for background data usage has changed
+ * values. Use {@link #getBackgroundDataSetting()} to get the current value.
+ * <p>
+ * If an application uses the network in the background, it should listen
+ * for this broadcast and stop using the background data if the value is
+ * {@code false}.
+ * <p>
+ *
+ * @deprecated As of {@link VERSION_CODES#ICE_CREAM_SANDWICH}, availability
+ * of background data depends on several combined factors, and
+ * this broadcast is no longer sent. Instead, when background
+ * data is unavailable, {@link #getActiveNetworkInfo()} will now
+ * appear disconnected. During first boot after a platform
+ * upgrade, this broadcast will be sent once if
+ * {@link #getBackgroundDataSetting()} was {@code false} before
+ * the upgrade.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ @Deprecated
+ public static final String ACTION_BACKGROUND_DATA_SETTING_CHANGED =
+ "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED";
+
+ /**
+ * Broadcast Action: The network connection may not be good
+ * uses {@code ConnectivityManager.EXTRA_INET_CONDITION} and
+ * {@code ConnectivityManager.EXTRA_NETWORK_INFO} to specify
+ * the network and it's condition.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String INET_CONDITION_ACTION =
+ "android.net.conn.INET_CONDITION_ACTION";
+
+ /**
+ * Broadcast Action: A tetherable connection has come or gone.
+ * Uses {@code ConnectivityManager.EXTRA_AVAILABLE_TETHER},
+ * {@code ConnectivityManager.EXTRA_ACTIVE_TETHER} and
+ * {@code ConnectivityManager.EXTRA_ERRORED_TETHER} to indicate
+ * the current state of tethering. Each include a list of
+ * interface names in that state (may be empty).
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_TETHER_STATE_CHANGED =
+ "android.net.conn.TETHER_STATE_CHANGED";
+
+ /**
+ * @hide
+ * gives a String[] listing all the interfaces configured for
+ * tethering and currently available for tethering.
+ */
+ public static final String EXTRA_AVAILABLE_TETHER = "availableArray";
+
+ /**
+ * @hide
+ * gives a String[] listing all the interfaces currently tethered
+ * (ie, has dhcp support and packets potentially forwarded/NATed)
+ */
+ public static final String EXTRA_ACTIVE_TETHER = "activeArray";
+
+ /**
+ * @hide
+ * gives a String[] listing all the interfaces we tried to tether and
+ * failed. Use {@link #getLastTetherError} to find the error code
+ * for any interfaces listed here.
+ */
+ public static final String EXTRA_ERRORED_TETHER = "erroredArray";
+
+ /**
+ * Broadcast Action: The captive portal tracker has finished its test.
+ * Sent only while running Setup Wizard, in lieu of showing a user
+ * notification.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_CAPTIVE_PORTAL_TEST_COMPLETED =
+ "android.net.conn.CAPTIVE_PORTAL_TEST_COMPLETED";
+ /**
+ * The lookup key for a boolean that indicates whether a captive portal was detected.
+ * Retrieve it with {@link android.content.Intent#getBooleanExtra(String,boolean)}.
+ * @hide
+ */
+ public static final String EXTRA_IS_CAPTIVE_PORTAL = "captivePortal";
+
+ /**
+ * The absence of a connection type.
+ * @hide
+ */
+ public static final int TYPE_NONE = -1;
+
+ /**
+ * The Mobile data connection. When active, all data traffic
+ * will use this network type's interface by default
+ * (it has a default route)
+ */
+ public static final int TYPE_MOBILE = 0;
+ /**
+ * The WIFI data connection. When active, all data traffic
+ * will use this network type's interface by default
+ * (it has a default route).
+ */
+ public static final int TYPE_WIFI = 1;
+ /**
+ * An MMS-specific Mobile data connection. This network type may use the
+ * same network interface as {@link #TYPE_MOBILE} or it may use a different
+ * one. This is used by applications needing to talk to the carrier's
+ * Multimedia Messaging Service servers.
+ */
+ public static final int TYPE_MOBILE_MMS = 2;
+ /**
+ * A SUPL-specific Mobile data connection. This network type may use the
+ * same network interface as {@link #TYPE_MOBILE} or it may use a different
+ * one. This is used by applications needing to talk to the carrier's
+ * Secure User Plane Location servers for help locating the device.
+ */
+ public static final int TYPE_MOBILE_SUPL = 3;
+ /**
+ * A DUN-specific Mobile data connection. This network type may use the
+ * same network interface as {@link #TYPE_MOBILE} or it may use a different
+ * one. This is sometimes by the system when setting up an upstream connection
+ * for tethering so that the carrier is aware of DUN traffic.
+ */
+ public static final int TYPE_MOBILE_DUN = 4;
+ /**
+ * A High Priority Mobile data connection. This network type uses the
+ * same network interface as {@link #TYPE_MOBILE} but the routing setup
+ * is different. Only requesting processes will have access to the
+ * Mobile DNS servers and only IP's explicitly requested via {@link #requestRouteToHost}
+ * will route over this interface if no default route exists.
+ */
+ public static final int TYPE_MOBILE_HIPRI = 5;
+ /**
+ * The WiMAX data connection. When active, all data traffic
+ * will use this network type's interface by default
+ * (it has a default route).
+ */
+ public static final int TYPE_WIMAX = 6;
+
+ /**
+ * The Bluetooth data connection. When active, all data traffic
+ * will use this network type's interface by default
+ * (it has a default route).
+ */
+ public static final int TYPE_BLUETOOTH = 7;
+
+ /**
+ * Dummy data connection. This should not be used on shipping devices.
+ */
+ public static final int TYPE_DUMMY = 8;
+
+ /**
+ * The Ethernet data connection. When active, all data traffic
+ * will use this network type's interface by default
+ * (it has a default route).
+ */
+ public static final int TYPE_ETHERNET = 9;
+
+ /**
+ * Over the air Administration.
+ * {@hide}
+ */
+ public static final int TYPE_MOBILE_FOTA = 10;
+
+ /**
+ * IP Multimedia Subsystem.
+ * {@hide}
+ */
+ public static final int TYPE_MOBILE_IMS = 11;
+
+ /**
+ * Carrier Branded Services.
+ * {@hide}
+ */
+ public static final int TYPE_MOBILE_CBS = 12;
+
+ /**
+ * A Wi-Fi p2p connection. Only requesting processes will have access to
+ * the peers connected.
+ * {@hide}
+ */
+ public static final int TYPE_WIFI_P2P = 13;
+
+ /**
+ * The network to use for initially attaching to the network
+ * {@hide}
+ */
+ public static final int TYPE_MOBILE_IA = 14;
+
+ /**
+ * The network that uses proxy to achieve connectivity.
+ * {@hide}
+ */
+ public static final int TYPE_PROXY = 16;
+
+ /** {@hide} */
+ public static final int MAX_RADIO_TYPE = TYPE_PROXY;
+
+ /** {@hide} */
+ public static final int MAX_NETWORK_TYPE = TYPE_PROXY;
+
+ /**
+ * If you want to set the default network preference,you can directly
+ * change the networkAttributes array in framework's config.xml.
+ *
+ * @deprecated Since we support so many more networks now, the single
+ * network default network preference can't really express
+ * the hierarchy. Instead, the default is defined by the
+ * networkAttributes in config.xml. You can determine
+ * the current value by calling {@link #getNetworkPreference()}
+ * from an App.
+ */
+ @Deprecated
+ public static final int DEFAULT_NETWORK_PREFERENCE = TYPE_WIFI;
+
+ /**
+ * Default value for {@link Settings.Global#CONNECTIVITY_CHANGE_DELAY} in
+ * milliseconds. This was introduced because IPv6 routes seem to take a
+ * moment to settle - trying network activity before the routes are adjusted
+ * can lead to packets using the wrong interface or having the wrong IP address.
+ * This delay is a bit crude, but in the future hopefully we will have kernel
+ * notifications letting us know when it's safe to use the new network.
+ *
+ * @hide
+ */
+ public static final int CONNECTIVITY_CHANGE_DELAY_DEFAULT = 3000;
+
+ /**
+ * @hide
+ */
+ public final static int INVALID_NET_ID = 0;
+
+ /**
+ * @hide
+ */
+ public final static int REQUEST_ID_UNSET = 0;
+
+ private final IConnectivityManager mService;
+
+ private final String mPackageName;
+
+ private INetworkManagementService mNMService;
+
+ /**
+ * Tests if a given integer represents a valid network type.
+ * @param networkType the type to be tested
+ * @return a boolean. {@code true} if the type is valid, else {@code false}
+ */
+ public static boolean isNetworkTypeValid(int networkType) {
+ return networkType >= 0 && networkType <= MAX_NETWORK_TYPE;
+ }
+
+ /**
+ * Returns a non-localized string representing a given network type.
+ * ONLY used for debugging output.
+ * @param type the type needing naming
+ * @return a String for the given type, or a string version of the type ("87")
+ * if no name is known.
+ * {@hide}
+ */
+ public static String getNetworkTypeName(int type) {
+ switch (type) {
+ case TYPE_MOBILE:
+ return "MOBILE";
+ case TYPE_WIFI:
+ return "WIFI";
+ case TYPE_MOBILE_MMS:
+ return "MOBILE_MMS";
+ case TYPE_MOBILE_SUPL:
+ return "MOBILE_SUPL";
+ case TYPE_MOBILE_DUN:
+ return "MOBILE_DUN";
+ case TYPE_MOBILE_HIPRI:
+ return "MOBILE_HIPRI";
+ case TYPE_WIMAX:
+ return "WIMAX";
+ case TYPE_BLUETOOTH:
+ return "BLUETOOTH";
+ case TYPE_DUMMY:
+ return "DUMMY";
+ case TYPE_ETHERNET:
+ return "ETHERNET";
+ case TYPE_MOBILE_FOTA:
+ return "MOBILE_FOTA";
+ case TYPE_MOBILE_IMS:
+ return "MOBILE_IMS";
+ case TYPE_MOBILE_CBS:
+ return "MOBILE_CBS";
+ case TYPE_WIFI_P2P:
+ return "WIFI_P2P";
+ case TYPE_MOBILE_IA:
+ return "MOBILE_IA";
+ case TYPE_PROXY:
+ return "PROXY";
+ default:
+ return Integer.toString(type);
+ }
+ }
+
+ /**
+ * Checks if a given type uses the cellular data connection.
+ * This should be replaced in the future by a network property.
+ * @param networkType the type to check
+ * @return a boolean - {@code true} if uses cellular network, else {@code false}
+ * {@hide}
+ */
+ public static boolean isNetworkTypeMobile(int networkType) {
+ switch (networkType) {
+ case TYPE_MOBILE:
+ case TYPE_MOBILE_MMS:
+ case TYPE_MOBILE_SUPL:
+ case TYPE_MOBILE_DUN:
+ case TYPE_MOBILE_HIPRI:
+ case TYPE_MOBILE_FOTA:
+ case TYPE_MOBILE_IMS:
+ case TYPE_MOBILE_CBS:
+ case TYPE_MOBILE_IA:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Checks if the given network type is backed by a Wi-Fi radio.
+ *
+ * @hide
+ */
+ public static boolean isNetworkTypeWifi(int networkType) {
+ switch (networkType) {
+ case TYPE_WIFI:
+ case TYPE_WIFI_P2P:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Checks if the given network type should be exempt from VPN routing rules
+ *
+ * @hide
+ */
+ public static boolean isNetworkTypeExempt(int networkType) {
+ switch (networkType) {
+ case TYPE_MOBILE_MMS:
+ case TYPE_MOBILE_SUPL:
+ case TYPE_MOBILE_HIPRI:
+ case TYPE_MOBILE_IA:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Specifies the preferred network type. When the device has more
+ * than one type available the preferred network type will be used.
+ *
+ * @param preference the network type to prefer over all others. It is
+ * unspecified what happens to the old preferred network in the
+ * overall ordering.
+ * @deprecated Functionality has been removed as it no longer makes sense,
+ * with many more than two networks - we'd need an array to express
+ * preference. Instead we use dynamic network properties of
+ * the networks to describe their precedence.
+ */
+ public void setNetworkPreference(int preference) {
+ }
+
+ /**
+ * Retrieves the current preferred network type.
+ *
+ * @return an integer representing the preferred network type
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ * @deprecated Functionality has been removed as it no longer makes sense,
+ * with many more than two networks - we'd need an array to express
+ * preference. Instead we use dynamic network properties of
+ * the networks to describe their precedence.
+ */
+ public int getNetworkPreference() {
+ return TYPE_NONE;
+ }
+
+ /**
+ * Returns details about the currently active default data network. When
+ * connected, this network is the default route for outgoing connections.
+ * You should always check {@link NetworkInfo#isConnected()} before initiating
+ * network traffic. This may return {@code null} when there is no default
+ * network.
+ *
+ * @return a {@link NetworkInfo} object for the current default network
+ * or {@code null} if no network default network is currently active
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ */
+ public NetworkInfo getActiveNetworkInfo() {
+ try {
+ return mService.getActiveNetworkInfo();
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Returns details about the currently active default data network
+ * for a given uid. This is for internal use only to avoid spying
+ * other apps.
+ *
+ * @return a {@link NetworkInfo} object for the current default network
+ * for the given uid or {@code null} if no default network is
+ * available for the specified uid.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}
+ * {@hide}
+ */
+ public NetworkInfo getActiveNetworkInfoForUid(int uid) {
+ try {
+ return mService.getActiveNetworkInfoForUid(uid);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Returns connection status information about a particular
+ * network type.
+ *
+ * @param networkType integer specifying which networkType in
+ * which you're interested.
+ * @return a {@link NetworkInfo} object for the requested
+ * network type or {@code null} if the type is not
+ * supported by the device.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ */
+ public NetworkInfo getNetworkInfo(int networkType) {
+ try {
+ return mService.getNetworkInfo(networkType);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Returns connection status information about all network
+ * types supported by the device.
+ *
+ * @return an array of {@link NetworkInfo} objects. Check each
+ * {@link NetworkInfo#getType} for which type each applies.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ */
+ public NetworkInfo[] getAllNetworkInfo() {
+ try {
+ return mService.getAllNetworkInfo();
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Returns details about the Provisioning or currently active default data network. When
+ * connected, this network is the default route for outgoing connections.
+ * You should always check {@link NetworkInfo#isConnected()} before initiating
+ * network traffic. This may return {@code null} when there is no default
+ * network.
+ *
+ * @return a {@link NetworkInfo} object for the current default network
+ * or {@code null} if no network default network is currently active
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ *
+ * {@hide}
+ */
+ public NetworkInfo getProvisioningOrActiveNetworkInfo() {
+ try {
+ return mService.getProvisioningOrActiveNetworkInfo();
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the IP information for the current default network.
+ *
+ * @return a {@link LinkProperties} object describing the IP info
+ * for the current default network, or {@code null} if there
+ * is no current default network.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ * {@hide}
+ */
+ public LinkProperties getActiveLinkProperties() {
+ try {
+ return mService.getActiveLinkProperties();
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the IP information for a given network type.
+ *
+ * @param networkType the network type of interest.
+ * @return a {@link LinkProperties} object describing the IP info
+ * for the given networkType, or {@code null} if there is
+ * no current default network.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ * {@hide}
+ */
+ public LinkProperties getLinkProperties(int networkType) {
+ try {
+ return mService.getLinkPropertiesForType(networkType);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Get the {@link LinkProperties} for the given {@link Network}. This
+ * will return {@code null} if the network is unknown.
+ *
+ * @param network The {@link Network} object identifying the network in question.
+ * @return The {@link LinkProperties} for the network, or {@code null}.
+ **/
+ public LinkProperties getLinkProperties(Network network) {
+ try {
+ return mService.getLinkProperties(network);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Get the {@link NetworkCapabilities} for the given {@link Network}. This
+ * will return {@code null} if the network is unknown.
+ *
+ * @param network The {@link Network} object identifying the network in question.
+ * @return The {@link NetworkCapabilities} for the network, or {@code null}.
+ */
+ public NetworkCapabilities getNetworkCapabilities(Network network) {
+ try {
+ return mService.getNetworkCapabilities(network);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Tells each network type to set its radio power state as directed.
+ *
+ * @param turnOn a boolean, {@code true} to turn the radios on,
+ * {@code false} to turn them off.
+ * @return a boolean, {@code true} indicating success. All network types
+ * will be tried, even if some fail.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
+ * {@hide}
+ */
+// TODO - check for any callers and remove
+// public boolean setRadios(boolean turnOn) {
+// try {
+// return mService.setRadios(turnOn);
+// } catch (RemoteException e) {
+// return false;
+// }
+// }
+
+ /**
+ * Tells a given networkType to set its radio power state as directed.
+ *
+ * @param networkType the int networkType of interest.
+ * @param turnOn a boolean, {@code true} to turn the radio on,
+ * {@code} false to turn it off.
+ * @return a boolean, {@code true} indicating success.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
+ * {@hide}
+ */
+// TODO - check for any callers and remove
+// public boolean setRadio(int networkType, boolean turnOn) {
+// try {
+// return mService.setRadio(networkType, turnOn);
+// } catch (RemoteException e) {
+// return false;
+// }
+// }
+
+ /**
+ * Tells the underlying networking system that the caller wants to
+ * begin using the named feature. The interpretation of {@code feature}
+ * is completely up to each networking implementation.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
+ * @param networkType specifies which network the request pertains to
+ * @param feature the name of the feature to be used
+ * @return an integer value representing the outcome of the request.
+ * The interpretation of this value is specific to each networking
+ * implementation+feature combination, except that the value {@code -1}
+ * always indicates failure.
+ *
+ * @deprecated Deprecated in favor of the cleaner {@link #requestNetwork} api.
+ */
+ public int startUsingNetworkFeature(int networkType, String feature) {
+ NetworkCapabilities netCap = networkCapabilitiesForFeature(networkType, feature);
+ if (netCap == null) {
+ Log.d(TAG, "Can't satisfy startUsingNetworkFeature for " + networkType + ", " +
+ feature);
+ return PhoneConstants.APN_REQUEST_FAILED;
+ }
+
+ NetworkRequest request = null;
+ synchronized (sLegacyRequests) {
+ LegacyRequest l = sLegacyRequests.get(netCap);
+ if (l != null) {
+ Log.d(TAG, "renewing startUsingNetworkFeature request " + l.networkRequest);
+ renewRequestLocked(l);
+ if (l.currentNetwork != null) {
+ return PhoneConstants.APN_ALREADY_ACTIVE;
+ } else {
+ return PhoneConstants.APN_REQUEST_STARTED;
+ }
+ }
+
+ request = requestNetworkForFeatureLocked(netCap);
+ }
+ if (request != null) {
+ Log.d(TAG, "starting startUsingNeworkFeature for request " + request);
+ return PhoneConstants.APN_REQUEST_STARTED;
+ } else {
+ Log.d(TAG, " request Failed");
+ return PhoneConstants.APN_REQUEST_FAILED;
+ }
+ }
+
+ /**
+ * Tells the underlying networking system that the caller is finished
+ * using the named feature. The interpretation of {@code feature}
+ * is completely up to each networking implementation.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
+ * @param networkType specifies which network the request pertains to
+ * @param feature the name of the feature that is no longer needed
+ * @return an integer value representing the outcome of the request.
+ * The interpretation of this value is specific to each networking
+ * implementation+feature combination, except that the value {@code -1}
+ * always indicates failure.
+ *
+ * @deprecated Deprecated in favor of the cleaner {@link #requestNetwork} api.
+ */
+ public int stopUsingNetworkFeature(int networkType, String feature) {
+ NetworkCapabilities netCap = networkCapabilitiesForFeature(networkType, feature);
+ if (netCap == null) {
+ Log.d(TAG, "Can't satisfy stopUsingNetworkFeature for " + networkType + ", " +
+ feature);
+ return -1;
+ }
+
+ NetworkRequest request = removeRequestForFeature(netCap);
+ if (request != null) {
+ Log.d(TAG, "stopUsingNetworkFeature for " + networkType + ", " + feature);
+ releaseNetworkRequest(request);
+ }
+ return 1;
+ }
+
+ /**
+ * Removes the NET_CAPABILITY_NOT_RESTRICTED capability from the given
+ * NetworkCapabilities object if all the capabilities it provides are
+ * typically provided by restricted networks.
+ *
+ * TODO: consider:
+ * - Moving to NetworkCapabilities
+ * - Renaming it to guessRestrictedCapability and make it set the
+ * restricted capability bit in addition to clearing it.
+ * @hide
+ */
+ public static void maybeMarkCapabilitiesRestricted(NetworkCapabilities nc) {
+ for (int capability : nc.getCapabilities()) {
+ switch (capability) {
+ case NetworkCapabilities.NET_CAPABILITY_CBS:
+ case NetworkCapabilities.NET_CAPABILITY_DUN:
+ case NetworkCapabilities.NET_CAPABILITY_EIMS:
+ case NetworkCapabilities.NET_CAPABILITY_FOTA:
+ case NetworkCapabilities.NET_CAPABILITY_IA:
+ case NetworkCapabilities.NET_CAPABILITY_IMS:
+ case NetworkCapabilities.NET_CAPABILITY_RCS:
+ case NetworkCapabilities.NET_CAPABILITY_XCAP:
+ case NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED: //there by default
+ continue;
+ default:
+ // At least one capability usually provided by unrestricted
+ // networks. Conclude that this network is unrestricted.
+ return;
+ }
+ }
+ // All the capabilities are typically provided by restricted networks.
+ // Conclude that this network is restricted.
+ nc.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+ }
+
+ private NetworkCapabilities networkCapabilitiesForFeature(int networkType, String feature) {
+ if (networkType == TYPE_MOBILE) {
+ int cap = -1;
+ if ("enableMMS".equals(feature)) {
+ cap = NetworkCapabilities.NET_CAPABILITY_MMS;
+ } else if ("enableSUPL".equals(feature)) {
+ cap = NetworkCapabilities.NET_CAPABILITY_SUPL;
+ } else if ("enableDUN".equals(feature) || "enableDUNAlways".equals(feature)) {
+ cap = NetworkCapabilities.NET_CAPABILITY_DUN;
+ } else if ("enableHIPRI".equals(feature)) {
+ cap = NetworkCapabilities.NET_CAPABILITY_INTERNET;
+ } else if ("enableFOTA".equals(feature)) {
+ cap = NetworkCapabilities.NET_CAPABILITY_FOTA;
+ } else if ("enableIMS".equals(feature)) {
+ cap = NetworkCapabilities.NET_CAPABILITY_IMS;
+ } else if ("enableCBS".equals(feature)) {
+ cap = NetworkCapabilities.NET_CAPABILITY_CBS;
+ } else {
+ return null;
+ }
+ NetworkCapabilities netCap = new NetworkCapabilities();
+ netCap.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR).addCapability(cap);
+ maybeMarkCapabilitiesRestricted(netCap);
+ return netCap;
+ } else if (networkType == TYPE_WIFI) {
+ if ("p2p".equals(feature)) {
+ NetworkCapabilities netCap = new NetworkCapabilities();
+ netCap.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
+ netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_WIFI_P2P);
+ maybeMarkCapabilitiesRestricted(netCap);
+ return netCap;
+ }
+ }
+ return null;
+ }
+
+ private int legacyTypeForNetworkCapabilities(NetworkCapabilities netCap) {
+ if (netCap == null) return TYPE_NONE;
+ if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) {
+ return TYPE_MOBILE_CBS;
+ }
+ if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_IMS)) {
+ return TYPE_MOBILE_IMS;
+ }
+ if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_FOTA)) {
+ return TYPE_MOBILE_FOTA;
+ }
+ if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_DUN)) {
+ return TYPE_MOBILE_DUN;
+ }
+ if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)) {
+ return TYPE_MOBILE_SUPL;
+ }
+ if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)) {
+ return TYPE_MOBILE_MMS;
+ }
+ if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
+ return TYPE_MOBILE_HIPRI;
+ }
+ if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_WIFI_P2P)) {
+ return TYPE_WIFI_P2P;
+ }
+ return TYPE_NONE;
+ }
+
+ private static class LegacyRequest {
+ NetworkCapabilities networkCapabilities;
+ NetworkRequest networkRequest;
+ int expireSequenceNumber;
+ Network currentNetwork;
+ int delay = -1;
+ NetworkCallbackListener networkCallbackListener = new NetworkCallbackListener() {
+ @Override
+ public void onAvailable(NetworkRequest request, Network network) {
+ currentNetwork = network;
+ Log.d(TAG, "startUsingNetworkFeature got Network:" + network);
+ setProcessDefaultNetworkForHostResolution(network);
+ }
+ @Override
+ public void onLost(NetworkRequest request, Network network) {
+ if (network.equals(currentNetwork)) {
+ currentNetwork = null;
+ setProcessDefaultNetworkForHostResolution(null);
+ }
+ Log.d(TAG, "startUsingNetworkFeature lost Network:" + network);
+ }
+ };
+ }
+
+ private HashMap<NetworkCapabilities, LegacyRequest> sLegacyRequests =
+ new HashMap<NetworkCapabilities, LegacyRequest>();
+
+ private NetworkRequest findRequestForFeature(NetworkCapabilities netCap) {
+ synchronized (sLegacyRequests) {
+ LegacyRequest l = sLegacyRequests.get(netCap);
+ if (l != null) return l.networkRequest;
+ }
+ return null;
+ }
+
+ private void renewRequestLocked(LegacyRequest l) {
+ l.expireSequenceNumber++;
+ Log.d(TAG, "renewing request to seqNum " + l.expireSequenceNumber);
+ sendExpireMsgForFeature(l.networkCapabilities, l.expireSequenceNumber, l.delay);
+ }
+
+ private void expireRequest(NetworkCapabilities netCap, int sequenceNum) {
+ int ourSeqNum = -1;
+ synchronized (sLegacyRequests) {
+ LegacyRequest l = sLegacyRequests.get(netCap);
+ if (l == null) return;
+ ourSeqNum = l.expireSequenceNumber;
+ if (l.expireSequenceNumber == sequenceNum) {
+ releaseNetworkRequest(l.networkRequest);
+ sLegacyRequests.remove(netCap);
+ }
+ }
+ Log.d(TAG, "expireRequest with " + ourSeqNum + ", " + sequenceNum);
+ }
+
+ private NetworkRequest requestNetworkForFeatureLocked(NetworkCapabilities netCap) {
+ int delay = -1;
+ int type = legacyTypeForNetworkCapabilities(netCap);
+ try {
+ delay = mService.getRestoreDefaultNetworkDelay(type);
+ } catch (RemoteException e) {}
+ LegacyRequest l = new LegacyRequest();
+ l.networkCapabilities = netCap;
+ l.delay = delay;
+ l.expireSequenceNumber = 0;
+ l.networkRequest = sendRequestForNetwork(netCap, l.networkCallbackListener, 0,
+ REQUEST, type);
+ if (l.networkRequest == null) return null;
+ sLegacyRequests.put(netCap, l);
+ sendExpireMsgForFeature(netCap, l.expireSequenceNumber, delay);
+ return l.networkRequest;
+ }
+
+ private void sendExpireMsgForFeature(NetworkCapabilities netCap, int seqNum, int delay) {
+ if (delay >= 0) {
+ Log.d(TAG, "sending expire msg with seqNum " + seqNum + " and delay " + delay);
+ Message msg = sCallbackHandler.obtainMessage(EXPIRE_LEGACY_REQUEST, seqNum, 0, netCap);
+ sCallbackHandler.sendMessageDelayed(msg, delay);
+ }
+ }
+
+ private NetworkRequest removeRequestForFeature(NetworkCapabilities netCap) {
+ synchronized (sLegacyRequests) {
+ LegacyRequest l = sLegacyRequests.remove(netCap);
+ if (l == null) return null;
+ return l.networkRequest;
+ }
+ }
+
+ /**
+ * Ensure that a network route exists to deliver traffic to the specified
+ * host via the specified network interface. An attempt to add a route that
+ * already exists is ignored, but treated as successful.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
+ * @param networkType the type of the network over which traffic to the specified
+ * host is to be routed
+ * @param hostAddress the IP address of the host to which the route is desired
+ * @return {@code true} on success, {@code false} on failure
+ *
+ * @deprecated Deprecated in favor of the {@link #requestNetwork},
+ * {@link #setProcessDefaultNetwork} and {@link Network#getSocketFactory} api.
+ */
+ public boolean requestRouteToHost(int networkType, int hostAddress) {
+ InetAddress inetAddress = NetworkUtils.intToInetAddress(hostAddress);
+
+ if (inetAddress == null) {
+ return false;
+ }
+
+ return requestRouteToHostAddress(networkType, inetAddress);
+ }
+
+ /**
+ * Ensure that a network route exists to deliver traffic to the specified
+ * host via the specified network interface. An attempt to add a route that
+ * already exists is ignored, but treated as successful.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
+ * @param networkType the type of the network over which traffic to the specified
+ * host is to be routed
+ * @param hostAddress the IP address of the host to which the route is desired
+ * @return {@code true} on success, {@code false} on failure
+ * @hide
+ * @deprecated Deprecated in favor of the {@link #requestNetwork} and
+ * {@link #setProcessDefaultNetwork} api.
+ */
+ public boolean requestRouteToHostAddress(int networkType, InetAddress hostAddress) {
+ byte[] address = hostAddress.getAddress();
+ try {
+ return mService.requestRouteToHostAddress(networkType, address, mPackageName);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Returns the value of the setting for background data usage. If false,
+ * applications should not use the network if the application is not in the
+ * foreground. Developers should respect this setting, and check the value
+ * of this before performing any background data operations.
+ * <p>
+ * All applications that have background services that use the network
+ * should listen to {@link #ACTION_BACKGROUND_DATA_SETTING_CHANGED}.
+ * <p>
+ * @deprecated As of {@link VERSION_CODES#ICE_CREAM_SANDWICH}, availability of
+ * background data depends on several combined factors, and this method will
+ * always return {@code true}. Instead, when background data is unavailable,
+ * {@link #getActiveNetworkInfo()} will now appear disconnected.
+ *
+ * @return Whether background data usage is allowed.
+ */
+ @Deprecated
+ public boolean getBackgroundDataSetting() {
+ // assume that background data is allowed; final authority is
+ // NetworkInfo which may be blocked.
+ return true;
+ }
+
+ /**
+ * Sets the value of the setting for background data usage.
+ *
+ * @param allowBackgroundData Whether an application should use data while
+ * it is in the background.
+ *
+ * @attr ref android.Manifest.permission#CHANGE_BACKGROUND_DATA_SETTING
+ * @see #getBackgroundDataSetting()
+ * @hide
+ */
+ @Deprecated
+ public void setBackgroundDataSetting(boolean allowBackgroundData) {
+ // ignored
+ }
+
+ /**
+ * Return quota status for the current active network, or {@code null} if no
+ * network is active. Quota status can change rapidly, so these values
+ * shouldn't be cached.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ *
+ * @hide
+ */
+ public NetworkQuotaInfo getActiveNetworkQuotaInfo() {
+ try {
+ return mService.getActiveNetworkQuotaInfo();
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /**
+ * @hide
+ * @deprecated Talk to TelephonyManager directly
+ */
+ public boolean getMobileDataEnabled() {
+ IBinder b = ServiceManager.getService(Context.TELEPHONY_SERVICE);
+ if (b != null) {
+ try {
+ ITelephony it = ITelephony.Stub.asInterface(b);
+ return it.getDataEnabled();
+ } catch (RemoteException e) { }
+ }
+ return false;
+ }
+
+ /**
+ * Callback for use with {@link ConnectivityManager#registerNetworkActiveListener} to
+ * find out when the current network has gone in to a high power state.
+ */
+ public interface OnNetworkActiveListener {
+ /**
+ * Called on the main thread of the process to report that the current data network
+ * has become active, and it is now a good time to perform any pending network
+ * operations. Note that this listener only tells you when the network becomes
+ * active; if at any other time you want to know whether it is active (and thus okay
+ * to initiate network traffic), you can retrieve its instantaneous state with
+ * {@link ConnectivityManager#isNetworkActive}.
+ */
+ public void onNetworkActive();
+ }
+
+ private INetworkManagementService getNetworkManagementService() {
+ synchronized (this) {
+ if (mNMService != null) {
+ return mNMService;
+ }
+ IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+ mNMService = INetworkManagementService.Stub.asInterface(b);
+ return mNMService;
+ }
+ }
+
+ private final ArrayMap<OnNetworkActiveListener, INetworkActivityListener>
+ mNetworkActivityListeners
+ = new ArrayMap<OnNetworkActiveListener, INetworkActivityListener>();
+
+ /**
+ * Start listening to reports when the data network is active, meaning it is
+ * a good time to perform network traffic. Use {@link #isNetworkActive()}
+ * to determine the current state of the network after registering the listener.
+ *
+ * @param l The listener to be told when the network is active.
+ */
+ public void registerNetworkActiveListener(final OnNetworkActiveListener l) {
+ INetworkActivityListener rl = new INetworkActivityListener.Stub() {
+ @Override
+ public void onNetworkActive() throws RemoteException {
+ l.onNetworkActive();
+ }
+ };
+
+ try {
+ getNetworkManagementService().registerNetworkActivityListener(rl);
+ mNetworkActivityListeners.put(l, rl);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Remove network active listener previously registered with
+ * {@link #registerNetworkActiveListener}.
+ *
+ * @param l Previously registered listener.
+ */
+ public void unregisterNetworkActiveListener(OnNetworkActiveListener l) {
+ INetworkActivityListener rl = mNetworkActivityListeners.get(l);
+ if (rl == null) {
+ throw new IllegalArgumentException("Listener not registered: " + l);
+ }
+ try {
+ getNetworkManagementService().unregisterNetworkActivityListener(rl);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Return whether the data network is currently active. An active network means that
+ * it is currently in a high power state for performing data transmission. On some
+ * types of networks, it may be expensive to move and stay in such a state, so it is
+ * more power efficient to batch network traffic together when the radio is already in
+ * this state. This method tells you whether right now is currently a good time to
+ * initiate network traffic, as the network is already active.
+ */
+ public boolean isNetworkActive() {
+ try {
+ return getNetworkManagementService().isNetworkActive();
+ } catch (RemoteException e) {
+ }
+ return false;
+ }
+
+ /**
+ * {@hide}
+ */
+ public ConnectivityManager(IConnectivityManager service, String packageName) {
+ mService = checkNotNull(service, "missing IConnectivityManager");
+ mPackageName = checkNotNull(packageName, "missing package name");
+ }
+
+ /** {@hide} */
+ public static ConnectivityManager from(Context context) {
+ return (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ }
+
+ /**
+ * Get the set of tetherable, available interfaces. This list is limited by
+ * device configuration and current interface existence.
+ *
+ * @return an array of 0 or more Strings of tetherable interface names.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ * {@hide}
+ */
+ public String[] getTetherableIfaces() {
+ try {
+ return mService.getTetherableIfaces();
+ } catch (RemoteException e) {
+ return new String[0];
+ }
+ }
+
+ /**
+ * Get the set of tethered interfaces.
+ *
+ * @return an array of 0 or more String of currently tethered interface names.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ * {@hide}
+ */
+ public String[] getTetheredIfaces() {
+ try {
+ return mService.getTetheredIfaces();
+ } catch (RemoteException e) {
+ return new String[0];
+ }
+ }
+
+ /**
+ * Get the set of interface names which attempted to tether but
+ * failed. Re-attempting to tether may cause them to reset to the Tethered
+ * state. Alternatively, causing the interface to be destroyed and recreated
+ * may cause them to reset to the available state.
+ * {@link ConnectivityManager#getLastTetherError} can be used to get more
+ * information on the cause of the errors.
+ *
+ * @return an array of 0 or more String indicating the interface names
+ * which failed to tether.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ * {@hide}
+ */
+ public String[] getTetheringErroredIfaces() {
+ try {
+ return mService.getTetheringErroredIfaces();
+ } catch (RemoteException e) {
+ return new String[0];
+ }
+ }
+
+ /**
+ * Attempt to tether the named interface. This will setup a dhcp server
+ * on the interface, forward and NAT IP packets and forward DNS requests
+ * to the best active upstream network interface. Note that if no upstream
+ * IP network interface is available, dhcp will still run and traffic will be
+ * allowed between the tethered devices and this device, though upstream net
+ * access will of course fail until an upstream network interface becomes
+ * active.
+ *
+ * @param iface the interface name to tether.
+ * @return error a {@code TETHER_ERROR} value indicating success or failure type
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
+ * {@hide}
+ */
+ public int tether(String iface) {
+ try {
+ return mService.tether(iface);
+ } catch (RemoteException e) {
+ return TETHER_ERROR_SERVICE_UNAVAIL;
+ }
+ }
+
+ /**
+ * Stop tethering the named interface.
+ *
+ * @param iface the interface name to untether.
+ * @return error a {@code TETHER_ERROR} value indicating success or failure type
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
+ * {@hide}
+ */
+ public int untether(String iface) {
+ try {
+ return mService.untether(iface);
+ } catch (RemoteException e) {
+ return TETHER_ERROR_SERVICE_UNAVAIL;
+ }
+ }
+
+ /**
+ * Check if the device allows for tethering. It may be disabled via
+ * {@code ro.tether.denied} system property, Settings.TETHER_SUPPORTED or
+ * due to device configuration.
+ *
+ * @return a boolean - {@code true} indicating Tethering is supported.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ * {@hide}
+ */
+ public boolean isTetheringSupported() {
+ try {
+ return mService.isTetheringSupported();
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Get the list of regular expressions that define any tetherable
+ * USB network interfaces. If USB tethering is not supported by the
+ * device, this list should be empty.
+ *
+ * @return an array of 0 or more regular expression Strings defining
+ * what interfaces are considered tetherable usb interfaces.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ * {@hide}
+ */
+ public String[] getTetherableUsbRegexs() {
+ try {
+ return mService.getTetherableUsbRegexs();
+ } catch (RemoteException e) {
+ return new String[0];
+ }
+ }
+
+ /**
+ * Get the list of regular expressions that define any tetherable
+ * Wifi network interfaces. If Wifi tethering is not supported by the
+ * device, this list should be empty.
+ *
+ * @return an array of 0 or more regular expression Strings defining
+ * what interfaces are considered tetherable wifi interfaces.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ * {@hide}
+ */
+ public String[] getTetherableWifiRegexs() {
+ try {
+ return mService.getTetherableWifiRegexs();
+ } catch (RemoteException e) {
+ return new String[0];
+ }
+ }
+
+ /**
+ * Get the list of regular expressions that define any tetherable
+ * Bluetooth network interfaces. If Bluetooth tethering is not supported by the
+ * device, this list should be empty.
+ *
+ * @return an array of 0 or more regular expression Strings defining
+ * what interfaces are considered tetherable bluetooth interfaces.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ * {@hide}
+ */
+ public String[] getTetherableBluetoothRegexs() {
+ try {
+ return mService.getTetherableBluetoothRegexs();
+ } catch (RemoteException e) {
+ return new String[0];
+ }
+ }
+
+ /**
+ * Attempt to both alter the mode of USB and Tethering of USB. A
+ * utility method to deal with some of the complexity of USB - will
+ * attempt to switch to Rndis and subsequently tether the resulting
+ * interface on {@code true} or turn off tethering and switch off
+ * Rndis on {@code false}.
+ *
+ * @param enable a boolean - {@code true} to enable tethering
+ * @return error a {@code TETHER_ERROR} value indicating success or failure type
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
+ * {@hide}
+ */
+ public int setUsbTethering(boolean enable) {
+ try {
+ return mService.setUsbTethering(enable);
+ } catch (RemoteException e) {
+ return TETHER_ERROR_SERVICE_UNAVAIL;
+ }
+ }
+
+ /** {@hide} */
+ public static final int TETHER_ERROR_NO_ERROR = 0;
+ /** {@hide} */
+ public static final int TETHER_ERROR_UNKNOWN_IFACE = 1;
+ /** {@hide} */
+ public static final int TETHER_ERROR_SERVICE_UNAVAIL = 2;
+ /** {@hide} */
+ public static final int TETHER_ERROR_UNSUPPORTED = 3;
+ /** {@hide} */
+ public static final int TETHER_ERROR_UNAVAIL_IFACE = 4;
+ /** {@hide} */
+ public static final int TETHER_ERROR_MASTER_ERROR = 5;
+ /** {@hide} */
+ public static final int TETHER_ERROR_TETHER_IFACE_ERROR = 6;
+ /** {@hide} */
+ public static final int TETHER_ERROR_UNTETHER_IFACE_ERROR = 7;
+ /** {@hide} */
+ public static final int TETHER_ERROR_ENABLE_NAT_ERROR = 8;
+ /** {@hide} */
+ public static final int TETHER_ERROR_DISABLE_NAT_ERROR = 9;
+ /** {@hide} */
+ public static final int TETHER_ERROR_IFACE_CFG_ERROR = 10;
+
+ /**
+ * Get a more detailed error code after a Tethering or Untethering
+ * request asynchronously failed.
+ *
+ * @param iface The name of the interface of interest
+ * @return error The error code of the last error tethering or untethering the named
+ * interface
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ * {@hide}
+ */
+ public int getLastTetherError(String iface) {
+ try {
+ return mService.getLastTetherError(iface);
+ } catch (RemoteException e) {
+ return TETHER_ERROR_SERVICE_UNAVAIL;
+ }
+ }
+
+ /**
+ * Try to ensure the device stays awake until we connect with the next network.
+ * Actually just holds a wakelock for a number of seconds while we try to connect
+ * to any default networks. This will expire if the timeout passes or if we connect
+ * to a default after this is called. For internal use only.
+ *
+ * @param forWhom the name of the network going down for logging purposes
+ * @return {@code true} on success, {@code false} on failure
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}.
+ * {@hide}
+ */
+ public boolean requestNetworkTransitionWakelock(String forWhom) {
+ try {
+ mService.requestNetworkTransitionWakelock(forWhom);
+ return true;
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Report network connectivity status. This is currently used only
+ * to alter status bar UI.
+ *
+ * @param networkType The type of network you want to report on
+ * @param percentage The quality of the connection 0 is bad, 100 is good
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#STATUS_BAR}.
+ * {@hide}
+ */
+ public void reportInetCondition(int networkType, int percentage) {
+ try {
+ mService.reportInetCondition(networkType, percentage);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Report a problem network to the framework. This provides a hint to the system
+ * that there might be connectivity problems on this network and may cause
+ * the framework to re-evaluate network connectivity and/or switch to another
+ * network.
+ *
+ * @param network The {@link Network} the application was attempting to use
+ * or {@code null} to indicate the current default network.
+ */
+ public void reportBadNetwork(Network network) {
+ try {
+ mService.reportBadNetwork(network);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Set a network-independent global http proxy. This is not normally what you want
+ * for typical HTTP proxies - they are general network dependent. However if you're
+ * doing something unusual like general internal filtering this may be useful. On
+ * a private network where the proxy is not accessible, you may break HTTP using this.
+ *
+ * @param p The a {@link ProxyInfo} object defining the new global
+ * HTTP proxy. A {@code null} value will clear the global HTTP proxy.
+ *
+ * <p>This method requires the call to hold the permission
+ * android.Manifest.permission#CONNECTIVITY_INTERNAL.
+ * @hide
+ */
+ public void setGlobalProxy(ProxyInfo p) {
+ try {
+ mService.setGlobalProxy(p);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Retrieve any network-independent global HTTP proxy.
+ *
+ * @return {@link ProxyInfo} for the current global HTTP proxy or {@code null}
+ * if no global HTTP proxy is set.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ * @hide
+ */
+ public ProxyInfo getGlobalProxy() {
+ try {
+ return mService.getGlobalProxy();
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Get the HTTP proxy settings for the current default network. Note that
+ * if a global proxy is set, it will override any per-network setting.
+ *
+ * @return the {@link ProxyInfo} for the current HTTP proxy, or {@code null} if no
+ * HTTP proxy is active.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ * {@hide}
+ * @deprecated Deprecated in favor of {@link #getLinkProperties}
+ */
+ public ProxyInfo getProxy() {
+ try {
+ return mService.getProxy();
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Sets a secondary requirement bit for the given networkType.
+ * This requirement bit is generally under the control of the carrier
+ * or its agents and is not directly controlled by the user.
+ *
+ * @param networkType The network who's dependence has changed
+ * @param met Boolean - true if network use is OK, false if not
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}.
+ * {@hide}
+ */
+ public void setDataDependency(int networkType, boolean met) {
+ try {
+ mService.setDataDependency(networkType, met);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Returns true if the hardware supports the given network type
+ * else it returns false. This doesn't indicate we have coverage
+ * or are authorized onto a network, just whether or not the
+ * hardware supports it. For example a GSM phone without a SIM
+ * should still return {@code true} for mobile data, but a wifi only
+ * tablet would return {@code false}.
+ *
+ * @param networkType The network type we'd like to check
+ * @return {@code true} if supported, else {@code false}
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ * @hide
+ */
+ public boolean isNetworkSupported(int networkType) {
+ try {
+ return mService.isNetworkSupported(networkType);
+ } catch (RemoteException e) {}
+ return false;
+ }
+
+ /**
+ * Returns if the currently active data network is metered. A network is
+ * classified as metered when the user is sensitive to heavy data usage on
+ * that connection due to monetary costs, data limitations or
+ * battery/performance issues. You should check this before doing large
+ * data transfers, and warn the user or delay the operation until another
+ * network is available.
+ *
+ * @return {@code true} if large transfers should be avoided, otherwise
+ * {@code false}.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ */
+ public boolean isActiveNetworkMetered() {
+ try {
+ return mService.isActiveNetworkMetered();
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
+ * If the LockdownVpn mechanism is enabled, updates the vpn
+ * with a reload of its profile.
+ *
+ * @return a boolean with {@code} indicating success
+ *
+ * <p>This method can only be called by the system UID
+ * {@hide}
+ */
+ public boolean updateLockdownVpn() {
+ try {
+ return mService.updateLockdownVpn();
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Signal that the captive portal check on the indicated network
+ * is complete and whether its a captive portal or not.
+ *
+ * @param info the {@link NetworkInfo} object for the networkType
+ * in question.
+ * @param isCaptivePortal true/false.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}.
+ * {@hide}
+ */
+ public void captivePortalCheckCompleted(NetworkInfo info, boolean isCaptivePortal) {
+ try {
+ mService.captivePortalCheckCompleted(info, isCaptivePortal);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Supply the backend messenger for a network tracker
+ *
+ * @param networkType NetworkType to set
+ * @param messenger {@link Messenger}
+ * {@hide}
+ */
+ public void supplyMessenger(int networkType, Messenger messenger) {
+ try {
+ mService.supplyMessenger(networkType, messenger);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Check mobile provisioning.
+ *
+ * @param suggestedTimeOutMs, timeout in milliseconds
+ *
+ * @return time out that will be used, maybe less that suggestedTimeOutMs
+ * -1 if an error.
+ *
+ * {@hide}
+ */
+ public int checkMobileProvisioning(int suggestedTimeOutMs) {
+ int timeOutMs = -1;
+ try {
+ timeOutMs = mService.checkMobileProvisioning(suggestedTimeOutMs);
+ } catch (RemoteException e) {
+ }
+ return timeOutMs;
+ }
+
+ /**
+ * Get the mobile provisioning url.
+ * {@hide}
+ */
+ public String getMobileProvisioningUrl() {
+ try {
+ return mService.getMobileProvisioningUrl();
+ } catch (RemoteException e) {
+ }
+ return null;
+ }
+
+ /**
+ * Get the mobile redirected provisioning url.
+ * {@hide}
+ */
+ public String getMobileRedirectedProvisioningUrl() {
+ try {
+ return mService.getMobileRedirectedProvisioningUrl();
+ } catch (RemoteException e) {
+ }
+ return null;
+ }
+
+ /**
+ * get the information about a specific network link
+ * @hide
+ */
+ public LinkQualityInfo getLinkQualityInfo(int networkType) {
+ try {
+ LinkQualityInfo li = mService.getLinkQualityInfo(networkType);
+ return li;
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /**
+ * get the information of currently active network link
+ * @hide
+ */
+ public LinkQualityInfo getActiveLinkQualityInfo() {
+ try {
+ LinkQualityInfo li = mService.getActiveLinkQualityInfo();
+ return li;
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /**
+ * get the information of all network links
+ * @hide
+ */
+ public LinkQualityInfo[] getAllLinkQualityInfo() {
+ try {
+ LinkQualityInfo[] li = mService.getAllLinkQualityInfo();
+ return li;
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Set sign in error notification to visible or in visible
+ *
+ * @param visible
+ * @param networkType
+ *
+ * {@hide}
+ */
+ public void setProvisioningNotificationVisible(boolean visible, int networkType,
+ String extraInfo, String url) {
+ try {
+ mService.setProvisioningNotificationVisible(visible, networkType, extraInfo, url);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Set the value for enabling/disabling airplane mode
+ *
+ * @param enable whether to enable airplane mode or not
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}.
+ * @hide
+ */
+ public void setAirplaneMode(boolean enable) {
+ try {
+ mService.setAirplaneMode(enable);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /** {@hide} */
+ public void registerNetworkFactory(Messenger messenger, String name) {
+ try {
+ mService.registerNetworkFactory(messenger, name);
+ } catch (RemoteException e) { }
+ }
+
+ /** {@hide} */
+ public void unregisterNetworkFactory(Messenger messenger) {
+ try {
+ mService.unregisterNetworkFactory(messenger);
+ } catch (RemoteException e) { }
+ }
+
+ /** {@hide} */
+ public void registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp,
+ NetworkCapabilities nc, int score) {
+ try {
+ mService.registerNetworkAgent(messenger, ni, lp, nc, score);
+ } catch (RemoteException e) { }
+ }
+
+ /**
+ * Base class for NetworkRequest callbacks. Used for notifications about network
+ * changes. Should be extended by applications wanting notifications.
+ */
+ public static class NetworkCallbackListener {
+ /** @hide */
+ public static final int PRECHECK = 1;
+ /** @hide */
+ public static final int AVAILABLE = 2;
+ /** @hide */
+ public static final int LOSING = 3;
+ /** @hide */
+ public static final int LOST = 4;
+ /** @hide */
+ public static final int UNAVAIL = 5;
+ /** @hide */
+ public static final int CAP_CHANGED = 6;
+ /** @hide */
+ public static final int PROP_CHANGED = 7;
+ /** @hide */
+ public static final int CANCELED = 8;
+
+ /**
+ * @hide
+ * Called whenever the framework connects to a network that it may use to
+ * satisfy this request
+ */
+ public void onPreCheck(NetworkRequest networkRequest, Network network) {}
+
+ /**
+ * Called when the framework connects and has declared new network ready for use.
+ *
+ * @param networkRequest The {@link NetworkRequest} used to initiate the request.
+ * @param network The {@link Network} of the satisfying network.
+ */
+ public void onAvailable(NetworkRequest networkRequest, Network network) {}
+
+ /**
+ * Called when the network is about to be disconnected. Often paired with an
+ * {@link NetworkCallbackListener#onAvailable} call with the new replacement network
+ * for graceful handover. This may not be called if we have a hard loss
+ * (loss without warning). This may be followed by either a
+ * {@link NetworkCallbackListener#onLost} call or a
+ * {@link NetworkCallbackListener#onAvailable} call for this network depending
+ * on whether we lose or regain it.
+ *
+ * @param networkRequest The {@link NetworkRequest} used to initiate the request.
+ * @param network The {@link Network} of the failing network.
+ * @param maxSecToLive The time in seconds the framework will attempt to keep the
+ * network connected. Note that the network may suffers a
+ * hard loss at any time.
+ */
+ public void onLosing(NetworkRequest networkRequest, Network network, int maxSecToLive) {}
+
+ /**
+ * Called when the framework has a hard loss of the network or when the
+ * graceful failure ends.
+ *
+ * @param networkRequest The {@link NetworkRequest} used to initiate the request.
+ * @param network The {@link Network} lost.
+ */
+ public void onLost(NetworkRequest networkRequest, Network network) {}
+
+ /**
+ * Called if no network is found in the given timeout time. If no timeout is given,
+ * this will not be called.
+ * @hide
+ */
+ public void onUnavailable(NetworkRequest networkRequest) {}
+
+ /**
+ * Called when the network the framework connected to for this request
+ * changes capabilities but still satisfies the stated need.
+ *
+ * @param networkRequest The {@link NetworkRequest} used to initiate the request.
+ * @param network The {@link Network} whose capabilities have changed.
+ * @param networkCapabilities The new {@link NetworkCapabilities} for this network.
+ */
+ public void onNetworkCapabilitiesChanged(NetworkRequest networkRequest, Network network,
+ NetworkCapabilities networkCapabilities) {}
+
+ /**
+ * Called when the network the framework connected to for this request
+ * changes {@link LinkProperties}.
+ *
+ * @param networkRequest The {@link NetworkRequest} used to initiate the request.
+ * @param network The {@link Network} whose link properties have changed.
+ * @param linkProperties The new {@link LinkProperties} for this network.
+ */
+ public void onLinkPropertiesChanged(NetworkRequest networkRequest, Network network,
+ LinkProperties linkProperties) {}
+
+ /**
+ * Called when a {@link #releaseNetworkRequest} call concludes and the registered
+ * callbacks will no longer be used.
+ *
+ * @param networkRequest The {@link NetworkRequest} used to initiate the request.
+ */
+ public void onReleased(NetworkRequest networkRequest) {}
+ }
+
+ private static final int BASE = Protocol.BASE_CONNECTIVITY_MANAGER;
+ /** @hide obj = pair(NetworkRequest, Network) */
+ public static final int CALLBACK_PRECHECK = BASE + 1;
+ /** @hide obj = pair(NetworkRequest, Network) */
+ public static final int CALLBACK_AVAILABLE = BASE + 2;
+ /** @hide obj = pair(NetworkRequest, Network), arg1 = ttl */
+ public static final int CALLBACK_LOSING = BASE + 3;
+ /** @hide obj = pair(NetworkRequest, Network) */
+ public static final int CALLBACK_LOST = BASE + 4;
+ /** @hide obj = NetworkRequest */
+ public static final int CALLBACK_UNAVAIL = BASE + 5;
+ /** @hide obj = pair(NetworkRequest, Network) */
+ public static final int CALLBACK_CAP_CHANGED = BASE + 6;
+ /** @hide obj = pair(NetworkRequest, Network) */
+ public static final int CALLBACK_IP_CHANGED = BASE + 7;
+ /** @hide obj = NetworkRequest */
+ public static final int CALLBACK_RELEASED = BASE + 8;
+ /** @hide */
+ public static final int CALLBACK_EXIT = BASE + 9;
+ /** @hide obj = NetworkCapabilities, arg1 = seq number */
+ private static final int EXPIRE_LEGACY_REQUEST = BASE + 10;
+
+ private class CallbackHandler extends Handler {
+ private final HashMap<NetworkRequest, NetworkCallbackListener>mCallbackMap;
+ private final AtomicInteger mRefCount;
+ private static final String TAG = "ConnectivityManager.CallbackHandler";
+ private final ConnectivityManager mCm;
+
+ CallbackHandler(Looper looper, HashMap<NetworkRequest, NetworkCallbackListener>callbackMap,
+ AtomicInteger refCount, ConnectivityManager cm) {
+ super(looper);
+ mCallbackMap = callbackMap;
+ mRefCount = refCount;
+ mCm = cm;
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ Log.d(TAG, "CM callback handler got msg " + message.what);
+ switch (message.what) {
+ case CALLBACK_PRECHECK: {
+ NetworkRequest request = getNetworkRequest(message);
+ NetworkCallbackListener callbacks = getCallbacks(request);
+ if (callbacks != null) {
+ callbacks.onPreCheck(request, getNetwork(message));
+ } else {
+ Log.e(TAG, "callback not found for PRECHECK message");
+ }
+ break;
+ }
+ case CALLBACK_AVAILABLE: {
+ NetworkRequest request = getNetworkRequest(message);
+ NetworkCallbackListener callbacks = getCallbacks(request);
+ if (callbacks != null) {
+ callbacks.onAvailable(request, getNetwork(message));
+ } else {
+ Log.e(TAG, "callback not found for AVAILABLE message");
+ }
+ break;
+ }
+ case CALLBACK_LOSING: {
+ NetworkRequest request = getNetworkRequest(message);
+ NetworkCallbackListener callbacks = getCallbacks(request);
+ if (callbacks != null) {
+ callbacks.onLosing(request, getNetwork(message), message.arg1);
+ } else {
+ Log.e(TAG, "callback not found for LOSING message");
+ }
+ break;
+ }
+ case CALLBACK_LOST: {
+ NetworkRequest request = getNetworkRequest(message);
+ NetworkCallbackListener callbacks = getCallbacks(request);
+ if (callbacks != null) {
+ callbacks.onLost(request, getNetwork(message));
+ } else {
+ Log.e(TAG, "callback not found for LOST message");
+ }
+ break;
+ }
+ case CALLBACK_UNAVAIL: {
+ NetworkRequest req = (NetworkRequest)message.obj;
+ NetworkCallbackListener callbacks = null;
+ synchronized(mCallbackMap) {
+ callbacks = mCallbackMap.get(req);
+ }
+ if (callbacks != null) {
+ callbacks.onUnavailable(req);
+ } else {
+ Log.e(TAG, "callback not found for UNAVAIL message");
+ }
+ break;
+ }
+ case CALLBACK_CAP_CHANGED: {
+ NetworkRequest request = getNetworkRequest(message);
+ NetworkCallbackListener callbacks = getCallbacks(request);
+ if (callbacks != null) {
+ Network network = getNetwork(message);
+ NetworkCapabilities cap = mCm.getNetworkCapabilities(network);
+
+ callbacks.onNetworkCapabilitiesChanged(request, network, cap);
+ } else {
+ Log.e(TAG, "callback not found for CHANGED message");
+ }
+ break;
+ }
+ case CALLBACK_IP_CHANGED: {
+ NetworkRequest request = getNetworkRequest(message);
+ NetworkCallbackListener callbacks = getCallbacks(request);
+ if (callbacks != null) {
+ Network network = getNetwork(message);
+ LinkProperties lp = mCm.getLinkProperties(network);
+
+ callbacks.onLinkPropertiesChanged(request, network, lp);
+ } else {
+ Log.e(TAG, "callback not found for CHANGED message");
+ }
+ break;
+ }
+ case CALLBACK_RELEASED: {
+ NetworkRequest req = (NetworkRequest)message.obj;
+ NetworkCallbackListener callbacks = null;
+ synchronized(mCallbackMap) {
+ callbacks = mCallbackMap.remove(req);
+ }
+ if (callbacks != null) {
+ callbacks.onReleased(req);
+ } else {
+ Log.e(TAG, "callback not found for CANCELED message");
+ }
+ synchronized(mRefCount) {
+ if (mRefCount.decrementAndGet() == 0) {
+ getLooper().quit();
+ }
+ }
+ break;
+ }
+ case CALLBACK_EXIT: {
+ Log.d(TAG, "Listener quiting");
+ getLooper().quit();
+ break;
+ }
+ case EXPIRE_LEGACY_REQUEST: {
+ expireRequest((NetworkCapabilities)message.obj, message.arg1);
+ break;
+ }
+ }
+ }
+
+ private NetworkRequest getNetworkRequest(Message msg) {
+ return (NetworkRequest)(msg.obj);
+ }
+ private NetworkCallbackListener getCallbacks(NetworkRequest req) {
+ synchronized(mCallbackMap) {
+ return mCallbackMap.get(req);
+ }
+ }
+ private Network getNetwork(Message msg) {
+ return new Network(msg.arg2);
+ }
+ private NetworkCallbackListener removeCallbacks(Message msg) {
+ NetworkRequest req = (NetworkRequest)msg.obj;
+ synchronized(mCallbackMap) {
+ return mCallbackMap.remove(req);
+ }
+ }
+ }
+
+ private void addCallbackListener() {
+ synchronized(sCallbackRefCount) {
+ if (sCallbackRefCount.incrementAndGet() == 1) {
+ // TODO - switch this over to a ManagerThread or expire it when done
+ HandlerThread callbackThread = new HandlerThread("ConnectivityManager");
+ callbackThread.start();
+ sCallbackHandler = new CallbackHandler(callbackThread.getLooper(),
+ sNetworkCallbackListener, sCallbackRefCount, this);
+ }
+ }
+ }
+
+ private void removeCallbackListener() {
+ synchronized(sCallbackRefCount) {
+ if (sCallbackRefCount.decrementAndGet() == 0) {
+ sCallbackHandler.obtainMessage(CALLBACK_EXIT).sendToTarget();
+ sCallbackHandler = null;
+ }
+ }
+ }
+
+ static final HashMap<NetworkRequest, NetworkCallbackListener> sNetworkCallbackListener =
+ new HashMap<NetworkRequest, NetworkCallbackListener>();
+ static final AtomicInteger sCallbackRefCount = new AtomicInteger(0);
+ static CallbackHandler sCallbackHandler = null;
+
+ private final static int LISTEN = 1;
+ private final static int REQUEST = 2;
+
+ private NetworkRequest sendRequestForNetwork(NetworkCapabilities need,
+ NetworkCallbackListener networkCallbackListener, int timeoutSec, int action,
+ int legacyType) {
+ NetworkRequest networkRequest = null;
+ if (networkCallbackListener == null) {
+ throw new IllegalArgumentException("null NetworkCallbackListener");
+ }
+ if (need == null) throw new IllegalArgumentException("null NetworkCapabilities");
+ try {
+ addCallbackListener();
+ if (action == LISTEN) {
+ networkRequest = mService.listenForNetwork(need, new Messenger(sCallbackHandler),
+ new Binder());
+ } else {
+ networkRequest = mService.requestNetwork(need, new Messenger(sCallbackHandler),
+ timeoutSec, new Binder(), legacyType);
+ }
+ if (networkRequest != null) {
+ synchronized(sNetworkCallbackListener) {
+ sNetworkCallbackListener.put(networkRequest, networkCallbackListener);
+ }
+ }
+ } catch (RemoteException e) {}
+ if (networkRequest == null) removeCallbackListener();
+ return networkRequest;
+ }
+
+ /**
+ * Request a network to satisfy a set of {@link NetworkCapabilities}.
+ *
+ * This {@link NetworkRequest} will live until released via
+ * {@link #releaseNetworkRequest} or the calling application exits.
+ * Status of the request can be followed by listening to the various
+ * callbacks described in {@link NetworkCallbackListener}. The {@link Network}
+ * can be used to direct traffic to the network.
+ *
+ * @param need {@link NetworkCapabilities} required by this request.
+ * @param networkCallbackListener The {@link NetworkCallbackListener} to be utilized for this
+ * request. Note the callbacks can be shared by multiple
+ * requests and the NetworkRequest token utilized to
+ * determine to which request the callback relates.
+ * @return A {@link NetworkRequest} object identifying the request.
+ */
+ public NetworkRequest requestNetwork(NetworkCapabilities need,
+ NetworkCallbackListener networkCallbackListener) {
+ return sendRequestForNetwork(need, networkCallbackListener, 0, REQUEST, TYPE_NONE);
+ }
+
+ /**
+ * Request a network to satisfy a set of {@link NetworkCapabilities}, limited
+ * by a timeout.
+ *
+ * This function behaves identically to the non-timedout version, but if a suitable
+ * network is not found within the given time (in Seconds) the
+ * {@link NetworkCallbackListener#unavailable} callback is called. The request must
+ * still be released normally by calling {@link releaseNetworkRequest}.
+ * @param need {@link NetworkCapabilities} required by this request.
+ * @param networkCallbackListener The callbacks to be utilized for this request. Note
+ * the callbacks can be shared by multiple requests and
+ * the NetworkRequest token utilized to determine to which
+ * request the callback relates.
+ * @param timeoutSec The time in seconds to attempt looking for a suitable network
+ * before {@link NetworkCallbackListener#unavailable} is called.
+ * @return A {@link NetworkRequest} object identifying the request.
+ * @hide
+ */
+ public NetworkRequest requestNetwork(NetworkCapabilities need,
+ NetworkCallbackListener networkCallbackListener, int timeoutSec) {
+ return sendRequestForNetwork(need, networkCallbackListener, timeoutSec, REQUEST,
+ TYPE_NONE);
+ }
+
+ /**
+ * The maximum number of seconds the framework will look for a suitable network
+ * during a timeout-equiped call to {@link requestNetwork}.
+ * {@hide}
+ */
+ public final static int MAX_NETWORK_REQUEST_TIMEOUT_SEC = 100 * 60;
+
+ /**
+ * The lookup key for a {@link Network} object included with the intent after
+ * succesfully finding a network for the applications request. Retrieve it with
+ * {@link android.content.Intent#getParcelableExtra(String)}.
+ */
+ public static final String EXTRA_NETWORK_REQUEST_NETWORK = "networkRequestNetwork";
+
+ /**
+ * The lookup key for a {@link NetworkCapabilities} object included with the intent after
+ * succesfully finding a network for the applications request. Retrieve it with
+ * {@link android.content.Intent#getParcelableExtra(String)}.
+ */
+ public static final String EXTRA_NETWORK_REQUEST_NETWORK_CAPABILITIES =
+ "networkRequestNetworkCapabilities";
+
+
+ /**
+ * Request a network to satisfy a set of {@link NetworkCapabilities}.
+ *
+ * This function behavies identically to the callback-equiped version, but instead
+ * of {@link NetworkCallbackListener} a {@link PendingIntent} is used. This means
+ * the request may outlive the calling application and get called back when a suitable
+ * network is found.
+ * <p>
+ * The operation is an Intent broadcast that goes to a broadcast receiver that
+ * you registered with {@link Context#registerReceiver} or through the
+ * <receiver> tag in an AndroidManifest.xml file
+ * <p>
+ * The operation Intent is delivered with two extras, a {@link Network} typed
+ * extra called {@link #EXTRA_NETWORK_REQUEST_NETWORK} and a {@link NetworkCapabilities}
+ * typed extra called {@link #EXTRA_NETWORK_REQUEST_NETWORK_CAPABILITIES} containing
+ * the original requests parameters. It is important to create a new,
+ * {@link NetworkCallbackListener} based request before completing the processing of the
+ * Intent to reserve the network or it will be released shortly after the Intent
+ * is processed.
+ * <p>
+ * If there is already an request for this Intent registered (with the equality of
+ * two Intents defined by {@link Intent#filterEquals}), then it will be removed and
+ * replaced by this one, effectively releasing the previous {@link NetworkRequest}.
+ * <p>
+ * The request may be released normally by calling {@link #releaseNetworkRequest}.
+ *
+ * @param need {@link NetworkCapabilities} required by this request.
+ * @param operation Action to perform when the network is available (corresponds
+ * to the {@link NetworkCallbackListener#onAvailable} call. Typically
+ * comes from {@link PendingIntent#getBroadcast}.
+ * @return A {@link NetworkRequest} object identifying the request.
+ */
+ public NetworkRequest requestNetwork(NetworkCapabilities need, PendingIntent operation) {
+ try {
+ return mService.pendingRequestForNetwork(need, operation);
+ } catch (RemoteException e) {}
+ return null;
+ }
+
+ /**
+ * Registers to receive notifications about all networks which satisfy the given
+ * {@link NetworkCapabilities}. The callbacks will continue to be called until
+ * either the application exits or the request is released using
+ * {@link #releaseNetworkRequest}.
+ *
+ * @param need {@link NetworkCapabilities} required by this request.
+ * @param networkCallbackListener The {@link NetworkCallbackListener} to be called as suitable
+ * networks change state.
+ * @return A {@link NetworkRequest} object identifying the request.
+ */
+ public NetworkRequest listenForNetwork(NetworkCapabilities need,
+ NetworkCallbackListener networkCallbackListener) {
+ return sendRequestForNetwork(need, networkCallbackListener, 0, LISTEN, TYPE_NONE);
+ }
+
+ /**
+ * Releases a {@link NetworkRequest} generated either through a {@link #requestNetwork}
+ * or a {@link #listenForNetwork} call. The {@link NetworkCallbackListener} given in the
+ * earlier call may continue receiving calls until the
+ * {@link NetworkCallbackListener#onReleased} function is called, signifying the end
+ * of the request.
+ *
+ * @param networkRequest The {@link NetworkRequest} generated by an earlier call to
+ * {@link #requestNetwork} or {@link #listenForNetwork}.
+ */
+ public void releaseNetworkRequest(NetworkRequest networkRequest) {
+ if (networkRequest == null) throw new IllegalArgumentException("null NetworkRequest");
+ try {
+ mService.releaseNetworkRequest(networkRequest);
+ } catch (RemoteException e) {}
+ }
+
+ /**
+ * Binds the current process to {@code network}. All Sockets created in the future
+ * (and not explicitly bound via a bound SocketFactory from
+ * {@link Network#getSocketFactory() Network.getSocketFactory()}) will be bound to
+ * {@code network}. All host name resolutions will be limited to {@code network} as well.
+ * Note that if {@code network} ever disconnects, all Sockets created in this way will cease to
+ * work and all host name resolutions will fail. This is by design so an application doesn't
+ * accidentally use Sockets it thinks are still bound to a particular {@link Network}.
+ * To clear binding pass {@code null} for {@code network}. Using individually bound
+ * Sockets created by Network.getSocketFactory().createSocket() and
+ * performing network-specific host name resolutions via
+ * {@link Network#getAllByName Network.getAllByName} is preferred to calling
+ * {@code setProcessDefaultNetwork}.
+ *
+ * @param network The {@link Network} to bind the current process to, or {@code null} to clear
+ * the current binding.
+ * @return {@code true} on success, {@code false} if the {@link Network} is no longer valid.
+ */
+ public static boolean setProcessDefaultNetwork(Network network) {
+ if (network == null) {
+ NetworkUtils.unbindProcessToNetwork();
+ } else {
+ NetworkUtils.bindProcessToNetwork(network.netId);
+ }
+ // TODO fix return value
+ return true;
+ }
+
+ /**
+ * Returns the {@link Network} currently bound to this process via
+ * {@link #setProcessDefaultNetwork}, or {@code null} if no {@link Network} is explicitly bound.
+ *
+ * @return {@code Network} to which this process is bound, or {@code null}.
+ */
+ public static Network getProcessDefaultNetwork() {
+ int netId = NetworkUtils.getNetworkBoundToProcess();
+ if (netId == 0) return null;
+ return new Network(netId);
+ }
+
+ /**
+ * Binds host resolutions performed by this process to {@code network}.
+ * {@link #setProcessDefaultNetwork} takes precedence over this setting.
+ *
+ * @param network The {@link Network} to bind host resolutions from the current process to, or
+ * {@code null} to clear the current binding.
+ * @return {@code true} on success, {@code false} if the {@link Network} is no longer valid.
+ * @hide
+ * @deprecated This is strictly for legacy usage to support {@link #startUsingNetworkFeature}.
+ */
+ public static boolean setProcessDefaultNetworkForHostResolution(Network network) {
+ if (network == null) {
+ NetworkUtils.unbindProcessToNetworkForHostResolution();
+ } else {
+ NetworkUtils.bindProcessToNetworkForHostResolution(network.netId);
+ }
+ // TODO hook up the return value.
+ return true;
+ }
+}
diff --git a/core/java/android/net/DhcpInfo.java b/core/java/android/net/DhcpInfo.java
new file mode 100644
index 0000000..788d7d9
--- /dev/null
+++ b/core/java/android/net/DhcpInfo.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2008 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.os.Parcelable;
+import android.os.Parcel;
+
+/**
+ * A simple object for retrieving the results of a DHCP request.
+ */
+public class DhcpInfo implements Parcelable {
+ public int ipAddress;
+ public int gateway;
+ public int netmask;
+ public int dns1;
+ public int dns2;
+ public int serverAddress;
+
+ public int leaseDuration;
+
+ public DhcpInfo() {
+ super();
+ }
+
+ /** copy constructor {@hide} */
+ public DhcpInfo(DhcpInfo source) {
+ if (source != null) {
+ ipAddress = source.ipAddress;
+ gateway = source.gateway;
+ netmask = source.netmask;
+ dns1 = source.dns1;
+ dns2 = source.dns2;
+ serverAddress = source.serverAddress;
+ leaseDuration = source.leaseDuration;
+ }
+ }
+
+ public String toString() {
+ StringBuffer str = new StringBuffer();
+
+ str.append("ipaddr "); putAddress(str, ipAddress);
+ str.append(" gateway "); putAddress(str, gateway);
+ str.append(" netmask "); putAddress(str, netmask);
+ str.append(" dns1 "); putAddress(str, dns1);
+ str.append(" dns2 "); putAddress(str, dns2);
+ str.append(" DHCP server "); putAddress(str, serverAddress);
+ str.append(" lease ").append(leaseDuration).append(" seconds");
+
+ return str.toString();
+ }
+
+ private static void putAddress(StringBuffer buf, int addr) {
+ buf.append(NetworkUtils.intToInetAddress(addr).getHostAddress());
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(ipAddress);
+ dest.writeInt(gateway);
+ dest.writeInt(netmask);
+ dest.writeInt(dns1);
+ dest.writeInt(dns2);
+ dest.writeInt(serverAddress);
+ dest.writeInt(leaseDuration);
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public static final Creator<DhcpInfo> CREATOR =
+ new Creator<DhcpInfo>() {
+ public DhcpInfo createFromParcel(Parcel in) {
+ DhcpInfo info = new DhcpInfo();
+ info.ipAddress = in.readInt();
+ info.gateway = in.readInt();
+ info.netmask = in.readInt();
+ info.dns1 = in.readInt();
+ info.dns2 = in.readInt();
+ info.serverAddress = in.readInt();
+ info.leaseDuration = in.readInt();
+ return info;
+ }
+
+ public DhcpInfo[] newArray(int size) {
+ return new DhcpInfo[size];
+ }
+ };
+}
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
new file mode 100644
index 0000000..5f1ff3e
--- /dev/null
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -0,0 +1,175 @@
+/**
+ * Copyright (c) 2008, 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.PendingIntent;
+import android.net.LinkQualityInfo;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.net.NetworkQuotaInfo;
+import android.net.NetworkRequest;
+import android.net.NetworkState;
+import android.net.ProxyInfo;
+import android.os.IBinder;
+import android.os.Messenger;
+import android.os.ParcelFileDescriptor;
+import android.os.ResultReceiver;
+
+import com.android.internal.net.LegacyVpnInfo;
+import com.android.internal.net.VpnConfig;
+import com.android.internal.net.VpnProfile;
+
+/**
+ * Interface that answers queries about, and allows changing, the
+ * state of network connectivity.
+ */
+/** {@hide} */
+interface IConnectivityManager
+{
+ // Keep this in sync with framework/native/services/connectivitymanager/ConnectivityManager.h
+ void markSocketAsUser(in ParcelFileDescriptor socket, int uid);
+
+ NetworkInfo getActiveNetworkInfo();
+ NetworkInfo getActiveNetworkInfoForUid(int uid);
+ NetworkInfo getNetworkInfo(int networkType);
+ NetworkInfo[] getAllNetworkInfo();
+
+ NetworkInfo getProvisioningOrActiveNetworkInfo();
+
+ boolean isNetworkSupported(int networkType);
+
+ LinkProperties getActiveLinkProperties();
+ LinkProperties getLinkPropertiesForType(int networkType);
+ LinkProperties getLinkProperties(in Network network);
+
+ NetworkCapabilities getNetworkCapabilities(in Network network);
+
+ NetworkState[] getAllNetworkState();
+
+ NetworkQuotaInfo getActiveNetworkQuotaInfo();
+ boolean isActiveNetworkMetered();
+
+ int startUsingNetworkFeature(int networkType, in String feature,
+ in IBinder binder);
+
+ int stopUsingNetworkFeature(int networkType, in String feature);
+
+ boolean requestRouteToHost(int networkType, int hostAddress, String packageName);
+
+ boolean requestRouteToHostAddress(int networkType, in byte[] hostAddress, String packageName);
+
+ /** Policy control over specific {@link NetworkStateTracker}. */
+ void setPolicyDataEnable(int networkType, boolean enabled);
+
+ int tether(String iface);
+
+ int untether(String iface);
+
+ int getLastTetherError(String iface);
+
+ boolean isTetheringSupported();
+
+ String[] getTetherableIfaces();
+
+ String[] getTetheredIfaces();
+
+ String[] getTetheringErroredIfaces();
+
+ String[] getTetherableUsbRegexs();
+
+ String[] getTetherableWifiRegexs();
+
+ String[] getTetherableBluetoothRegexs();
+
+ int setUsbTethering(boolean enable);
+
+ void requestNetworkTransitionWakelock(in String forWhom);
+
+ void reportInetCondition(int networkType, int percentage);
+
+ void reportBadNetwork(in Network network);
+
+ ProxyInfo getGlobalProxy();
+
+ void setGlobalProxy(in ProxyInfo p);
+
+ ProxyInfo getProxy();
+
+ void setDataDependency(int networkType, boolean met);
+
+ boolean protectVpn(in ParcelFileDescriptor socket);
+
+ boolean prepareVpn(String oldPackage, String newPackage);
+
+ ParcelFileDescriptor establishVpn(in VpnConfig config);
+
+ VpnConfig getVpnConfig();
+
+ void startLegacyVpn(in VpnProfile profile);
+
+ LegacyVpnInfo getLegacyVpnInfo();
+
+ boolean updateLockdownVpn();
+
+ void captivePortalCheckCompleted(in NetworkInfo info, boolean isCaptivePortal);
+
+ void supplyMessenger(int networkType, in Messenger messenger);
+
+ int findConnectionTypeForIface(in String iface);
+
+ int checkMobileProvisioning(int suggestedTimeOutMs);
+
+ String getMobileProvisioningUrl();
+
+ String getMobileRedirectedProvisioningUrl();
+
+ LinkQualityInfo getLinkQualityInfo(int networkType);
+
+ LinkQualityInfo getActiveLinkQualityInfo();
+
+ LinkQualityInfo[] getAllLinkQualityInfo();
+
+ void setProvisioningNotificationVisible(boolean visible, int networkType, in String extraInfo,
+ in String url);
+
+ void setAirplaneMode(boolean enable);
+
+ void registerNetworkFactory(in Messenger messenger, in String name);
+
+ void unregisterNetworkFactory(in Messenger messenger);
+
+ void registerNetworkAgent(in Messenger messenger, in NetworkInfo ni, in LinkProperties lp,
+ in NetworkCapabilities nc, int score);
+
+ NetworkRequest requestNetwork(in NetworkCapabilities networkCapabilities,
+ in Messenger messenger, int timeoutSec, in IBinder binder, int legacy);
+
+ NetworkRequest pendingRequestForNetwork(in NetworkCapabilities networkCapabilities,
+ in PendingIntent operation);
+
+ NetworkRequest listenForNetwork(in NetworkCapabilities networkCapabilities,
+ in Messenger messenger, in IBinder binder);
+
+ void pendingListenForNetwork(in NetworkCapabilities networkCapabilities,
+ in PendingIntent operation);
+
+ void releaseNetworkRequest(in NetworkRequest networkRequest);
+
+ int getRestoreDefaultNetworkDelay(int networkType);
+}
diff --git a/core/java/android/net/IpConfiguration.java b/core/java/android/net/IpConfiguration.java
new file mode 100644
index 0000000..4730bab
--- /dev/null
+++ b/core/java/android/net/IpConfiguration.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.net.LinkProperties;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * A class representing a configured network.
+ * @hide
+ */
+public class IpConfiguration implements Parcelable {
+ private static final String TAG = "IpConfiguration";
+
+ public enum IpAssignment {
+ /* Use statically configured IP settings. Configuration can be accessed
+ * with linkProperties */
+ STATIC,
+ /* Use dynamically configured IP settigns */
+ DHCP,
+ /* no IP details are assigned, this is used to indicate
+ * that any existing IP settings should be retained */
+ UNASSIGNED
+ }
+
+ public IpAssignment ipAssignment;
+
+ public enum ProxySettings {
+ /* No proxy is to be used. Any existing proxy settings
+ * should be cleared. */
+ NONE,
+ /* Use statically configured proxy. Configuration can be accessed
+ * with linkProperties */
+ STATIC,
+ /* no proxy details are assigned, this is used to indicate
+ * that any existing proxy settings should be retained */
+ UNASSIGNED,
+ /* Use a Pac based proxy.
+ */
+ PAC
+ }
+
+ public ProxySettings proxySettings;
+
+ public LinkProperties linkProperties;
+
+ public IpConfiguration(IpConfiguration source) {
+ if (source != null) {
+ ipAssignment = source.ipAssignment;
+ proxySettings = source.proxySettings;
+ linkProperties = new LinkProperties(source.linkProperties);
+ } else {
+ ipAssignment = IpAssignment.UNASSIGNED;
+ proxySettings = ProxySettings.UNASSIGNED;
+ linkProperties = new LinkProperties();
+ }
+ }
+
+ public IpConfiguration() {
+ this(null);
+ }
+
+ public IpConfiguration(IpAssignment ipAssignment,
+ ProxySettings proxySettings,
+ LinkProperties linkProperties) {
+ this.ipAssignment = ipAssignment;
+ this.proxySettings = proxySettings;
+ this.linkProperties = new LinkProperties(linkProperties);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sbuf = new StringBuilder();
+ sbuf.append("IP assignment: " + ipAssignment.toString());
+ sbuf.append("\n");
+ sbuf.append("Proxy settings: " + proxySettings.toString());
+ sbuf.append("\n");
+ sbuf.append(linkProperties.toString());
+ sbuf.append("\n");
+
+ return sbuf.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+
+ if (!(o instanceof IpConfiguration)) {
+ return false;
+ }
+
+ IpConfiguration other = (IpConfiguration) o;
+ return this.ipAssignment == other.ipAssignment &&
+ this.proxySettings == other.proxySettings &&
+ Objects.equals(this.linkProperties, other.linkProperties);
+ }
+
+ @Override
+ public int hashCode() {
+ return 13 + (linkProperties != null ? linkProperties.hashCode() : 0) +
+ 17 * ipAssignment.ordinal() +
+ 47 * proxySettings.ordinal();
+ }
+
+ /** Implement the Parcelable interface */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(ipAssignment.name());
+ dest.writeString(proxySettings.name());
+ dest.writeParcelable(linkProperties, flags);
+ }
+
+ /** Implement the Parcelable interface */
+ public static final Creator<IpConfiguration> CREATOR =
+ new Creator<IpConfiguration>() {
+ public IpConfiguration createFromParcel(Parcel in) {
+ IpConfiguration config = new IpConfiguration();
+ config.ipAssignment = IpAssignment.valueOf(in.readString());
+ config.proxySettings = ProxySettings.valueOf(in.readString());
+ config.linkProperties = in.readParcelable(null);
+ return config;
+ }
+
+ public IpConfiguration[] newArray(int size) {
+ return new IpConfiguration[size];
+ }
+ };
+}
diff --git a/core/java/android/net/IpPrefix.java b/core/java/android/net/IpPrefix.java
new file mode 100644
index 0000000..dfe0384
--- /dev/null
+++ b/core/java/android/net/IpPrefix.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2014 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.os.Parcel;
+import android.os.Parcelable;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+
+/**
+ * This class represents an IP prefix, i.e., a contiguous block of IP addresses aligned on a
+ * power of two boundary (also known as an "IP subnet"). A prefix is specified by two pieces of
+ * information:
+ *
+ * <ul>
+ * <li>A starting IP address (IPv4 or IPv6). This is the first IP address of the prefix.
+ * <li>A prefix length. This specifies the length of the prefix by specifing the number of bits
+ * in the IP address, starting from the most significant bit in network byte order, that
+ * are constant for all addresses in the prefix.
+ * </ul>
+ *
+ * For example, the prefix <code>192.0.2.0/24</code> covers the 256 IPv4 addresses from
+ * <code>192.0.2.0</code> to <code>192.0.2.255</code>, inclusive, and the prefix
+ * <code>2001:db8:1:2</code> covers the 2^64 IPv6 addresses from <code>2001:db8:1:2::</code> to
+ * <code>2001:db8:1:2:ffff:ffff:ffff:ffff</code>, inclusive.
+ *
+ * Objects of this class are immutable.
+ */
+public class IpPrefix implements Parcelable {
+ private final byte[] address; // network byte order
+ private final int prefixLength;
+
+ /**
+ * Constructs a new {@code IpPrefix} from a byte array containing an IPv4 or IPv6 address in
+ * network byte order and a prefix length.
+ *
+ * @param address the IP address. Must be non-null and exactly 4 or 16 bytes long.
+ * @param prefixLength the prefix length. Must be >= 0 and <= (32 or 128) (IPv4 or IPv6).
+ *
+ * @hide
+ */
+ public IpPrefix(byte[] address, int prefixLength) {
+ if (address.length != 4 && address.length != 16) {
+ throw new IllegalArgumentException(
+ "IpPrefix has " + address.length + " bytes which is neither 4 nor 16");
+ }
+ if (prefixLength < 0 || prefixLength > (address.length * 8)) {
+ throw new IllegalArgumentException("IpPrefix with " + address.length +
+ " bytes has invalid prefix length " + prefixLength);
+ }
+ this.address = address.clone();
+ this.prefixLength = prefixLength;
+ // TODO: Validate that the non-prefix bits are zero
+ }
+
+ /**
+ * @hide
+ */
+ public IpPrefix(InetAddress address, int prefixLength) {
+ this(address.getAddress(), prefixLength);
+ }
+
+ /**
+ * Compares this {@code IpPrefix} object against the specified object in {@code obj}. Two
+ * objects are equal if they have the same startAddress and prefixLength.
+ *
+ * @param obj the object to be tested for equality.
+ * @return {@code true} if both objects are equal, {@code false} otherwise.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof IpPrefix)) {
+ return false;
+ }
+ IpPrefix that = (IpPrefix) obj;
+ return Arrays.equals(this.address, that.address) && this.prefixLength == that.prefixLength;
+ }
+
+ /**
+ * Gets the hashcode of the represented IP prefix.
+ *
+ * @return the appropriate hashcode value.
+ */
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(address) + 11 * prefixLength;
+ }
+
+ /**
+ * Returns a copy of the first IP address in the prefix. Modifying the returned object does not
+ * change this object's contents.
+ *
+ * @return the address in the form of a byte array.
+ */
+ public InetAddress getAddress() {
+ try {
+ return InetAddress.getByAddress(address);
+ } catch (UnknownHostException e) {
+ // Cannot happen. InetAddress.getByAddress can only throw an exception if the byte
+ // array is the wrong length, but we check that in the constructor.
+ return null;
+ }
+ }
+
+ /**
+ * Returns a copy of the IP address bytes in network order (the highest order byte is the zeroth
+ * element). Modifying the returned array does not change this object's contents.
+ *
+ * @return the address in the form of a byte array.
+ */
+ public byte[] getRawAddress() {
+ return address.clone();
+ }
+
+ /**
+ * Returns the prefix length of this {@code IpAddress}.
+ *
+ * @return the prefix length.
+ */
+ public int getPrefixLength() {
+ return prefixLength;
+ }
+
+ /**
+ * Implement the Parcelable interface.
+ * @hide
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Implement the Parcelable interface.
+ * @hide
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeByteArray(address);
+ dest.writeInt(prefixLength);
+ }
+
+ /**
+ * Implement the Parcelable interface.
+ * @hide
+ */
+ public static final Creator<IpPrefix> CREATOR =
+ new Creator<IpPrefix>() {
+ public IpPrefix createFromParcel(Parcel in) {
+ byte[] address = in.createByteArray();
+ int prefixLength = in.readInt();
+ return new IpPrefix(address, prefixLength);
+ }
+
+ public IpPrefix[] newArray(int size) {
+ return new IpPrefix[size];
+ }
+ };
+}
diff --git a/core/java/android/net/LinkAddress.java b/core/java/android/net/LinkAddress.java
new file mode 100644
index 0000000..5246078
--- /dev/null
+++ b/core/java/android/net/LinkAddress.java
@@ -0,0 +1,332 @@
+/*
+ * Copyright (C) 2010 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.os.Parcel;
+import android.os.Parcelable;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.InterfaceAddress;
+import java.net.UnknownHostException;
+
+import static android.system.OsConstants.IFA_F_DADFAILED;
+import static android.system.OsConstants.IFA_F_DEPRECATED;
+import static android.system.OsConstants.IFA_F_TENTATIVE;
+import static android.system.OsConstants.RT_SCOPE_HOST;
+import static android.system.OsConstants.RT_SCOPE_LINK;
+import static android.system.OsConstants.RT_SCOPE_SITE;
+import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
+
+/**
+ * Identifies an IP address on a network link.
+ *
+ * A {@code LinkAddress} consists of:
+ * <ul>
+ * <li>An IP address and prefix length (e.g., {@code 2001:db8::1/64} or {@code 192.0.2.1/24}).
+ * The address must be unicast, as multicast addresses cannot be assigned to interfaces.
+ * <li>Address flags: A bitmask of {@code OsConstants.IFA_F_*} values representing properties
+ * of the address (e.g., {@code android.system.OsConstants.IFA_F_OPTIMISTIC}).
+ * <li>Address scope: One of the {@code OsConstants.IFA_F_*} values; defines the scope in which
+ * the address is unique (e.g.,
+ * {@code android.system.OsConstants.RT_SCOPE_LINK} or
+ * {@code android.system.OsConstants.RT_SCOPE_UNIVERSE}).
+ * </ul>
+ */
+public class LinkAddress implements Parcelable {
+ /**
+ * IPv4 or IPv6 address.
+ */
+ private InetAddress address;
+
+ /**
+ * Prefix length.
+ */
+ private int prefixLength;
+
+ /**
+ * Address flags. A bitmask of IFA_F_* values.
+ */
+ private int flags;
+
+ /**
+ * Address scope. One of the RT_SCOPE_* constants.
+ */
+ private int scope;
+
+ /**
+ * Utility function to determines the scope of a unicast address. Per RFC 4291 section 2.5 and
+ * RFC 6724 section 3.2.
+ * @hide
+ */
+ static int scopeForUnicastAddress(InetAddress addr) {
+ if (addr.isAnyLocalAddress()) {
+ return RT_SCOPE_HOST;
+ }
+
+ if (addr.isLoopbackAddress() || addr.isLinkLocalAddress()) {
+ return RT_SCOPE_LINK;
+ }
+
+ // isSiteLocalAddress() returns true for private IPv4 addresses, but RFC 6724 section 3.2
+ // says that they are assigned global scope.
+ if (!(addr instanceof Inet4Address) && addr.isSiteLocalAddress()) {
+ return RT_SCOPE_SITE;
+ }
+
+ return RT_SCOPE_UNIVERSE;
+ }
+
+ /**
+ * Utility function for the constructors.
+ */
+ private void init(InetAddress address, int prefixLength, int flags, int scope) {
+ if (address == null ||
+ address.isMulticastAddress() ||
+ prefixLength < 0 ||
+ ((address instanceof Inet4Address) && prefixLength > 32) ||
+ (prefixLength > 128)) {
+ throw new IllegalArgumentException("Bad LinkAddress params " + address +
+ "/" + prefixLength);
+ }
+ this.address = address;
+ this.prefixLength = prefixLength;
+ this.flags = flags;
+ this.scope = scope;
+ }
+
+ /**
+ * Constructs a new {@code LinkAddress} from an {@code InetAddress} and prefix length, with
+ * the specified flags and scope. Flags and scope are not checked for validity.
+ * @param address The IP address.
+ * @param prefixLength The prefix length.
+ * @param flags A bitmask of {@code IFA_F_*} values representing properties of the address.
+ * @param scope An integer defining the scope in which the address is unique (e.g.,
+ * {@link OsConstants#RT_SCOPE_LINK} or {@link OsConstants#RT_SCOPE_SITE}).
+ * @hide
+ */
+ public LinkAddress(InetAddress address, int prefixLength, int flags, int scope) {
+ init(address, prefixLength, flags, scope);
+ }
+
+ /**
+ * Constructs a new {@code LinkAddress} from an {@code InetAddress} and a prefix length.
+ * The flags are set to zero and the scope is determined from the address.
+ * @param address The IP address.
+ * @param prefixLength The prefix length.
+ * @hide
+ */
+ public LinkAddress(InetAddress address, int prefixLength) {
+ this(address, prefixLength, 0, 0);
+ this.scope = scopeForUnicastAddress(address);
+ }
+
+ /**
+ * Constructs a new {@code LinkAddress} from an {@code InterfaceAddress}.
+ * The flags are set to zero and the scope is determined from the address.
+ * @param interfaceAddress The interface address.
+ * @hide
+ */
+ public LinkAddress(InterfaceAddress interfaceAddress) {
+ this(interfaceAddress.getAddress(),
+ interfaceAddress.getNetworkPrefixLength());
+ }
+
+ /**
+ * Constructs a new {@code LinkAddress} from a string such as "192.0.2.5/24" or
+ * "2001:db8::1/64". The flags are set to zero and the scope is determined from the address.
+ * @param string The string to parse.
+ * @hide
+ */
+ public LinkAddress(String address) {
+ this(address, 0, 0);
+ this.scope = scopeForUnicastAddress(this.address);
+ }
+
+ /**
+ * Constructs a new {@code LinkAddress} from a string such as "192.0.2.5/24" or
+ * "2001:db8::1/64", with the specified flags and scope.
+ * @param string The string to parse.
+ * @param flags The address flags.
+ * @param scope The address scope.
+ * @hide
+ */
+ public LinkAddress(String address, int flags, int scope) {
+ InetAddress inetAddress = null;
+ int prefixLength = -1;
+ try {
+ String [] pieces = address.split("/", 2);
+ prefixLength = Integer.parseInt(pieces[1]);
+ inetAddress = InetAddress.parseNumericAddress(pieces[0]);
+ } catch (NullPointerException e) { // Null string.
+ } catch (ArrayIndexOutOfBoundsException e) { // No prefix length.
+ } catch (NumberFormatException e) { // Non-numeric prefix.
+ } catch (IllegalArgumentException e) { // Invalid IP address.
+ }
+
+ if (inetAddress == null || prefixLength == -1) {
+ throw new IllegalArgumentException("Bad LinkAddress params " + address);
+ }
+
+ init(inetAddress, prefixLength, flags, scope);
+ }
+
+ /**
+ * Returns a string representation of this address, such as "192.0.2.1/24" or "2001:db8::1/64".
+ * The string representation does not contain the flags and scope, just the address and prefix
+ * length.
+ */
+ @Override
+ public String toString() {
+ return address.getHostAddress() + "/" + prefixLength;
+ }
+
+ /**
+ * Compares this {@code LinkAddress} instance against {@code obj}. Two addresses are equal if
+ * their address, prefix length, flags and scope are equal. Thus, for example, two addresses
+ * that have the same address and prefix length are not equal if one of them is deprecated and
+ * the other is not.
+ *
+ * @param obj the object to be tested for equality.
+ * @return {@code true} if both objects are equal, {@code false} otherwise.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof LinkAddress)) {
+ return false;
+ }
+ LinkAddress linkAddress = (LinkAddress) obj;
+ return this.address.equals(linkAddress.address) &&
+ this.prefixLength == linkAddress.prefixLength &&
+ this.flags == linkAddress.flags &&
+ this.scope == linkAddress.scope;
+ }
+
+ /**
+ * Returns a hashcode for this address.
+ */
+ @Override
+ public int hashCode() {
+ return address.hashCode() + 11 * prefixLength + 19 * flags + 43 * scope;
+ }
+
+ /**
+ * Determines whether this {@code LinkAddress} and the provided {@code LinkAddress}
+ * represent the same address. Two {@code LinkAddresses} represent the same address
+ * if they have the same IP address and prefix length, even if their properties are
+ * different.
+ *
+ * @param other the {@code LinkAddress} to compare to.
+ * @return {@code true} if both objects have the same address and prefix length, {@code false}
+ * otherwise.
+ * @hide
+ */
+ public boolean isSameAddressAs(LinkAddress other) {
+ return address.equals(other.address) && prefixLength == other.prefixLength;
+ }
+
+ /**
+ * Returns the {@link InetAddress} of this {@code LinkAddress}.
+ */
+ public InetAddress getAddress() {
+ return address;
+ }
+
+ /**
+ * Returns the prefix length of this {@code LinkAddress}.
+ */
+ public int getPrefixLength() {
+ return prefixLength;
+ }
+
+ /**
+ * Returns the prefix length of this {@code LinkAddress}.
+ * TODO: Delete all callers and remove in favour of getPrefixLength().
+ * @hide
+ */
+ public int getNetworkPrefixLength() {
+ return getPrefixLength();
+ }
+
+ /**
+ * Returns the flags of this {@code LinkAddress}.
+ */
+ public int getFlags() {
+ return flags;
+ }
+
+ /**
+ * Returns the scope of this {@code LinkAddress}.
+ */
+ public int getScope() {
+ return scope;
+ }
+
+ /**
+ * Returns true if this {@code LinkAddress} is global scope and preferred.
+ * @hide
+ */
+ public boolean isGlobalPreferred() {
+ return (scope == RT_SCOPE_UNIVERSE &&
+ (flags & (IFA_F_DADFAILED | IFA_F_DEPRECATED | IFA_F_TENTATIVE)) == 0L);
+ }
+
+ /**
+ * Implement the Parcelable interface.
+ * @hide
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Implement the Parcelable interface.
+ * @hide
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeByteArray(address.getAddress());
+ dest.writeInt(prefixLength);
+ dest.writeInt(this.flags);
+ dest.writeInt(scope);
+ }
+
+ /**
+ * Implement the Parcelable interface.
+ * @hide
+ */
+ public static final Creator<LinkAddress> CREATOR =
+ new Creator<LinkAddress>() {
+ public LinkAddress createFromParcel(Parcel in) {
+ InetAddress address = null;
+ try {
+ address = InetAddress.getByAddress(in.createByteArray());
+ } catch (UnknownHostException e) {
+ // Nothing we can do here. When we call the constructor, we'll throw an
+ // IllegalArgumentException, because a LinkAddress can't have a null
+ // InetAddress.
+ }
+ int prefixLength = in.readInt();
+ int flags = in.readInt();
+ int scope = in.readInt();
+ return new LinkAddress(address, prefixLength, flags, scope);
+ }
+
+ public LinkAddress[] newArray(int size) {
+ return new LinkAddress[size];
+ }
+ };
+}
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
new file mode 100644
index 0000000..bb05936
--- /dev/null
+++ b/core/java/android/net/LinkProperties.java
@@ -0,0 +1,880 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.net.ProxyInfo;
+import android.os.Parcelable;
+import android.os.Parcel;
+import android.text.TextUtils;
+
+import java.net.InetAddress;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Hashtable;
+import java.util.List;
+
+/**
+ * Describes the properties of a network link.
+ *
+ * A link represents a connection to a network.
+ * It may have multiple addresses and multiple gateways,
+ * multiple dns servers but only one http proxy and one
+ * network interface.
+ *
+ * Note that this is just a holder of data. Modifying it
+ * does not affect live networks.
+ *
+ */
+public class LinkProperties implements Parcelable {
+ // The interface described by the network link.
+ private String mIfaceName;
+ private ArrayList<LinkAddress> mLinkAddresses = new ArrayList<LinkAddress>();
+ private ArrayList<InetAddress> mDnses = new ArrayList<InetAddress>();
+ private String mDomains;
+ private ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
+ private ProxyInfo mHttpProxy;
+ private int mMtu;
+
+ // Stores the properties of links that are "stacked" above this link.
+ // Indexed by interface name to allow modification and to prevent duplicates being added.
+ private Hashtable<String, LinkProperties> mStackedLinks =
+ new Hashtable<String, LinkProperties>();
+
+ /**
+ * @hide
+ */
+ public static class CompareResult<T> {
+ public List<T> removed = new ArrayList<T>();
+ public List<T> added = new ArrayList<T>();
+
+ @Override
+ public String toString() {
+ String retVal = "removed=[";
+ for (T addr : removed) retVal += addr.toString() + ",";
+ retVal += "] added=[";
+ for (T addr : added) retVal += addr.toString() + ",";
+ retVal += "]";
+ return retVal;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public LinkProperties() {
+ }
+
+ /**
+ * @hide
+ */
+ public LinkProperties(LinkProperties source) {
+ if (source != null) {
+ mIfaceName = source.getInterfaceName();
+ for (LinkAddress l : source.getLinkAddresses()) mLinkAddresses.add(l);
+ for (InetAddress i : source.getDnsServers()) mDnses.add(i);
+ mDomains = source.getDomains();
+ for (RouteInfo r : source.getRoutes()) mRoutes.add(r);
+ mHttpProxy = (source.getHttpProxy() == null) ?
+ null : new ProxyInfo(source.getHttpProxy());
+ for (LinkProperties l: source.mStackedLinks.values()) {
+ addStackedLink(l);
+ }
+ setMtu(source.getMtu());
+ }
+ }
+
+ /**
+ * Sets the interface name for this link. All {@link RouteInfo} already set for this
+ * will have their interface changed to match this new value.
+ *
+ * @param iface The name of the network interface used for this link.
+ * @hide
+ */
+ public void setInterfaceName(String iface) {
+ mIfaceName = iface;
+ ArrayList<RouteInfo> newRoutes = new ArrayList<RouteInfo>(mRoutes.size());
+ for (RouteInfo route : mRoutes) {
+ newRoutes.add(routeWithInterface(route));
+ }
+ mRoutes = newRoutes;
+ }
+
+ /**
+ * Gets the interface name for this link. May be {@code null} if not set.
+ *
+ * @return The interface name set for this link or {@code null}.
+ */
+ public String getInterfaceName() {
+ return mIfaceName;
+ }
+
+ /**
+ * @hide
+ */
+ public List<String> getAllInterfaceNames() {
+ List<String> interfaceNames = new ArrayList<String>(mStackedLinks.size() + 1);
+ if (mIfaceName != null) interfaceNames.add(new String(mIfaceName));
+ for (LinkProperties stacked: mStackedLinks.values()) {
+ interfaceNames.addAll(stacked.getAllInterfaceNames());
+ }
+ return interfaceNames;
+ }
+
+ /**
+ * Returns all the addresses on this link. We often think of a link having a single address,
+ * however, particularly with Ipv6 several addresses are typical. Note that the
+ * {@code LinkProperties} actually contains {@link LinkAddress} objects which also include
+ * prefix lengths for each address. This is a simplified utility alternative to
+ * {@link LinkProperties#getLinkAddresses}.
+ *
+ * @return An umodifiable {@link List} of {@link InetAddress} for this link.
+ * @hide
+ */
+ public List<InetAddress> getAddresses() {
+ List<InetAddress> addresses = new ArrayList<InetAddress>();
+ for (LinkAddress linkAddress : mLinkAddresses) {
+ addresses.add(linkAddress.getAddress());
+ }
+ return Collections.unmodifiableList(addresses);
+ }
+
+ /**
+ * Returns all the addresses on this link and all the links stacked above it.
+ * @hide
+ */
+ public List<InetAddress> getAllAddresses() {
+ List<InetAddress> addresses = new ArrayList<InetAddress>();
+ for (LinkAddress linkAddress : mLinkAddresses) {
+ addresses.add(linkAddress.getAddress());
+ }
+ for (LinkProperties stacked: mStackedLinks.values()) {
+ addresses.addAll(stacked.getAllAddresses());
+ }
+ return addresses;
+ }
+
+ private int findLinkAddressIndex(LinkAddress address) {
+ for (int i = 0; i < mLinkAddresses.size(); i++) {
+ if (mLinkAddresses.get(i).isSameAddressAs(address)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Adds a {@link LinkAddress} to this {@code LinkProperties} if a {@link LinkAddress} of the
+ * same address/prefix does not already exist. If it does exist it is replaced.
+ * @param address The {@code LinkAddress} to add.
+ * @return true if {@code address} was added or updated, false otherwise.
+ * @hide
+ */
+ public boolean addLinkAddress(LinkAddress address) {
+ if (address == null) {
+ return false;
+ }
+ int i = findLinkAddressIndex(address);
+ if (i < 0) {
+ // Address was not present. Add it.
+ mLinkAddresses.add(address);
+ return true;
+ } else if (mLinkAddresses.get(i).equals(address)) {
+ // Address was present and has same properties. Do nothing.
+ return false;
+ } else {
+ // Address was present and has different properties. Update it.
+ mLinkAddresses.set(i, address);
+ return true;
+ }
+ }
+
+ /**
+ * Removes a {@link LinkAddress} from this {@code LinkProperties}. Specifically, matches
+ * and {@link LinkAddress} with the same address and prefix.
+ *
+ * @param toRemove A {@link LinkAddress} specifying the address to remove.
+ * @return true if the address was removed, false if it did not exist.
+ * @hide
+ */
+ public boolean removeLinkAddress(LinkAddress toRemove) {
+ int i = findLinkAddressIndex(toRemove);
+ if (i >= 0) {
+ mLinkAddresses.remove(i);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns all the {@link LinkAddress} on this link. Typically a link will have
+ * one IPv4 address and one or more IPv6 addresses.
+ *
+ * @return An unmodifiable {@link List} of {@link LinkAddress} for this link.
+ */
+ public List<LinkAddress> getLinkAddresses() {
+ return Collections.unmodifiableList(mLinkAddresses);
+ }
+
+ /**
+ * Returns all the addresses on this link and all the links stacked above it.
+ * @hide
+ */
+ public List<LinkAddress> getAllLinkAddresses() {
+ List<LinkAddress> addresses = new ArrayList<LinkAddress>();
+ addresses.addAll(mLinkAddresses);
+ for (LinkProperties stacked: mStackedLinks.values()) {
+ addresses.addAll(stacked.getAllLinkAddresses());
+ }
+ return addresses;
+ }
+
+ /**
+ * Replaces the {@link LinkAddress} in this {@code LinkProperties} with
+ * the given {@link Collection} of {@link LinkAddress}.
+ *
+ * @param addresses The {@link Collection} of {@link LinkAddress} to set in this
+ * object.
+ * @hide
+ */
+ public void setLinkAddresses(Collection<LinkAddress> addresses) {
+ mLinkAddresses.clear();
+ for (LinkAddress address: addresses) {
+ addLinkAddress(address);
+ }
+ }
+
+ /**
+ * Adds the given {@link InetAddress} to the list of DNS servers.
+ *
+ * @param dnsServer The {@link InetAddress} to add to the list of DNS servers.
+ * @hide
+ */
+ public void addDnsServer(InetAddress dnsServer) {
+ if (dnsServer != null) mDnses.add(dnsServer);
+ }
+
+ /**
+ * Returns all the {@link InetAddress} for DNS servers on this link.
+ *
+ * @return An umodifiable {@link List} of {@link InetAddress} for DNS servers on
+ * this link.
+ */
+ public List<InetAddress> getDnsServers() {
+ return Collections.unmodifiableList(mDnses);
+ }
+
+ /**
+ * Sets the DNS domain search path used on this link.
+ *
+ * @param domains A {@link String} listing in priority order the comma separated
+ * domains to search when resolving host names on this link.
+ * @hide
+ */
+ public void setDomains(String domains) {
+ mDomains = domains;
+ }
+
+ /**
+ * Get the DNS domains search path set for this link.
+ *
+ * @return A {@link String} containing the comma separated domains to search when resolving
+ * host names on this link.
+ */
+ public String getDomains() {
+ return mDomains;
+ }
+
+ /**
+ * Sets the Maximum Transmission Unit size to use on this link. This should not be used
+ * unless the system default (1500) is incorrect. Values less than 68 or greater than
+ * 10000 will be ignored.
+ *
+ * @param mtu The MTU to use for this link.
+ * @hide
+ */
+ public void setMtu(int mtu) {
+ mMtu = mtu;
+ }
+
+ /**
+ * Gets any non-default MTU size set for this link. Note that if the default is being used
+ * this will return 0.
+ *
+ * @return The mtu value set for this link.
+ * @hide
+ */
+ public int getMtu() {
+ return mMtu;
+ }
+
+ private RouteInfo routeWithInterface(RouteInfo route) {
+ return new RouteInfo(
+ route.getDestination(),
+ route.getGateway(),
+ mIfaceName);
+ }
+
+ /**
+ * Adds a {@link RouteInfo} to this {@code LinkProperties}. If the {@link RouteInfo}
+ * had an interface name set and that differs from the interface set for this
+ * {@code LinkProperties} an {@link IllegalArgumentException} will be thrown. The
+ * proper course is to add either un-named or properly named {@link RouteInfo}.
+ *
+ * @param route A {@link RouteInfo} to add to this object.
+ * @hide
+ */
+ public void addRoute(RouteInfo route) {
+ if (route != null) {
+ String routeIface = route.getInterface();
+ if (routeIface != null && !routeIface.equals(mIfaceName)) {
+ throw new IllegalArgumentException(
+ "Route added with non-matching interface: " + routeIface +
+ " vs. " + mIfaceName);
+ }
+ mRoutes.add(routeWithInterface(route));
+ }
+ }
+
+ /**
+ * Returns all the {@link RouteInfo} set on this link.
+ *
+ * @return An unmodifiable {@link List} of {@link RouteInfo} for this link.
+ */
+ public List<RouteInfo> getRoutes() {
+ return Collections.unmodifiableList(mRoutes);
+ }
+
+ /**
+ * Returns all the routes on this link and all the links stacked above it.
+ * @hide
+ */
+ public List<RouteInfo> getAllRoutes() {
+ List<RouteInfo> routes = new ArrayList();
+ routes.addAll(mRoutes);
+ for (LinkProperties stacked: mStackedLinks.values()) {
+ routes.addAll(stacked.getAllRoutes());
+ }
+ return routes;
+ }
+
+ /**
+ * Sets the recommended {@link ProxyInfo} to use on this link, or {@code null} for none.
+ * Note that Http Proxies are only a hint - the system recommends their use, but it does
+ * not enforce it and applications may ignore them.
+ *
+ * @param proxy A {@link ProxyInfo} defining the Http Proxy to use on this link.
+ * @hide
+ */
+ public void setHttpProxy(ProxyInfo proxy) {
+ mHttpProxy = proxy;
+ }
+
+ /**
+ * Gets the recommended {@link ProxyInfo} (or {@code null}) set on this link.
+ *
+ * @return The {@link ProxyInfo} set on this link
+ */
+ public ProxyInfo getHttpProxy() {
+ return mHttpProxy;
+ }
+
+ /**
+ * Adds a stacked link.
+ *
+ * If there is already a stacked link with the same interfacename as link,
+ * that link is replaced with link. Otherwise, link is added to the list
+ * of stacked links. If link is null, nothing changes.
+ *
+ * @param link The link to add.
+ * @return true if the link was stacked, false otherwise.
+ * @hide
+ */
+ public boolean addStackedLink(LinkProperties link) {
+ if (link != null && link.getInterfaceName() != null) {
+ mStackedLinks.put(link.getInterfaceName(), link);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Removes a stacked link.
+ *
+ * If there a stacked link with the same interfacename as link, it is
+ * removed. Otherwise, nothing changes.
+ *
+ * @param link The link to remove.
+ * @return true if the link was removed, false otherwise.
+ * @hide
+ */
+ public boolean removeStackedLink(LinkProperties link) {
+ if (link != null && link.getInterfaceName() != null) {
+ LinkProperties removed = mStackedLinks.remove(link.getInterfaceName());
+ return removed != null;
+ }
+ return false;
+ }
+
+ /**
+ * Returns all the links stacked on top of this link.
+ * @hide
+ */
+ public List<LinkProperties> getStackedLinks() {
+ List<LinkProperties> stacked = new ArrayList<LinkProperties>();
+ for (LinkProperties link : mStackedLinks.values()) {
+ stacked.add(new LinkProperties(link));
+ }
+ return Collections.unmodifiableList(stacked);
+ }
+
+ /**
+ * Clears this object to its initial state.
+ * @hide
+ */
+ public void clear() {
+ mIfaceName = null;
+ mLinkAddresses.clear();
+ mDnses.clear();
+ mDomains = null;
+ mRoutes.clear();
+ mHttpProxy = null;
+ mStackedLinks.clear();
+ mMtu = 0;
+ }
+
+ /**
+ * Implement the Parcelable interface
+ * @hide
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ String ifaceName = (mIfaceName == null ? "" : "InterfaceName: " + mIfaceName + " ");
+
+ String linkAddresses = "LinkAddresses: [";
+ for (LinkAddress addr : mLinkAddresses) linkAddresses += addr.toString() + ",";
+ linkAddresses += "] ";
+
+ String dns = "DnsAddresses: [";
+ for (InetAddress addr : mDnses) dns += addr.getHostAddress() + ",";
+ dns += "] ";
+
+ String domainName = "Domains: " + mDomains;
+
+ String mtu = " MTU: " + mMtu;
+
+ String routes = " Routes: [";
+ for (RouteInfo route : mRoutes) routes += route.toString() + ",";
+ routes += "] ";
+ String proxy = (mHttpProxy == null ? "" : " HttpProxy: " + mHttpProxy.toString() + " ");
+
+ String stacked = "";
+ if (mStackedLinks.values().size() > 0) {
+ stacked += " Stacked: [";
+ for (LinkProperties link: mStackedLinks.values()) {
+ stacked += " [" + link.toString() + " ],";
+ }
+ stacked += "] ";
+ }
+ return "{" + ifaceName + linkAddresses + routes + dns + domainName + mtu
+ + proxy + stacked + "}";
+ }
+
+ /**
+ * Returns true if this link has an IPv4 address.
+ *
+ * @return {@code true} if there is an IPv4 address, {@code false} otherwise.
+ * @hide
+ */
+ public boolean hasIPv4Address() {
+ for (LinkAddress address : mLinkAddresses) {
+ if (address.getAddress() instanceof Inet4Address) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if this link has an IPv6 address.
+ *
+ * @return {@code true} if there is an IPv6 address, {@code false} otherwise.
+ * @hide
+ */
+ public boolean hasIPv6Address() {
+ for (LinkAddress address : mLinkAddresses) {
+ if (address.getAddress() instanceof Inet6Address) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Compares this {@code LinkProperties} interface name against the target
+ *
+ * @param target LinkProperties to compare.
+ * @return {@code true} if both are identical, {@code false} otherwise.
+ * @hide
+ */
+ public boolean isIdenticalInterfaceName(LinkProperties target) {
+ return TextUtils.equals(getInterfaceName(), target.getInterfaceName());
+ }
+
+ /**
+ * Compares this {@code LinkProperties} interface addresses against the target
+ *
+ * @param target LinkProperties to compare.
+ * @return {@code true} if both are identical, {@code false} otherwise.
+ * @hide
+ */
+ public boolean isIdenticalAddresses(LinkProperties target) {
+ Collection<InetAddress> targetAddresses = target.getAddresses();
+ Collection<InetAddress> sourceAddresses = getAddresses();
+ return (sourceAddresses.size() == targetAddresses.size()) ?
+ sourceAddresses.containsAll(targetAddresses) : false;
+ }
+
+ /**
+ * Compares this {@code LinkProperties} DNS addresses against the target
+ *
+ * @param target LinkProperties to compare.
+ * @return {@code true} if both are identical, {@code false} otherwise.
+ * @hide
+ */
+ public boolean isIdenticalDnses(LinkProperties target) {
+ Collection<InetAddress> targetDnses = target.getDnsServers();
+ String targetDomains = target.getDomains();
+ if (mDomains == null) {
+ if (targetDomains != null) return false;
+ } else {
+ if (mDomains.equals(targetDomains) == false) return false;
+ }
+ return (mDnses.size() == targetDnses.size()) ?
+ mDnses.containsAll(targetDnses) : false;
+ }
+
+ /**
+ * Compares this {@code LinkProperties} Routes against the target
+ *
+ * @param target LinkProperties to compare.
+ * @return {@code true} if both are identical, {@code false} otherwise.
+ * @hide
+ */
+ public boolean isIdenticalRoutes(LinkProperties target) {
+ Collection<RouteInfo> targetRoutes = target.getRoutes();
+ return (mRoutes.size() == targetRoutes.size()) ?
+ mRoutes.containsAll(targetRoutes) : false;
+ }
+
+ /**
+ * Compares this {@code LinkProperties} HttpProxy against the target
+ *
+ * @param target LinkProperties to compare.
+ * @return {@code true} if both are identical, {@code false} otherwise.
+ * @hide
+ */
+ public boolean isIdenticalHttpProxy(LinkProperties target) {
+ return getHttpProxy() == null ? target.getHttpProxy() == null :
+ getHttpProxy().equals(target.getHttpProxy());
+ }
+
+ /**
+ * Compares this {@code LinkProperties} stacked links against the target
+ *
+ * @param target LinkProperties to compare.
+ * @return {@code true} if both are identical, {@code false} otherwise.
+ * @hide
+ */
+ public boolean isIdenticalStackedLinks(LinkProperties target) {
+ if (!mStackedLinks.keySet().equals(target.mStackedLinks.keySet())) {
+ return false;
+ }
+ for (LinkProperties stacked : mStackedLinks.values()) {
+ // Hashtable values can never be null.
+ String iface = stacked.getInterfaceName();
+ if (!stacked.equals(target.mStackedLinks.get(iface))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Compares this {@code LinkProperties} MTU against the target
+ *
+ * @param target LinkProperties to compare.
+ * @return {@code true} if both are identical, {@code false} otherwise.
+ * @hide
+ */
+ public boolean isIdenticalMtu(LinkProperties target) {
+ return getMtu() == target.getMtu();
+ }
+
+ @Override
+ /**
+ * Compares this {@code LinkProperties} instance against the target
+ * LinkProperties in {@code obj}. Two LinkPropertieses are equal if
+ * all their fields are equal in values.
+ *
+ * For collection fields, such as mDnses, containsAll() is used to check
+ * if two collections contains the same elements, independent of order.
+ * There are two thoughts regarding containsAll()
+ * 1. Duplicated elements. eg, (A, B, B) and (A, A, B) are equal.
+ * 2. Worst case performance is O(n^2).
+ *
+ * @param obj the object to be tested for equality.
+ * @return {@code true} if both objects are equal, {@code false} otherwise.
+ */
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+
+ if (!(obj instanceof LinkProperties)) return false;
+
+ LinkProperties target = (LinkProperties) obj;
+ /**
+ * This method does not check that stacked interfaces are equal, because
+ * stacked interfaces are not so much a property of the link as a
+ * description of connections between links.
+ */
+ return isIdenticalInterfaceName(target) &&
+ isIdenticalAddresses(target) &&
+ isIdenticalDnses(target) &&
+ isIdenticalRoutes(target) &&
+ isIdenticalHttpProxy(target) &&
+ isIdenticalStackedLinks(target) &&
+ isIdenticalMtu(target);
+ }
+
+ /**
+ * Compares the addresses in this LinkProperties with another
+ * LinkProperties, examining only addresses on the base link.
+ *
+ * @param target a LinkProperties with the new list of addresses
+ * @return the differences between the addresses.
+ * @hide
+ */
+ public CompareResult<LinkAddress> compareAddresses(LinkProperties target) {
+ /*
+ * Duplicate the LinkAddresses into removed, we will be removing
+ * address which are common between mLinkAddresses and target
+ * leaving the addresses that are different. And address which
+ * are in target but not in mLinkAddresses are placed in the
+ * addedAddresses.
+ */
+ CompareResult<LinkAddress> result = new CompareResult<LinkAddress>();
+ result.removed = new ArrayList<LinkAddress>(mLinkAddresses);
+ result.added.clear();
+ if (target != null) {
+ for (LinkAddress newAddress : target.getLinkAddresses()) {
+ if (! result.removed.remove(newAddress)) {
+ result.added.add(newAddress);
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Compares the DNS addresses in this LinkProperties with another
+ * LinkProperties, examining only DNS addresses on the base link.
+ *
+ * @param target a LinkProperties with the new list of dns addresses
+ * @return the differences between the DNS addresses.
+ * @hide
+ */
+ public CompareResult<InetAddress> compareDnses(LinkProperties target) {
+ /*
+ * Duplicate the InetAddresses into removed, we will be removing
+ * dns address which are common between mDnses and target
+ * leaving the addresses that are different. And dns address which
+ * are in target but not in mDnses are placed in the
+ * addedAddresses.
+ */
+ CompareResult<InetAddress> result = new CompareResult<InetAddress>();
+
+ result.removed = new ArrayList<InetAddress>(mDnses);
+ result.added.clear();
+ if (target != null) {
+ for (InetAddress newAddress : target.getDnsServers()) {
+ if (! result.removed.remove(newAddress)) {
+ result.added.add(newAddress);
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Compares all routes in this LinkProperties with another LinkProperties,
+ * examining both the the base link and all stacked links.
+ *
+ * @param target a LinkProperties with the new list of routes
+ * @return the differences between the routes.
+ * @hide
+ */
+ public CompareResult<RouteInfo> compareAllRoutes(LinkProperties target) {
+ /*
+ * Duplicate the RouteInfos into removed, we will be removing
+ * routes which are common between mRoutes and target
+ * leaving the routes that are different. And route address which
+ * are in target but not in mRoutes are placed in added.
+ */
+ CompareResult<RouteInfo> result = new CompareResult<RouteInfo>();
+
+ result.removed = getAllRoutes();
+ result.added.clear();
+ if (target != null) {
+ for (RouteInfo r : target.getAllRoutes()) {
+ if (! result.removed.remove(r)) {
+ result.added.add(r);
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Compares all interface names in this LinkProperties with another
+ * LinkProperties, examining both the the base link and all stacked links.
+ *
+ * @param target a LinkProperties with the new list of interface names
+ * @return the differences between the interface names.
+ * @hide
+ */
+ public CompareResult<String> compareAllInterfaceNames(LinkProperties target) {
+ /*
+ * Duplicate the interface names into removed, we will be removing
+ * interface names which are common between this and target
+ * leaving the interface names that are different. And interface names which
+ * are in target but not in this are placed in added.
+ */
+ CompareResult<String> result = new CompareResult<String>();
+
+ result.removed = getAllInterfaceNames();
+ result.added.clear();
+ if (target != null) {
+ for (String r : target.getAllInterfaceNames()) {
+ if (! result.removed.remove(r)) {
+ result.added.add(r);
+ }
+ }
+ }
+ return result;
+ }
+
+
+ @Override
+ /**
+ * generate hashcode based on significant fields
+ * Equal objects must produce the same hash code, while unequal objects
+ * may have the same hash codes.
+ */
+ public int hashCode() {
+ return ((null == mIfaceName) ? 0 : mIfaceName.hashCode()
+ + mLinkAddresses.size() * 31
+ + mDnses.size() * 37
+ + ((null == mDomains) ? 0 : mDomains.hashCode())
+ + mRoutes.size() * 41
+ + ((null == mHttpProxy) ? 0 : mHttpProxy.hashCode())
+ + mStackedLinks.hashCode() * 47)
+ + mMtu * 51;
+ }
+
+ /**
+ * Implement the Parcelable interface.
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(getInterfaceName());
+ dest.writeInt(mLinkAddresses.size());
+ for(LinkAddress linkAddress : mLinkAddresses) {
+ dest.writeParcelable(linkAddress, flags);
+ }
+
+ dest.writeInt(mDnses.size());
+ for(InetAddress d : mDnses) {
+ dest.writeByteArray(d.getAddress());
+ }
+ dest.writeString(mDomains);
+ dest.writeInt(mMtu);
+ dest.writeInt(mRoutes.size());
+ for(RouteInfo route : mRoutes) {
+ dest.writeParcelable(route, flags);
+ }
+
+ if (mHttpProxy != null) {
+ dest.writeByte((byte)1);
+ dest.writeParcelable(mHttpProxy, flags);
+ } else {
+ dest.writeByte((byte)0);
+ }
+ ArrayList<LinkProperties> stackedLinks = new ArrayList(mStackedLinks.values());
+ dest.writeList(stackedLinks);
+ }
+
+ /**
+ * Implement the Parcelable interface.
+ */
+ public static final Creator<LinkProperties> CREATOR =
+ new Creator<LinkProperties>() {
+ public LinkProperties createFromParcel(Parcel in) {
+ LinkProperties netProp = new LinkProperties();
+
+ String iface = in.readString();
+ if (iface != null) {
+ netProp.setInterfaceName(iface);
+ }
+ int addressCount = in.readInt();
+ for (int i=0; i<addressCount; i++) {
+ netProp.addLinkAddress((LinkAddress)in.readParcelable(null));
+ }
+ addressCount = in.readInt();
+ for (int i=0; i<addressCount; i++) {
+ try {
+ netProp.addDnsServer(InetAddress.getByAddress(in.createByteArray()));
+ } catch (UnknownHostException e) { }
+ }
+ netProp.setDomains(in.readString());
+ netProp.setMtu(in.readInt());
+ addressCount = in.readInt();
+ for (int i=0; i<addressCount; i++) {
+ netProp.addRoute((RouteInfo)in.readParcelable(null));
+ }
+ if (in.readByte() == 1) {
+ netProp.setHttpProxy((ProxyInfo)in.readParcelable(null));
+ }
+ ArrayList<LinkProperties> stackedLinks = new ArrayList<LinkProperties>();
+ in.readList(stackedLinks, LinkProperties.class.getClassLoader());
+ for (LinkProperties stackedLink: stackedLinks) {
+ netProp.addStackedLink(stackedLink);
+ }
+ return netProp;
+ }
+
+ public LinkProperties[] newArray(int size) {
+ return new LinkProperties[size];
+ }
+ };
+}
diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java
new file mode 100644
index 0000000..d933f26
--- /dev/null
+++ b/core/java/android/net/Network.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.net.NetworkUtils;
+import android.os.Parcelable;
+import android.os.Parcel;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import javax.net.SocketFactory;
+
+/**
+ * Identifies a {@code Network}. This is supplied to applications via
+ * {@link ConnectivityManager.NetworkCallbackListener} in response to
+ * {@link ConnectivityManager#requestNetwork} or {@link ConnectivityManager#listenForNetwork}.
+ * It is used to direct traffic to the given {@code Network}, either on a {@link Socket} basis
+ * through a targeted {@link SocketFactory} or process-wide via
+ * {@link ConnectivityManager#setProcessDefaultNetwork}.
+ */
+public class Network implements Parcelable {
+
+ /**
+ * @hide
+ */
+ public final int netId;
+
+ private NetworkBoundSocketFactory mNetworkBoundSocketFactory = null;
+
+ /**
+ * @hide
+ */
+ public Network(int netId) {
+ this.netId = netId;
+ }
+
+ /**
+ * @hide
+ */
+ public Network(Network that) {
+ this.netId = that.netId;
+ }
+
+ /**
+ * Operates the same as {@code InetAddress.getAllByName} except that host
+ * resolution is done on this network.
+ *
+ * @param host the hostname or literal IP string to be resolved.
+ * @return the array of addresses associated with the specified host.
+ * @throws UnknownHostException if the address lookup fails.
+ */
+ public InetAddress[] getAllByName(String host) throws UnknownHostException {
+ return InetAddress.getAllByNameOnNet(host, netId);
+ }
+
+ /**
+ * Operates the same as {@code InetAddress.getByName} except that host
+ * resolution is done on this network.
+ *
+ * @param host
+ * the hostName to be resolved to an address or {@code null}.
+ * @return the {@code InetAddress} instance representing the host.
+ * @throws UnknownHostException
+ * if the address lookup fails.
+ */
+ public InetAddress getByName(String host) throws UnknownHostException {
+ return InetAddress.getByNameOnNet(host, netId);
+ }
+
+ /**
+ * A {@code SocketFactory} that produces {@code Socket}'s bound to this network.
+ */
+ private class NetworkBoundSocketFactory extends SocketFactory {
+ private final int mNetId;
+
+ public NetworkBoundSocketFactory(int netId) {
+ super();
+ mNetId = netId;
+ }
+
+ private void connectToHost(Socket socket, String host, int port) throws IOException {
+ // Lookup addresses only on this Network.
+ InetAddress[] hostAddresses = getAllByName(host);
+ // Try all but last address ignoring exceptions.
+ for (int i = 0; i < hostAddresses.length - 1; i++) {
+ try {
+ socket.connect(new InetSocketAddress(hostAddresses[i], port));
+ return;
+ } catch (IOException e) {
+ }
+ }
+ // Try last address. Do throw exceptions.
+ socket.connect(new InetSocketAddress(hostAddresses[hostAddresses.length - 1], port));
+ }
+
+ @Override
+ public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
+ Socket socket = createSocket();
+ socket.bind(new InetSocketAddress(localHost, localPort));
+ connectToHost(socket, host, port);
+ return socket;
+ }
+
+ @Override
+ public Socket createSocket(InetAddress address, int port, InetAddress localAddress,
+ int localPort) throws IOException {
+ Socket socket = createSocket();
+ socket.bind(new InetSocketAddress(localAddress, localPort));
+ socket.connect(new InetSocketAddress(address, port));
+ return socket;
+ }
+
+ @Override
+ public Socket createSocket(InetAddress host, int port) throws IOException {
+ Socket socket = createSocket();
+ socket.connect(new InetSocketAddress(host, port));
+ return socket;
+ }
+
+ @Override
+ public Socket createSocket(String host, int port) throws IOException {
+ Socket socket = createSocket();
+ connectToHost(socket, host, port);
+ return socket;
+ }
+
+ @Override
+ public Socket createSocket() throws IOException {
+ Socket socket = new Socket();
+ // Query a property of the underlying socket to ensure the underlying
+ // socket exists so a file descriptor is available to bind to a network.
+ socket.getReuseAddress();
+ NetworkUtils.bindSocketToNetwork(socket.getFileDescriptor$().getInt$(), mNetId);
+ return socket;
+ }
+ }
+
+ /**
+ * Returns a {@link SocketFactory} bound to this network. Any {@link Socket} created by
+ * this factory will have its traffic sent over this {@code Network}. Note that if this
+ * {@code Network} ever disconnects, this factory and any {@link Socket} it produced in the
+ * past or future will cease to work.
+ *
+ * @return a {@link SocketFactory} which produces {@link Socket} instances bound to this
+ * {@code Network}.
+ */
+ public SocketFactory getSocketFactory() {
+ if (mNetworkBoundSocketFactory == null) {
+ mNetworkBoundSocketFactory = new NetworkBoundSocketFactory(netId);
+ }
+ return mNetworkBoundSocketFactory;
+ }
+
+ // implement the Parcelable interface
+ public int describeContents() {
+ return 0;
+ }
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(netId);
+ }
+
+ public static final Creator<Network> CREATOR =
+ new Creator<Network>() {
+ public Network createFromParcel(Parcel in) {
+ int netId = in.readInt();
+
+ return new Network(netId);
+ }
+
+ public Network[] newArray(int size) {
+ return new Network[size];
+ }
+ };
+
+ public boolean equals(Object obj) {
+ if (obj instanceof Network == false) return false;
+ Network other = (Network)obj;
+ return this.netId == other.netId;
+ }
+
+ public int hashCode() {
+ return netId * 11;
+ }
+}
diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java
new file mode 100644
index 0000000..3d0874b
--- /dev/null
+++ b/core/java/android/net/NetworkAgent.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2014 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.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import com.android.internal.util.AsyncChannel;
+import com.android.internal.util.Protocol;
+
+import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A Utility class for handling for communicating between bearer-specific
+ * code and ConnectivityService.
+ *
+ * A bearer may have more than one NetworkAgent if it can simultaneously
+ * support separate networks (IMS / Internet / MMS Apns on cellular, or
+ * perhaps connections with different SSID or P2P for Wi-Fi).
+ *
+ * @hide
+ */
+public abstract class NetworkAgent extends Handler {
+ private volatile AsyncChannel mAsyncChannel;
+ private final String LOG_TAG;
+ private static final boolean DBG = true;
+ private static final boolean VDBG = true;
+ private final Context mContext;
+ private final ArrayList<Message>mPreConnectedQueue = new ArrayList<Message>();
+
+ private static final int BASE = Protocol.BASE_NETWORK_AGENT;
+
+ /**
+ * Sent by ConnectivityService to the NetworkAgent to inform it of
+ * suspected connectivity problems on its network. The NetworkAgent
+ * should take steps to verify and correct connectivity.
+ */
+ public static final int CMD_SUSPECT_BAD = BASE;
+
+ /**
+ * Sent by the NetworkAgent (note the EVENT vs CMD prefix) to
+ * ConnectivityService to pass the current NetworkInfo (connection state).
+ * Sent when the NetworkInfo changes, mainly due to change of state.
+ * obj = NetworkInfo
+ */
+ public static final int EVENT_NETWORK_INFO_CHANGED = BASE + 1;
+
+ /**
+ * Sent by the NetworkAgent to ConnectivityService to pass the current
+ * NetworkCapabilties.
+ * obj = NetworkCapabilities
+ */
+ public static final int EVENT_NETWORK_CAPABILITIES_CHANGED = BASE + 2;
+
+ /**
+ * Sent by the NetworkAgent to ConnectivityService to pass the current
+ * NetworkProperties.
+ * obj = NetworkProperties
+ */
+ public static final int EVENT_NETWORK_PROPERTIES_CHANGED = BASE + 3;
+
+ /* centralize place where base network score, and network score scaling, will be
+ * stored, so as we can consistently compare apple and oranges, or wifi, ethernet and LTE
+ */
+ public static final int WIFI_BASE_SCORE = 60;
+
+ /**
+ * Sent by the NetworkAgent to ConnectivityService to pass the current
+ * network score.
+ * obj = network score Integer
+ */
+ public static final int EVENT_NETWORK_SCORE_CHANGED = BASE + 4;
+
+ public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
+ NetworkCapabilities nc, LinkProperties lp, int score) {
+ super(looper);
+ LOG_TAG = logTag;
+ mContext = context;
+ if (ni == null || nc == null || lp == null) {
+ throw new IllegalArgumentException();
+ }
+
+ if (DBG) log("Registering NetworkAgent");
+ ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ cm.registerNetworkAgent(new Messenger(this), new NetworkInfo(ni),
+ new LinkProperties(lp), new NetworkCapabilities(nc), score);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
+ if (mAsyncChannel != null) {
+ log("Received new connection while already connected!");
+ } else {
+ if (DBG) log("NetworkAgent fully connected");
+ AsyncChannel ac = new AsyncChannel();
+ ac.connected(null, this, msg.replyTo);
+ ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
+ AsyncChannel.STATUS_SUCCESSFUL);
+ synchronized (mPreConnectedQueue) {
+ mAsyncChannel = ac;
+ for (Message m : mPreConnectedQueue) {
+ ac.sendMessage(m);
+ }
+ mPreConnectedQueue.clear();
+ }
+ }
+ break;
+ }
+ case AsyncChannel.CMD_CHANNEL_DISCONNECT: {
+ if (DBG) log("CMD_CHANNEL_DISCONNECT");
+ if (mAsyncChannel != null) mAsyncChannel.disconnect();
+ break;
+ }
+ case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
+ if (DBG) log("NetworkAgent channel lost");
+ // let the client know CS is done with us.
+ unwanted();
+ synchronized (mPreConnectedQueue) {
+ mAsyncChannel = null;
+ }
+ break;
+ }
+ case CMD_SUSPECT_BAD: {
+ log("Unhandled Message " + msg);
+ break;
+ }
+ }
+ }
+
+ private void queueOrSendMessage(int what, Object obj) {
+ synchronized (mPreConnectedQueue) {
+ if (mAsyncChannel != null) {
+ mAsyncChannel.sendMessage(what, obj);
+ } else {
+ Message msg = Message.obtain();
+ msg.what = what;
+ msg.obj = obj;
+ mPreConnectedQueue.add(msg);
+ }
+ }
+ }
+
+ /**
+ * Called by the bearer code when it has new LinkProperties data.
+ */
+ public void sendLinkProperties(LinkProperties linkProperties) {
+ queueOrSendMessage(EVENT_NETWORK_PROPERTIES_CHANGED, new LinkProperties(linkProperties));
+ }
+
+ /**
+ * Called by the bearer code when it has new NetworkInfo data.
+ */
+ public void sendNetworkInfo(NetworkInfo networkInfo) {
+ queueOrSendMessage(EVENT_NETWORK_INFO_CHANGED, new NetworkInfo(networkInfo));
+ }
+
+ /**
+ * Called by the bearer code when it has new NetworkCapabilities data.
+ */
+ public void sendNetworkCapabilities(NetworkCapabilities networkCapabilities) {
+ queueOrSendMessage(EVENT_NETWORK_CAPABILITIES_CHANGED,
+ new NetworkCapabilities(networkCapabilities));
+ }
+
+ /**
+ * Called by the bearer code when it has a new score for this network.
+ */
+ public void sendNetworkScore(int score) {
+ queueOrSendMessage(EVENT_NETWORK_SCORE_CHANGED, new Integer(score));
+ }
+
+ /**
+ * Called when ConnectivityService has indicated they no longer want this network.
+ * The parent factory should (previously) have received indication of the change
+ * as well, either canceling NetworkRequests or altering their score such that this
+ * network won't be immediately requested again.
+ */
+ abstract protected void unwanted();
+
+ protected void log(String s) {
+ Log.d(LOG_TAG, "NetworkAgent: " + s);
+ }
+}
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
new file mode 100644
index 0000000..fe96287
--- /dev/null
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -0,0 +1,526 @@
+/*
+ * Copyright (C) 2014 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.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.lang.IllegalArgumentException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * This class represents the capabilities of a network. This is used both to specify
+ * needs to {@link ConnectivityManager} and when inspecting a network.
+ *
+ * Note that this replaces the old {@link ConnectivityManager#TYPE_MOBILE} method
+ * of network selection. Rather than indicate a need for Wi-Fi because an application
+ * needs high bandwidth and risk obselence when a new, fast network appears (like LTE),
+ * the application should specify it needs high bandwidth. Similarly if an application
+ * needs an unmetered network for a bulk transfer it can specify that rather than assuming
+ * all cellular based connections are metered and all Wi-Fi based connections are not.
+ */
+public final class NetworkCapabilities implements Parcelable {
+ private static final String TAG = "NetworkCapabilities";
+ private static final boolean DBG = false;
+
+ /**
+ * @hide
+ */
+ public NetworkCapabilities() {
+ }
+
+ public NetworkCapabilities(NetworkCapabilities nc) {
+ if (nc != null) {
+ mNetworkCapabilities = nc.mNetworkCapabilities;
+ mTransportTypes = nc.mTransportTypes;
+ mLinkUpBandwidthKbps = nc.mLinkUpBandwidthKbps;
+ mLinkDownBandwidthKbps = nc.mLinkDownBandwidthKbps;
+ }
+ }
+
+ /**
+ * Represents the network's capabilities. If any are specified they will be satisfied
+ * by any Network that matches all of them.
+ */
+ private long mNetworkCapabilities = (1 << NET_CAPABILITY_NOT_RESTRICTED);
+
+ /**
+ * Indicates this is a network that has the ability to reach the
+ * carrier's MMSC for sending and receiving MMS messages.
+ */
+ public static final int NET_CAPABILITY_MMS = 0;
+
+ /**
+ * Indicates this is a network that has the ability to reach the carrier's
+ * SUPL server, used to retrieve GPS information.
+ */
+ public static final int NET_CAPABILITY_SUPL = 1;
+
+ /**
+ * Indicates this is a network that has the ability to reach the carrier's
+ * DUN or tethering gateway.
+ */
+ public static final int NET_CAPABILITY_DUN = 2;
+
+ /**
+ * Indicates this is a network that has the ability to reach the carrier's
+ * FOTA portal, used for over the air updates.
+ */
+ public static final int NET_CAPABILITY_FOTA = 3;
+
+ /**
+ * Indicates this is a network that has the ability to reach the carrier's
+ * IMS servers, used for network registration and signaling.
+ */
+ public static final int NET_CAPABILITY_IMS = 4;
+
+ /**
+ * Indicates this is a network that has the ability to reach the carrier's
+ * CBS servers, used for carrier specific services.
+ */
+ public static final int NET_CAPABILITY_CBS = 5;
+
+ /**
+ * Indicates this is a network that has the ability to reach a Wi-Fi direct
+ * peer.
+ */
+ public static final int NET_CAPABILITY_WIFI_P2P = 6;
+
+ /**
+ * Indicates this is a network that has the ability to reach a carrier's
+ * Initial Attach servers.
+ */
+ public static final int NET_CAPABILITY_IA = 7;
+
+ /**
+ * Indicates this is a network that has the ability to reach a carrier's
+ * RCS servers, used for Rich Communication Services.
+ */
+ public static final int NET_CAPABILITY_RCS = 8;
+
+ /**
+ * Indicates this is a network that has the ability to reach a carrier's
+ * XCAP servers, used for configuration and control.
+ */
+ public static final int NET_CAPABILITY_XCAP = 9;
+
+ /**
+ * Indicates this is a network that has the ability to reach a carrier's
+ * Emergency IMS servers, used for network signaling during emergency calls.
+ */
+ public static final int NET_CAPABILITY_EIMS = 10;
+
+ /**
+ * Indicates that this network is unmetered.
+ */
+ public static final int NET_CAPABILITY_NOT_METERED = 11;
+
+ /**
+ * Indicates that this network should be able to reach the internet.
+ */
+ public static final int NET_CAPABILITY_INTERNET = 12;
+
+ /**
+ * Indicates that this network is available for general use. If this is not set
+ * applications should not attempt to communicate on this network. Note that this
+ * is simply informative and not enforcement - enforcement is handled via other means.
+ * Set by default.
+ */
+ public static final int NET_CAPABILITY_NOT_RESTRICTED = 13;
+
+ private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS;
+ private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_NOT_RESTRICTED;
+
+ /**
+ * Adds the given capability to this {@code NetworkCapability} instance.
+ * Multiple capabilities may be applied sequentially. Note that when searching
+ * for a network to satisfy a request, all capabilities requested must be satisfied.
+ *
+ * @param capability the {@code NetworkCapabilities.NET_CAPABILITY_*} to be added.
+ * @return This NetworkCapability to facilitate chaining.
+ * @hide
+ */
+ public NetworkCapabilities addCapability(int capability) {
+ if (capability < MIN_NET_CAPABILITY || capability > MAX_NET_CAPABILITY) {
+ throw new IllegalArgumentException("NetworkCapability out of range");
+ }
+ mNetworkCapabilities |= 1 << capability;
+ return this;
+ }
+
+ /**
+ * Removes (if found) the given capability from this {@code NetworkCapability} instance.
+ *
+ * @param capability the {@code NetworkCapabilities.NET_CAPABILTIY_*} to be removed.
+ * @return This NetworkCapability to facilitate chaining.
+ * @hide
+ */
+ public NetworkCapabilities removeCapability(int capability) {
+ if (capability < MIN_NET_CAPABILITY || capability > MAX_NET_CAPABILITY) {
+ throw new IllegalArgumentException("NetworkCapability out of range");
+ }
+ mNetworkCapabilities &= ~(1 << capability);
+ return this;
+ }
+
+ /**
+ * Gets all the capabilities set on this {@code NetworkCapability} instance.
+ *
+ * @return an array of {@code NetworkCapabilities.NET_CAPABILITY_*} values
+ * for this instance.
+ * @hide
+ */
+ public int[] getCapabilities() {
+ return enumerateBits(mNetworkCapabilities);
+ }
+
+ /**
+ * Tests for the presence of a capabilitity on this instance.
+ *
+ * @param capability the {@code NetworkCapabilities.NET_CAPABILITY_*} to be tested for.
+ * @return {@code true} if set on this instance.
+ */
+ public boolean hasCapability(int capability) {
+ if (capability < MIN_NET_CAPABILITY || capability > MAX_NET_CAPABILITY) {
+ return false;
+ }
+ return ((mNetworkCapabilities & (1 << capability)) != 0);
+ }
+
+ private int[] enumerateBits(long val) {
+ int size = Long.bitCount(val);
+ int[] result = new int[size];
+ int index = 0;
+ int resource = 0;
+ while (val > 0) {
+ if ((val & 1) == 1) result[index++] = resource;
+ val = val >> 1;
+ resource++;
+ }
+ return result;
+ }
+
+ private void combineNetCapabilities(NetworkCapabilities nc) {
+ this.mNetworkCapabilities |= nc.mNetworkCapabilities;
+ }
+
+ private boolean satisfiedByNetCapabilities(NetworkCapabilities nc) {
+ return ((nc.mNetworkCapabilities & this.mNetworkCapabilities) == this.mNetworkCapabilities);
+ }
+
+ private boolean equalsNetCapabilities(NetworkCapabilities nc) {
+ return (nc.mNetworkCapabilities == this.mNetworkCapabilities);
+ }
+
+ /**
+ * Representing the transport type. Apps should generally not care about transport. A
+ * request for a fast internet connection could be satisfied by a number of different
+ * transports. If any are specified here it will be satisfied a Network that matches
+ * any of them. If a caller doesn't care about the transport it should not specify any.
+ */
+ private long mTransportTypes;
+
+ /**
+ * Indicates this network uses a Cellular transport.
+ */
+ public static final int TRANSPORT_CELLULAR = 0;
+
+ /**
+ * Indicates this network uses a Wi-Fi transport.
+ */
+ public static final int TRANSPORT_WIFI = 1;
+
+ /**
+ * Indicates this network uses a Bluetooth transport.
+ */
+ public static final int TRANSPORT_BLUETOOTH = 2;
+
+ /**
+ * Indicates this network uses an Ethernet transport.
+ */
+ public static final int TRANSPORT_ETHERNET = 3;
+
+ private static final int MIN_TRANSPORT = TRANSPORT_CELLULAR;
+ private static final int MAX_TRANSPORT = TRANSPORT_ETHERNET;
+
+ /**
+ * Adds the given transport type to this {@code NetworkCapability} instance.
+ * Multiple transports may be applied sequentially. Note that when searching
+ * for a network to satisfy a request, any listed in the request will satisfy the request.
+ * For example {@code TRANSPORT_WIFI} and {@code TRANSPORT_ETHERNET} added to a
+ * {@code NetworkCapabilities} would cause either a Wi-Fi network or an Ethernet network
+ * to be selected. This is logically different than
+ * {@code NetworkCapabilities.NET_CAPABILITY_*} listed above.
+ *
+ * @param transportType the {@code NetworkCapabilities.TRANSPORT_*} to be added.
+ * @return This NetworkCapability to facilitate chaining.
+ * @hide
+ */
+ public NetworkCapabilities addTransportType(int transportType) {
+ if (transportType < MIN_TRANSPORT || transportType > MAX_TRANSPORT) {
+ throw new IllegalArgumentException("TransportType out of range");
+ }
+ mTransportTypes |= 1 << transportType;
+ return this;
+ }
+
+ /**
+ * Removes (if found) the given transport from this {@code NetworkCapability} instance.
+ *
+ * @param transportType the {@code NetworkCapabilities.TRANSPORT_*} to be removed.
+ * @return This NetworkCapability to facilitate chaining.
+ * @hide
+ */
+ public NetworkCapabilities removeTransportType(int transportType) {
+ if (transportType < MIN_TRANSPORT || transportType > MAX_TRANSPORT) {
+ throw new IllegalArgumentException("TransportType out of range");
+ }
+ mTransportTypes &= ~(1 << transportType);
+ return this;
+ }
+
+ /**
+ * Gets all the transports set on this {@code NetworkCapability} instance.
+ *
+ * @return an array of {@code NetworkCapabilities.TRANSPORT_*} values
+ * for this instance.
+ * @hide
+ */
+ public int[] getTransportTypes() {
+ return enumerateBits(mTransportTypes);
+ }
+
+ /**
+ * Tests for the presence of a transport on this instance.
+ *
+ * @param transportType the {@code NetworkCapabilities.TRANSPORT_*} to be tested for.
+ * @return {@code true} if set on this instance.
+ */
+ public boolean hasTransport(int transportType) {
+ if (transportType < MIN_TRANSPORT || transportType > MAX_TRANSPORT) {
+ return false;
+ }
+ return ((mTransportTypes & (1 << transportType)) != 0);
+ }
+
+ private void combineTransportTypes(NetworkCapabilities nc) {
+ this.mTransportTypes |= nc.mTransportTypes;
+ }
+ private boolean satisfiedByTransportTypes(NetworkCapabilities nc) {
+ return ((this.mTransportTypes == 0) ||
+ ((this.mTransportTypes & nc.mTransportTypes) != 0));
+ }
+ private boolean equalsTransportTypes(NetworkCapabilities nc) {
+ return (nc.mTransportTypes == this.mTransportTypes);
+ }
+
+ /**
+ * Passive link bandwidth. This is a rough guide of the expected peak bandwidth
+ * for the first hop on the given transport. It is not measured, but may take into account
+ * link parameters (Radio technology, allocated channels, etc).
+ */
+ private int mLinkUpBandwidthKbps;
+ private int mLinkDownBandwidthKbps;
+
+ /**
+ * Sets the upstream bandwidth for this network in Kbps. This always only refers to
+ * the estimated first hop transport bandwidth.
+ * <p>
+ * Note that when used to request a network, this specifies the minimum acceptable.
+ * When received as the state of an existing network this specifies the typical
+ * first hop bandwidth expected. This is never measured, but rather is inferred
+ * from technology type and other link parameters. It could be used to differentiate
+ * between very slow 1xRTT cellular links and other faster networks or even between
+ * 802.11b vs 802.11AC wifi technologies. It should not be used to differentiate between
+ * fast backhauls and slow backhauls.
+ *
+ * @param upKbps the estimated first hop upstream (device to network) bandwidth.
+ * @hide
+ */
+ public void setLinkUpstreamBandwidthKbps(int upKbps) {
+ mLinkUpBandwidthKbps = upKbps;
+ }
+
+ /**
+ * Retrieves the upstream bandwidth for this network in Kbps. This always only refers to
+ * the estimated first hop transport bandwidth.
+ *
+ * @return The estimated first hop upstream (device to network) bandwidth.
+ */
+ public int getLinkUpstreamBandwidthKbps() {
+ return mLinkUpBandwidthKbps;
+ }
+
+ /**
+ * Sets the downstream bandwidth for this network in Kbps. This always only refers to
+ * the estimated first hop transport bandwidth.
+ * <p>
+ * Note that when used to request a network, this specifies the minimum acceptable.
+ * When received as the state of an existing network this specifies the typical
+ * first hop bandwidth expected. This is never measured, but rather is inferred
+ * from technology type and other link parameters. It could be used to differentiate
+ * between very slow 1xRTT cellular links and other faster networks or even between
+ * 802.11b vs 802.11AC wifi technologies. It should not be used to differentiate between
+ * fast backhauls and slow backhauls.
+ *
+ * @param downKbps the estimated first hop downstream (network to device) bandwidth.
+ * @hide
+ */
+ public void setLinkDownstreamBandwidthKbps(int downKbps) {
+ mLinkDownBandwidthKbps = downKbps;
+ }
+
+ /**
+ * Retrieves the downstream bandwidth for this network in Kbps. This always only refers to
+ * the estimated first hop transport bandwidth.
+ *
+ * @return The estimated first hop downstream (network to device) bandwidth.
+ */
+ public int getLinkDownstreamBandwidthKbps() {
+ return mLinkDownBandwidthKbps;
+ }
+
+ private void combineLinkBandwidths(NetworkCapabilities nc) {
+ this.mLinkUpBandwidthKbps =
+ Math.max(this.mLinkUpBandwidthKbps, nc.mLinkUpBandwidthKbps);
+ this.mLinkDownBandwidthKbps =
+ Math.max(this.mLinkDownBandwidthKbps, nc.mLinkDownBandwidthKbps);
+ }
+ private boolean satisfiedByLinkBandwidths(NetworkCapabilities nc) {
+ return !(this.mLinkUpBandwidthKbps > nc.mLinkUpBandwidthKbps ||
+ this.mLinkDownBandwidthKbps > nc.mLinkDownBandwidthKbps);
+ }
+ private boolean equalsLinkBandwidths(NetworkCapabilities nc) {
+ return (this.mLinkUpBandwidthKbps == nc.mLinkUpBandwidthKbps &&
+ this.mLinkDownBandwidthKbps == nc.mLinkDownBandwidthKbps);
+ }
+
+ /**
+ * Combine a set of Capabilities to this one. Useful for coming up with the complete set
+ * {@hide}
+ */
+ public void combineCapabilities(NetworkCapabilities nc) {
+ combineNetCapabilities(nc);
+ combineTransportTypes(nc);
+ combineLinkBandwidths(nc);
+ }
+
+ /**
+ * Check if our requirements are satisfied by the given Capabilities.
+ * {@hide}
+ */
+ public boolean satisfiedByNetworkCapabilities(NetworkCapabilities nc) {
+ return (nc != null &&
+ satisfiedByNetCapabilities(nc) &&
+ satisfiedByTransportTypes(nc) &&
+ satisfiedByLinkBandwidths(nc));
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null || (obj instanceof NetworkCapabilities == false)) return false;
+ NetworkCapabilities that = (NetworkCapabilities)obj;
+ return (equalsNetCapabilities(that) &&
+ equalsTransportTypes(that) &&
+ equalsLinkBandwidths(that));
+ }
+
+ @Override
+ public int hashCode() {
+ return ((int)(mNetworkCapabilities & 0xFFFFFFFF) +
+ ((int)(mNetworkCapabilities >> 32) * 3) +
+ ((int)(mTransportTypes & 0xFFFFFFFF) * 5) +
+ ((int)(mTransportTypes >> 32) * 7) +
+ (mLinkUpBandwidthKbps * 11) +
+ (mLinkDownBandwidthKbps * 13));
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(mNetworkCapabilities);
+ dest.writeLong(mTransportTypes);
+ dest.writeInt(mLinkUpBandwidthKbps);
+ dest.writeInt(mLinkDownBandwidthKbps);
+ }
+ public static final Creator<NetworkCapabilities> CREATOR =
+ new Creator<NetworkCapabilities>() {
+ public NetworkCapabilities createFromParcel(Parcel in) {
+ NetworkCapabilities netCap = new NetworkCapabilities();
+
+ netCap.mNetworkCapabilities = in.readLong();
+ netCap.mTransportTypes = in.readLong();
+ netCap.mLinkUpBandwidthKbps = in.readInt();
+ netCap.mLinkDownBandwidthKbps = in.readInt();
+ return netCap;
+ }
+ public NetworkCapabilities[] newArray(int size) {
+ return new NetworkCapabilities[size];
+ }
+ };
+
+ public String toString() {
+ int[] types = getTransportTypes();
+ String transports = (types.length > 0 ? " Transports: " : "");
+ for (int i = 0; i < types.length;) {
+ switch (types[i]) {
+ case TRANSPORT_CELLULAR: transports += "CELLULAR"; break;
+ case TRANSPORT_WIFI: transports += "WIFI"; break;
+ case TRANSPORT_BLUETOOTH: transports += "BLUETOOTH"; break;
+ case TRANSPORT_ETHERNET: transports += "ETHERNET"; break;
+ }
+ if (++i < types.length) transports += "|";
+ }
+
+ types = getCapabilities();
+ String capabilities = (types.length > 0 ? " Capabilities: " : "");
+ for (int i = 0; i < types.length; ) {
+ switch (types[i]) {
+ case NET_CAPABILITY_MMS: capabilities += "MMS"; break;
+ case NET_CAPABILITY_SUPL: capabilities += "SUPL"; break;
+ case NET_CAPABILITY_DUN: capabilities += "DUN"; break;
+ case NET_CAPABILITY_FOTA: capabilities += "FOTA"; break;
+ case NET_CAPABILITY_IMS: capabilities += "IMS"; break;
+ case NET_CAPABILITY_CBS: capabilities += "CBS"; break;
+ case NET_CAPABILITY_WIFI_P2P: capabilities += "WIFI_P2P"; break;
+ case NET_CAPABILITY_IA: capabilities += "IA"; break;
+ case NET_CAPABILITY_RCS: capabilities += "RCS"; break;
+ case NET_CAPABILITY_XCAP: capabilities += "XCAP"; break;
+ case NET_CAPABILITY_EIMS: capabilities += "EIMS"; break;
+ case NET_CAPABILITY_NOT_METERED: capabilities += "NOT_METERED"; break;
+ case NET_CAPABILITY_INTERNET: capabilities += "INTERNET"; break;
+ case NET_CAPABILITY_NOT_RESTRICTED: capabilities += "NOT_RESTRICTED"; break;
+ }
+ if (++i < types.length) capabilities += "&";
+ }
+
+ String upBand = ((mLinkUpBandwidthKbps > 0) ? " LinkUpBandwidth>=" +
+ mLinkUpBandwidthKbps + "Kbps" : "");
+ String dnBand = ((mLinkDownBandwidthKbps > 0) ? " LinkDnBandwidth>=" +
+ mLinkDownBandwidthKbps + "Kbps" : "");
+
+ return "[" + transports + capabilities + upBand + dnBand + "]";
+ }
+}
diff --git a/core/java/android/net/NetworkConfig.java b/core/java/android/net/NetworkConfig.java
new file mode 100644
index 0000000..32a2cda
--- /dev/null
+++ b/core/java/android/net/NetworkConfig.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2010 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 java.util.Locale;
+
+/**
+ * Describes the buildtime configuration of a network.
+ * Holds settings read from resources.
+ * @hide
+ */
+public class NetworkConfig {
+ /**
+ * Human readable string
+ */
+ public String name;
+
+ /**
+ * Type from ConnectivityManager
+ */
+ public int type;
+
+ /**
+ * the radio number from radio attributes config
+ */
+ public int radio;
+
+ /**
+ * higher number == higher priority when turning off connections
+ */
+ public int priority;
+
+ /**
+ * indicates the boot time dependencyMet setting
+ */
+ public boolean dependencyMet;
+
+ /**
+ * indicates the default restoral timer in seconds
+ * if the network is used as a special network feature
+ * -1 indicates no restoration of default
+ */
+ public int restoreTime;
+
+ /**
+ * input string from config.xml resource. Uses the form:
+ * [Connection name],[ConnectivityManager connection type],
+ * [associated radio-type],[priority],[dependencyMet]
+ */
+ public NetworkConfig(String init) {
+ String fragments[] = init.split(",");
+ name = fragments[0].trim().toLowerCase(Locale.ROOT);
+ type = Integer.parseInt(fragments[1]);
+ radio = Integer.parseInt(fragments[2]);
+ priority = Integer.parseInt(fragments[3]);
+ restoreTime = Integer.parseInt(fragments[4]);
+ dependencyMet = Boolean.parseBoolean(fragments[5]);
+ }
+
+ /**
+ * Indicates if this network is supposed to be default-routable
+ */
+ public boolean isDefault() {
+ return (type == radio);
+ }
+}
diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java
new file mode 100644
index 0000000..d279412
--- /dev/null
+++ b/core/java/android/net/NetworkInfo.java
@@ -0,0 +1,506 @@
+/*
+ * Copyright (C) 2008 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.os.Parcelable;
+import android.os.Parcel;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.EnumMap;
+
+/**
+ * Describes the status of a network interface.
+ * <p>Use {@link ConnectivityManager#getActiveNetworkInfo()} to get an instance that represents
+ * the current network connection.
+ */
+public class NetworkInfo implements Parcelable {
+
+ /**
+ * Coarse-grained network state. This is probably what most applications should
+ * use, rather than {@link android.net.NetworkInfo.DetailedState DetailedState}.
+ * The mapping between the two is as follows:
+ * <br/><br/>
+ * <table>
+ * <tr><td><b>Detailed state</b></td><td><b>Coarse-grained state</b></td></tr>
+ * <tr><td><code>IDLE</code></td><td><code>DISCONNECTED</code></td></tr>
+ * <tr><td><code>SCANNING</code></td><td><code>CONNECTING</code></td></tr>
+ * <tr><td><code>CONNECTING</code></td><td><code>CONNECTING</code></td></tr>
+ * <tr><td><code>AUTHENTICATING</code></td><td><code>CONNECTING</code></td></tr>
+ * <tr><td><code>CONNECTED</code></td><td><code>CONNECTED</code></td></tr>
+ * <tr><td><code>DISCONNECTING</code></td><td><code>DISCONNECTING</code></td></tr>
+ * <tr><td><code>DISCONNECTED</code></td><td><code>DISCONNECTED</code></td></tr>
+ * <tr><td><code>UNAVAILABLE</code></td><td><code>DISCONNECTED</code></td></tr>
+ * <tr><td><code>FAILED</code></td><td><code>DISCONNECTED</code></td></tr>
+ * </table>
+ */
+ public enum State {
+ CONNECTING, CONNECTED, SUSPENDED, DISCONNECTING, DISCONNECTED, UNKNOWN
+ }
+
+ /**
+ * The fine-grained state of a network connection. This level of detail
+ * is probably of interest to few applications. Most should use
+ * {@link android.net.NetworkInfo.State State} instead.
+ */
+ public enum DetailedState {
+ /** Ready to start data connection setup. */
+ IDLE,
+ /** Searching for an available access point. */
+ SCANNING,
+ /** Currently setting up data connection. */
+ CONNECTING,
+ /** Network link established, performing authentication. */
+ AUTHENTICATING,
+ /** Awaiting response from DHCP server in order to assign IP address information. */
+ OBTAINING_IPADDR,
+ /** IP traffic should be available. */
+ CONNECTED,
+ /** IP traffic is suspended */
+ SUSPENDED,
+ /** Currently tearing down data connection. */
+ DISCONNECTING,
+ /** IP traffic not available. */
+ DISCONNECTED,
+ /** Attempt to connect failed. */
+ FAILED,
+ /** Access to this network is blocked. */
+ BLOCKED,
+ /** Link has poor connectivity. */
+ VERIFYING_POOR_LINK,
+ /** Checking if network is a captive portal */
+ CAPTIVE_PORTAL_CHECK
+ }
+
+ /**
+ * This is the map described in the Javadoc comment above. The positions
+ * of the elements of the array must correspond to the ordinal values
+ * of <code>DetailedState</code>.
+ */
+ private static final EnumMap<DetailedState, State> stateMap =
+ new EnumMap<DetailedState, State>(DetailedState.class);
+
+ static {
+ stateMap.put(DetailedState.IDLE, State.DISCONNECTED);
+ stateMap.put(DetailedState.SCANNING, State.DISCONNECTED);
+ stateMap.put(DetailedState.CONNECTING, State.CONNECTING);
+ stateMap.put(DetailedState.AUTHENTICATING, State.CONNECTING);
+ stateMap.put(DetailedState.OBTAINING_IPADDR, State.CONNECTING);
+ stateMap.put(DetailedState.VERIFYING_POOR_LINK, State.CONNECTING);
+ stateMap.put(DetailedState.CAPTIVE_PORTAL_CHECK, State.CONNECTING);
+ stateMap.put(DetailedState.CONNECTED, State.CONNECTED);
+ stateMap.put(DetailedState.SUSPENDED, State.SUSPENDED);
+ stateMap.put(DetailedState.DISCONNECTING, State.DISCONNECTING);
+ stateMap.put(DetailedState.DISCONNECTED, State.DISCONNECTED);
+ stateMap.put(DetailedState.FAILED, State.DISCONNECTED);
+ stateMap.put(DetailedState.BLOCKED, State.DISCONNECTED);
+ }
+
+ private int mNetworkType;
+ private int mSubtype;
+ private String mTypeName;
+ private String mSubtypeName;
+ private State mState;
+ private DetailedState mDetailedState;
+ private String mReason;
+ private String mExtraInfo;
+ private boolean mIsFailover;
+ private boolean mIsRoaming;
+ private boolean mIsConnectedToProvisioningNetwork;
+
+ /**
+ * Indicates whether network connectivity is possible:
+ */
+ private boolean mIsAvailable;
+
+ /**
+ * @param type network type
+ * @deprecated
+ * @hide because this constructor was only meant for internal use (and
+ * has now been superseded by the package-private constructor below).
+ */
+ public NetworkInfo(int type) {}
+
+ /**
+ * @hide
+ */
+ public NetworkInfo(int type, int subtype, String typeName, String subtypeName) {
+ if (!ConnectivityManager.isNetworkTypeValid(type)) {
+ throw new IllegalArgumentException("Invalid network type: " + type);
+ }
+ mNetworkType = type;
+ mSubtype = subtype;
+ mTypeName = typeName;
+ mSubtypeName = subtypeName;
+ setDetailedState(DetailedState.IDLE, null, null);
+ mState = State.UNKNOWN;
+ mIsAvailable = false; // until we're told otherwise, assume unavailable
+ mIsRoaming = false;
+ mIsConnectedToProvisioningNetwork = false;
+ }
+
+ /** {@hide} */
+ public NetworkInfo(NetworkInfo source) {
+ if (source != null) {
+ synchronized (source) {
+ mNetworkType = source.mNetworkType;
+ mSubtype = source.mSubtype;
+ mTypeName = source.mTypeName;
+ mSubtypeName = source.mSubtypeName;
+ mState = source.mState;
+ mDetailedState = source.mDetailedState;
+ mReason = source.mReason;
+ mExtraInfo = source.mExtraInfo;
+ mIsFailover = source.mIsFailover;
+ mIsRoaming = source.mIsRoaming;
+ mIsAvailable = source.mIsAvailable;
+ mIsConnectedToProvisioningNetwork = source.mIsConnectedToProvisioningNetwork;
+ }
+ }
+ }
+
+ /**
+ * Reports the type of network to which the
+ * info in this {@code NetworkInfo} pertains.
+ * @return one of {@link ConnectivityManager#TYPE_MOBILE}, {@link
+ * ConnectivityManager#TYPE_WIFI}, {@link ConnectivityManager#TYPE_WIMAX}, {@link
+ * ConnectivityManager#TYPE_ETHERNET}, {@link ConnectivityManager#TYPE_BLUETOOTH}, or other
+ * types defined by {@link ConnectivityManager}
+ */
+ public int getType() {
+ synchronized (this) {
+ return mNetworkType;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void setType(int type) {
+ synchronized (this) {
+ mNetworkType = type;
+ }
+ }
+
+ /**
+ * Return a network-type-specific integer describing the subtype
+ * of the network.
+ * @return the network subtype
+ */
+ public int getSubtype() {
+ synchronized (this) {
+ return mSubtype;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void setSubtype(int subtype, String subtypeName) {
+ synchronized (this) {
+ mSubtype = subtype;
+ mSubtypeName = subtypeName;
+ }
+ }
+
+ /**
+ * Return a human-readable name describe the type of the network,
+ * for example "WIFI" or "MOBILE".
+ * @return the name of the network type
+ */
+ public String getTypeName() {
+ synchronized (this) {
+ return mTypeName;
+ }
+ }
+
+ /**
+ * Return a human-readable name describing the subtype of the network.
+ * @return the name of the network subtype
+ */
+ public String getSubtypeName() {
+ synchronized (this) {
+ return mSubtypeName;
+ }
+ }
+
+ /**
+ * Indicates whether network connectivity exists or is in the process
+ * of being established. This is good for applications that need to
+ * do anything related to the network other than read or write data.
+ * For the latter, call {@link #isConnected()} instead, which guarantees
+ * that the network is fully usable.
+ * @return {@code true} if network connectivity exists or is in the process
+ * of being established, {@code false} otherwise.
+ */
+ public boolean isConnectedOrConnecting() {
+ synchronized (this) {
+ return mState == State.CONNECTED || mState == State.CONNECTING;
+ }
+ }
+
+ /**
+ * Indicates whether network connectivity exists and it is possible to establish
+ * connections and pass data.
+ * <p>Always call this before attempting to perform data transactions.
+ * @return {@code true} if network connectivity exists, {@code false} otherwise.
+ */
+ public boolean isConnected() {
+ synchronized (this) {
+ return mState == State.CONNECTED;
+ }
+ }
+
+ /**
+ * Indicates whether network connectivity is possible. A network is unavailable
+ * when a persistent or semi-persistent condition prevents the possibility
+ * of connecting to that network. Examples include
+ * <ul>
+ * <li>The device is out of the coverage area for any network of this type.</li>
+ * <li>The device is on a network other than the home network (i.e., roaming), and
+ * data roaming has been disabled.</li>
+ * <li>The device's radio is turned off, e.g., because airplane mode is enabled.</li>
+ * </ul>
+ * @return {@code true} if the network is available, {@code false} otherwise
+ */
+ public boolean isAvailable() {
+ synchronized (this) {
+ return mIsAvailable;
+ }
+ }
+
+ /**
+ * Sets if the network is available, ie, if the connectivity is possible.
+ * @param isAvailable the new availability value.
+ *
+ * @hide
+ */
+ public void setIsAvailable(boolean isAvailable) {
+ synchronized (this) {
+ mIsAvailable = isAvailable;
+ }
+ }
+
+ /**
+ * Indicates whether the current attempt to connect to the network
+ * resulted from the ConnectivityManager trying to fail over to this
+ * network following a disconnect from another network.
+ * @return {@code true} if this is a failover attempt, {@code false}
+ * otherwise.
+ */
+ public boolean isFailover() {
+ synchronized (this) {
+ return mIsFailover;
+ }
+ }
+
+ /**
+ * Set the failover boolean.
+ * @param isFailover {@code true} to mark the current connection attempt
+ * as a failover.
+ * @hide
+ */
+ public void setFailover(boolean isFailover) {
+ synchronized (this) {
+ mIsFailover = isFailover;
+ }
+ }
+
+ /**
+ * Indicates whether the device is currently roaming on this network.
+ * When {@code true}, it suggests that use of data on this network
+ * may incur extra costs.
+ * @return {@code true} if roaming is in effect, {@code false} otherwise.
+ */
+ public boolean isRoaming() {
+ synchronized (this) {
+ return mIsRoaming;
+ }
+ }
+
+ /** {@hide} */
+ @VisibleForTesting
+ public void setRoaming(boolean isRoaming) {
+ synchronized (this) {
+ mIsRoaming = isRoaming;
+ }
+ }
+
+ /** {@hide} */
+ @VisibleForTesting
+ public boolean isConnectedToProvisioningNetwork() {
+ synchronized (this) {
+ return mIsConnectedToProvisioningNetwork;
+ }
+ }
+
+ /** {@hide} */
+ @VisibleForTesting
+ public void setIsConnectedToProvisioningNetwork(boolean val) {
+ synchronized (this) {
+ mIsConnectedToProvisioningNetwork = val;
+ }
+ }
+
+ /**
+ * Reports the current coarse-grained state of the network.
+ * @return the coarse-grained state
+ */
+ public State getState() {
+ synchronized (this) {
+ return mState;
+ }
+ }
+
+ /**
+ * Reports the current fine-grained state of the network.
+ * @return the fine-grained state
+ */
+ public DetailedState getDetailedState() {
+ synchronized (this) {
+ return mDetailedState;
+ }
+ }
+
+ /**
+ * Sets the fine-grained state of the network.
+ * @param detailedState the {@link DetailedState}.
+ * @param reason a {@code String} indicating the reason for the state change,
+ * if one was supplied. May be {@code null}.
+ * @param extraInfo an optional {@code String} providing addditional network state
+ * information passed up from the lower networking layers.
+ * @hide
+ */
+ public void setDetailedState(DetailedState detailedState, String reason, String extraInfo) {
+ synchronized (this) {
+ this.mDetailedState = detailedState;
+ this.mState = stateMap.get(detailedState);
+ this.mReason = reason;
+ this.mExtraInfo = extraInfo;
+ }
+ }
+
+ /**
+ * Set the extraInfo field.
+ * @param extraInfo an optional {@code String} providing addditional network state
+ * information passed up from the lower networking layers.
+ * @hide
+ */
+ public void setExtraInfo(String extraInfo) {
+ synchronized (this) {
+ this.mExtraInfo = extraInfo;
+ }
+ }
+
+ /**
+ * Report the reason an attempt to establish connectivity failed,
+ * if one is available.
+ * @return the reason for failure, or null if not available
+ */
+ public String getReason() {
+ synchronized (this) {
+ return mReason;
+ }
+ }
+
+ /**
+ * Report the extra information about the network state, if any was
+ * provided by the lower networking layers.,
+ * if one is available.
+ * @return the extra information, or null if not available
+ */
+ public String getExtraInfo() {
+ synchronized (this) {
+ return mExtraInfo;
+ }
+ }
+
+ @Override
+ public String toString() {
+ synchronized (this) {
+ StringBuilder builder = new StringBuilder("[");
+ builder.append("type: ").append(getTypeName()).append("[").append(getSubtypeName()).
+ append("], state: ").append(mState).append("/").append(mDetailedState).
+ append(", reason: ").append(mReason == null ? "(unspecified)" : mReason).
+ append(", extra: ").append(mExtraInfo == null ? "(none)" : mExtraInfo).
+ append(", roaming: ").append(mIsRoaming).
+ append(", failover: ").append(mIsFailover).
+ append(", isAvailable: ").append(mIsAvailable).
+ append(", isConnectedToProvisioningNetwork: ").
+ append(mIsConnectedToProvisioningNetwork).
+ append("]");
+ return builder.toString();
+ }
+ }
+
+ /**
+ * Implement the Parcelable interface
+ * @hide
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Implement the Parcelable interface.
+ * @hide
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ synchronized (this) {
+ dest.writeInt(mNetworkType);
+ dest.writeInt(mSubtype);
+ dest.writeString(mTypeName);
+ dest.writeString(mSubtypeName);
+ dest.writeString(mState.name());
+ dest.writeString(mDetailedState.name());
+ dest.writeInt(mIsFailover ? 1 : 0);
+ dest.writeInt(mIsAvailable ? 1 : 0);
+ dest.writeInt(mIsRoaming ? 1 : 0);
+ dest.writeInt(mIsConnectedToProvisioningNetwork ? 1 : 0);
+ dest.writeString(mReason);
+ dest.writeString(mExtraInfo);
+ }
+ }
+
+ /**
+ * Implement the Parcelable interface.
+ * @hide
+ */
+ public static final Creator<NetworkInfo> CREATOR =
+ new Creator<NetworkInfo>() {
+ public NetworkInfo createFromParcel(Parcel in) {
+ int netType = in.readInt();
+ int subtype = in.readInt();
+ String typeName = in.readString();
+ String subtypeName = in.readString();
+ NetworkInfo netInfo = new NetworkInfo(netType, subtype, typeName, subtypeName);
+ netInfo.mState = State.valueOf(in.readString());
+ netInfo.mDetailedState = DetailedState.valueOf(in.readString());
+ netInfo.mIsFailover = in.readInt() != 0;
+ netInfo.mIsAvailable = in.readInt() != 0;
+ netInfo.mIsRoaming = in.readInt() != 0;
+ netInfo.mIsConnectedToProvisioningNetwork = in.readInt() != 0;
+ netInfo.mReason = in.readString();
+ netInfo.mExtraInfo = in.readString();
+ return netInfo;
+ }
+
+ public NetworkInfo[] newArray(int size) {
+ return new NetworkInfo[size];
+ }
+ };
+}
diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java
new file mode 100644
index 0000000..7911c72
--- /dev/null
+++ b/core/java/android/net/NetworkRequest.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2014 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.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Defines a request for a network, made through {@link NetworkRequest.Builder} and used
+ * to request a network via {@link ConnectivityManager#requestNetwork} or listen for changes
+ * via {@link ConnectivityManager#listenForNetwork}.
+ */
+public class NetworkRequest implements Parcelable {
+ /**
+ * The {@link NetworkCapabilities} that define this request.
+ * @hide
+ */
+ public final NetworkCapabilities networkCapabilities;
+
+ /**
+ * Identifies the request. NetworkRequests should only be constructed by
+ * the Framework and given out to applications as tokens to be used to identify
+ * the request.
+ * @hide
+ */
+ public final int requestId;
+
+ /**
+ * Set for legacy requests and the default. Set to TYPE_NONE for none.
+ * Causes CONNECTIVITY_ACTION broadcasts to be sent.
+ * @hide
+ */
+ public final int legacyType;
+
+ /**
+ * @hide
+ */
+ public NetworkRequest(NetworkCapabilities nc, int legacyType, int rId) {
+ requestId = rId;
+ networkCapabilities = nc;
+ this.legacyType = legacyType;
+ }
+
+ /**
+ * @hide
+ */
+ public NetworkRequest(NetworkRequest that) {
+ networkCapabilities = new NetworkCapabilities(that.networkCapabilities);
+ requestId = that.requestId;
+ this.legacyType = that.legacyType;
+ }
+
+ /**
+ * Builder used to create {@link NetworkRequest} objects. Specify the Network features
+ * needed in terms of {@link NetworkCapabilities} features
+ */
+ public static class Builder {
+ private final NetworkCapabilities mNetworkCapabilities = new NetworkCapabilities();
+
+ /**
+ * Default constructor for Builder.
+ */
+ public Builder() {}
+
+ /**
+ * Build {@link NetworkRequest} give the current set of capabilities.
+ */
+ public NetworkRequest build() {
+ return new NetworkRequest(mNetworkCapabilities, ConnectivityManager.TYPE_NONE,
+ ConnectivityManager.REQUEST_ID_UNSET);
+ }
+
+ /**
+ * Add the given capability requirement to this builder. These represent
+ * the requested network's required capabilities. Note that when searching
+ * for a network to satisfy a request, all capabilities requested must be
+ * satisfied. See {@link NetworkCapabilities} for {@code NET_CAPABILITIY_*}
+ * definitions.
+ *
+ * @param capability The {@code NetworkCapabilities.NET_CAPABILITY_*} to add.
+ * @return The builder to facilitate chaining
+ * {@code builder.addCapability(...).addCapability();}.
+ */
+ public Builder addCapability(int capability) {
+ mNetworkCapabilities.addCapability(capability);
+ return this;
+ }
+
+ /**
+ * Removes (if found) the given capability from this builder instance.
+ *
+ * @param capability The {@code NetworkCapabilities.NET_CAPABILITY_*} to remove.
+ * @return The builder to facilitate chaining.
+ */
+ public Builder removeCapability(int capability) {
+ mNetworkCapabilities.removeCapability(capability);
+ return this;
+ }
+
+ /**
+ * Adds the given transport requirement to this builder. These represent
+ * the set of allowed transports for the request. Only networks using one
+ * of these transports will satisfy the request. If no particular transports
+ * are required, none should be specified here. See {@link NetworkCapabilities}
+ * for {@code TRANSPORT_*} definitions.
+ *
+ * @param transportType The {@code NetworkCapabilities.TRANSPORT_*} to add.
+ * @return The builder to facilitate chaining.
+ */
+ public Builder addTransportType(int transportType) {
+ mNetworkCapabilities.addTransportType(transportType);
+ return this;
+ }
+
+ /**
+ * Removes (if found) the given transport from this builder instance.
+ *
+ * @param transportType The {@code NetworkCapabilities.TRANSPORT_*} to remove.
+ * @return The builder to facilitate chaining.
+ */
+ public Builder removeTransportType(int transportType) {
+ mNetworkCapabilities.removeTransportType(transportType);
+ return this;
+ }
+
+ /**
+ * @hide
+ */
+ public Builder setLinkUpstreamBandwidthKbps(int upKbps) {
+ mNetworkCapabilities.setLinkUpstreamBandwidthKbps(upKbps);
+ return this;
+ }
+ /**
+ * @hide
+ */
+ public Builder setLinkDownstreamBandwidthKbps(int downKbps) {
+ mNetworkCapabilities.setLinkDownstreamBandwidthKbps(downKbps);
+ return this;
+ }
+ }
+
+ // implement the Parcelable interface
+ public int describeContents() {
+ return 0;
+ }
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(networkCapabilities, flags);
+ dest.writeInt(legacyType);
+ dest.writeInt(requestId);
+ }
+ public static final Creator<NetworkRequest> CREATOR =
+ new Creator<NetworkRequest>() {
+ public NetworkRequest createFromParcel(Parcel in) {
+ NetworkCapabilities nc = (NetworkCapabilities)in.readParcelable(null);
+ int legacyType = in.readInt();
+ int requestId = in.readInt();
+ NetworkRequest result = new NetworkRequest(nc, legacyType, requestId);
+ return result;
+ }
+ public NetworkRequest[] newArray(int size) {
+ return new NetworkRequest[size];
+ }
+ };
+
+ public String toString() {
+ return "NetworkRequest [ id=" + requestId + ", legacyType=" + legacyType +
+ ", " + networkCapabilities.toString() + " ]";
+ }
+
+ public boolean equals(Object obj) {
+ if (obj instanceof NetworkRequest == false) return false;
+ NetworkRequest that = (NetworkRequest)obj;
+ return (that.legacyType == this.legacyType &&
+ that.requestId == this.requestId &&
+ ((that.networkCapabilities == null && this.networkCapabilities == null) ||
+ (that.networkCapabilities != null &&
+ that.networkCapabilities.equals(this.networkCapabilities))));
+ }
+
+ public int hashCode() {
+ return requestId + (legacyType * 1013) +
+ (networkCapabilities.hashCode() * 1051);
+ }
+}
diff --git a/core/java/android/net/NetworkState.java b/core/java/android/net/NetworkState.java
new file mode 100644
index 0000000..2e0e9e4
--- /dev/null
+++ b/core/java/android/net/NetworkState.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2011 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.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Snapshot of network state.
+ *
+ * @hide
+ */
+public class NetworkState implements Parcelable {
+
+ public final NetworkInfo networkInfo;
+ public final LinkProperties linkProperties;
+ public final NetworkCapabilities networkCapabilities;
+ /** Currently only used by testing. */
+ public final String subscriberId;
+ public final String networkId;
+
+ public NetworkState(NetworkInfo networkInfo, LinkProperties linkProperties,
+ NetworkCapabilities networkCapabilities) {
+ this(networkInfo, linkProperties, networkCapabilities, null, null);
+ }
+
+ public NetworkState(NetworkInfo networkInfo, LinkProperties linkProperties,
+ NetworkCapabilities networkCapabilities, String subscriberId, String networkId) {
+ this.networkInfo = networkInfo;
+ this.linkProperties = linkProperties;
+ this.networkCapabilities = networkCapabilities;
+ this.subscriberId = subscriberId;
+ this.networkId = networkId;
+ }
+
+ public NetworkState(Parcel in) {
+ networkInfo = in.readParcelable(null);
+ linkProperties = in.readParcelable(null);
+ networkCapabilities = in.readParcelable(null);
+ subscriberId = in.readString();
+ networkId = in.readString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeParcelable(networkInfo, flags);
+ out.writeParcelable(linkProperties, flags);
+ out.writeParcelable(networkCapabilities, flags);
+ out.writeString(subscriberId);
+ out.writeString(networkId);
+ }
+
+ public static final Creator<NetworkState> CREATOR = new Creator<NetworkState>() {
+ @Override
+ public NetworkState createFromParcel(Parcel in) {
+ return new NetworkState(in);
+ }
+
+ @Override
+ public NetworkState[] newArray(int size) {
+ return new NetworkState[size];
+ }
+ };
+
+}
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
new file mode 100644
index 0000000..b02f88e
--- /dev/null
+++ b/core/java/android/net/NetworkUtils.java
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2008 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 java.net.InetAddress;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.UnknownHostException;
+import java.util.Collection;
+import java.util.Locale;
+
+import android.util.Log;
+
+/**
+ * Native methods for managing network interfaces.
+ *
+ * {@hide}
+ */
+public class NetworkUtils {
+
+ private static final String TAG = "NetworkUtils";
+
+ /** Bring the named network interface up. */
+ public native static int enableInterface(String interfaceName);
+
+ /** Bring the named network interface down. */
+ public native static int disableInterface(String interfaceName);
+
+ /** Setting bit 0 indicates reseting of IPv4 addresses required */
+ public static final int RESET_IPV4_ADDRESSES = 0x01;
+
+ /** Setting bit 1 indicates reseting of IPv4 addresses required */
+ public static final int RESET_IPV6_ADDRESSES = 0x02;
+
+ /** Reset all addresses */
+ public static final int RESET_ALL_ADDRESSES = RESET_IPV4_ADDRESSES | RESET_IPV6_ADDRESSES;
+
+ /**
+ * Reset IPv6 or IPv4 sockets that are connected via the named interface.
+ *
+ * @param interfaceName is the interface to reset
+ * @param mask {@see #RESET_IPV4_ADDRESSES} and {@see #RESET_IPV6_ADDRESSES}
+ */
+ public native static int resetConnections(String interfaceName, int mask);
+
+ /**
+ * Start the DHCP client daemon, in order to have it request addresses
+ * for the named interface, and then configure the interface with those
+ * addresses. This call blocks until it obtains a result (either success
+ * or failure) from the daemon.
+ * @param interfaceName the name of the interface to configure
+ * @param dhcpResults if the request succeeds, this object is filled in with
+ * the IP address information.
+ * @return {@code true} for success, {@code false} for failure
+ */
+ public native static boolean runDhcp(String interfaceName, DhcpResults dhcpResults);
+
+ /**
+ * Initiate renewal on the Dhcp client daemon. This call blocks until it obtains
+ * a result (either success or failure) from the daemon.
+ * @param interfaceName the name of the interface to configure
+ * @param dhcpResults if the request succeeds, this object is filled in with
+ * the IP address information.
+ * @return {@code true} for success, {@code false} for failure
+ */
+ public native static boolean runDhcpRenew(String interfaceName, DhcpResults dhcpResults);
+
+ /**
+ * Shut down the DHCP client daemon.
+ * @param interfaceName the name of the interface for which the daemon
+ * should be stopped
+ * @return {@code true} for success, {@code false} for failure
+ */
+ public native static boolean stopDhcp(String interfaceName);
+
+ /**
+ * Release the current DHCP lease.
+ * @param interfaceName the name of the interface for which the lease should
+ * be released
+ * @return {@code true} for success, {@code false} for failure
+ */
+ public native static boolean releaseDhcpLease(String interfaceName);
+
+ /**
+ * Return the last DHCP-related error message that was recorded.
+ * <p/>NOTE: This string is not localized, but currently it is only
+ * used in logging.
+ * @return the most recent error message, if any
+ */
+ public native static String getDhcpError();
+
+ /**
+ * Set the SO_MARK of {@code socketfd} to {@code mark}
+ */
+ public native static void markSocket(int socketfd, int mark);
+
+ /**
+ * Binds the current process to the network designated by {@code netId}. All sockets created
+ * in the future (and not explicitly bound via a bound {@link SocketFactory} (see
+ * {@link Network#getSocketFactory}) will be bound to this network. Note that if this
+ * {@code Network} ever disconnects all sockets created in this way will cease to work. This
+ * is by design so an application doesn't accidentally use sockets it thinks are still bound to
+ * a particular {@code Network}.
+ */
+ public native static void bindProcessToNetwork(int netId);
+
+ /**
+ * Clear any process specific {@code Network} binding. This reverts a call to
+ * {@link #bindProcessToNetwork}.
+ */
+ public native static void unbindProcessToNetwork();
+
+ /**
+ * Return the netId last passed to {@link #bindProcessToNetwork}, or NETID_UNSET if
+ * {@link #unbindProcessToNetwork} has been called since {@link #bindProcessToNetwork}.
+ */
+ public native static int getNetworkBoundToProcess();
+
+ /**
+ * Binds host resolutions performed by this process to the network designated by {@code netId}.
+ * {@link #bindProcessToNetwork} takes precedence over this setting.
+ *
+ * @deprecated This is strictly for legacy usage to support startUsingNetworkFeature().
+ */
+ public native static void bindProcessToNetworkForHostResolution(int netId);
+
+ /**
+ * Clears any process specific {@link Network} binding for host resolution. This does
+ * not clear bindings enacted via {@link #bindProcessToNetwork}.
+ *
+ * @deprecated This is strictly for legacy usage to support startUsingNetworkFeature().
+ */
+ public native static void unbindProcessToNetworkForHostResolution();
+
+ /**
+ * Explicitly binds {@code socketfd} to the network designated by {@code netId}. This
+ * overrides any binding via {@link #bindProcessToNetwork}.
+ */
+ public native static void bindSocketToNetwork(int socketfd, int netId);
+
+ /**
+ * Convert a IPv4 address from an integer to an InetAddress.
+ * @param hostAddress an int corresponding to the IPv4 address in network byte order
+ */
+ public static InetAddress intToInetAddress(int hostAddress) {
+ byte[] addressBytes = { (byte)(0xff & hostAddress),
+ (byte)(0xff & (hostAddress >> 8)),
+ (byte)(0xff & (hostAddress >> 16)),
+ (byte)(0xff & (hostAddress >> 24)) };
+
+ try {
+ return InetAddress.getByAddress(addressBytes);
+ } catch (UnknownHostException e) {
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * Convert a IPv4 address from an InetAddress to an integer
+ * @param inetAddr is an InetAddress corresponding to the IPv4 address
+ * @return the IP address as an integer in network byte order
+ */
+ public static int inetAddressToInt(Inet4Address inetAddr)
+ throws IllegalArgumentException {
+ byte [] addr = inetAddr.getAddress();
+ return ((addr[3] & 0xff) << 24) | ((addr[2] & 0xff) << 16) |
+ ((addr[1] & 0xff) << 8) | (addr[0] & 0xff);
+ }
+
+ /**
+ * Convert a network prefix length to an IPv4 netmask integer
+ * @param prefixLength
+ * @return the IPv4 netmask as an integer in network byte order
+ */
+ public static int prefixLengthToNetmaskInt(int prefixLength)
+ throws IllegalArgumentException {
+ if (prefixLength < 0 || prefixLength > 32) {
+ throw new IllegalArgumentException("Invalid prefix length (0 <= prefix <= 32)");
+ }
+ int value = 0xffffffff << (32 - prefixLength);
+ return Integer.reverseBytes(value);
+ }
+
+ /**
+ * Convert a IPv4 netmask integer to a prefix length
+ * @param netmask as an integer in network byte order
+ * @return the network prefix length
+ */
+ public static int netmaskIntToPrefixLength(int netmask) {
+ return Integer.bitCount(netmask);
+ }
+
+ /**
+ * Create an InetAddress from a string where the string must be a standard
+ * representation of a V4 or V6 address. Avoids doing a DNS lookup on failure
+ * but it will throw an IllegalArgumentException in that case.
+ * @param addrString
+ * @return the InetAddress
+ * @hide
+ */
+ public static InetAddress numericToInetAddress(String addrString)
+ throws IllegalArgumentException {
+ return InetAddress.parseNumericAddress(addrString);
+ }
+
+ /**
+ * Get InetAddress masked with prefixLength. Will never return null.
+ * @param IP address which will be masked with specified prefixLength
+ * @param prefixLength the prefixLength used to mask the IP
+ */
+ public static InetAddress getNetworkPart(InetAddress address, int prefixLength) {
+ if (address == null) {
+ throw new RuntimeException("getNetworkPart doesn't accept null address");
+ }
+
+ byte[] array = address.getAddress();
+
+ if (prefixLength < 0 || prefixLength > array.length * 8) {
+ throw new RuntimeException("getNetworkPart - bad prefixLength");
+ }
+
+ int offset = prefixLength / 8;
+ int reminder = prefixLength % 8;
+ byte mask = (byte)(0xFF << (8 - reminder));
+
+ if (offset < array.length) array[offset] = (byte)(array[offset] & mask);
+
+ offset++;
+
+ for (; offset < array.length; offset++) {
+ array[offset] = 0;
+ }
+
+ InetAddress netPart = null;
+ try {
+ netPart = InetAddress.getByAddress(array);
+ } catch (UnknownHostException e) {
+ throw new RuntimeException("getNetworkPart error - " + e.toString());
+ }
+ return netPart;
+ }
+
+ /**
+ * Check if IP address type is consistent between two InetAddress.
+ * @return true if both are the same type. False otherwise.
+ */
+ public static boolean addressTypeMatches(InetAddress left, InetAddress right) {
+ return (((left instanceof Inet4Address) && (right instanceof Inet4Address)) ||
+ ((left instanceof Inet6Address) && (right instanceof Inet6Address)));
+ }
+
+ /**
+ * Convert a 32 char hex string into a Inet6Address.
+ * throws a runtime exception if the string isn't 32 chars, isn't hex or can't be
+ * made into an Inet6Address
+ * @param addrHexString a 32 character hex string representing an IPv6 addr
+ * @return addr an InetAddress representation for the string
+ */
+ public static InetAddress hexToInet6Address(String addrHexString)
+ throws IllegalArgumentException {
+ try {
+ return numericToInetAddress(String.format(Locale.US, "%s:%s:%s:%s:%s:%s:%s:%s",
+ addrHexString.substring(0,4), addrHexString.substring(4,8),
+ addrHexString.substring(8,12), addrHexString.substring(12,16),
+ addrHexString.substring(16,20), addrHexString.substring(20,24),
+ addrHexString.substring(24,28), addrHexString.substring(28,32)));
+ } catch (Exception e) {
+ Log.e("NetworkUtils", "error in hexToInet6Address(" + addrHexString + "): " + e);
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ /**
+ * Create a string array of host addresses from a collection of InetAddresses
+ * @param addrs a Collection of InetAddresses
+ * @return an array of Strings containing their host addresses
+ */
+ public static String[] makeStrings(Collection<InetAddress> addrs) {
+ String[] result = new String[addrs.size()];
+ int i = 0;
+ for (InetAddress addr : addrs) {
+ result[i++] = addr.getHostAddress();
+ }
+ return result;
+ }
+
+ /**
+ * Trim leading zeros from IPv4 address strings
+ * Our base libraries will interpret that as octel..
+ * Must leave non v4 addresses and host names alone.
+ * For example, 192.168.000.010 -> 192.168.0.10
+ * TODO - fix base libraries and remove this function
+ * @param addr a string representing an ip addr
+ * @return a string propertly trimmed
+ */
+ public static String trimV4AddrZeros(String addr) {
+ if (addr == null) return null;
+ String[] octets = addr.split("\\.");
+ if (octets.length != 4) return addr;
+ StringBuilder builder = new StringBuilder(16);
+ String result = null;
+ for (int i = 0; i < 4; i++) {
+ try {
+ if (octets[i].length() > 3) return addr;
+ builder.append(Integer.parseInt(octets[i]));
+ } catch (NumberFormatException e) {
+ return addr;
+ }
+ if (i < 3) builder.append('.');
+ }
+ result = builder.toString();
+ return result;
+ }
+}
diff --git a/core/java/android/net/ProxyInfo.java b/core/java/android/net/ProxyInfo.java
new file mode 100644
index 0000000..7ea6bae
--- /dev/null
+++ b/core/java/android/net/ProxyInfo.java
@@ -0,0 +1,366 @@
+/*
+ * Copyright (C) 2010 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.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import org.apache.http.client.HttpClient;
+
+import java.net.InetSocketAddress;
+import java.net.URLConnection;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Describes a proxy configuration.
+ *
+ * Proxy configurations are already integrated within the Apache HTTP stack.
+ * So {@link URLConnection} and {@link HttpClient} will use them automatically.
+ *
+ * Other HTTP stacks will need to obtain the proxy info from
+ * {@link Proxy#PROXY_CHANGE_ACTION} broadcast as the extra {@link Proxy#EXTRA_PROXY_INFO}.
+ */
+public class ProxyInfo implements Parcelable {
+
+ private String mHost;
+ private int mPort;
+ private String mExclusionList;
+ private String[] mParsedExclusionList;
+
+ private Uri mPacFileUrl;
+ /**
+ *@hide
+ */
+ public static final String LOCAL_EXCL_LIST = "";
+ /**
+ *@hide
+ */
+ public static final int LOCAL_PORT = -1;
+ /**
+ *@hide
+ */
+ public static final String LOCAL_HOST = "localhost";
+
+ /**
+ * Constructs a {@link ProxyInfo} object that points at a Direct proxy
+ * on the specified host and port.
+ */
+ public static ProxyInfo buildDirectProxy(String host, int port) {
+ return new ProxyInfo(host, port, null);
+ }
+
+ /**
+ * Constructs a {@link ProxyInfo} object that points at a Direct proxy
+ * on the specified host and port.
+ *
+ * The proxy will not be used to access any host in exclusion list, exclList.
+ *
+ * @param exclList Hosts to exclude using the proxy on connections for. These
+ * hosts can use wildcards such as *.example.com.
+ */
+ public static ProxyInfo buildDirectProxy(String host, int port, List<String> exclList) {
+ String[] array = exclList.toArray(new String[exclList.size()]);
+ return new ProxyInfo(host, port, TextUtils.join(",", array), array);
+ }
+
+ /**
+ * Construct a {@link ProxyInfo} that will download and run the PAC script
+ * at the specified URL.
+ */
+ public static ProxyInfo buildPacProxy(Uri pacUri) {
+ return new ProxyInfo(pacUri);
+ }
+
+ /**
+ * Create a ProxyProperties that points at a HTTP Proxy.
+ * @hide
+ */
+ public ProxyInfo(String host, int port, String exclList) {
+ mHost = host;
+ mPort = port;
+ setExclusionList(exclList);
+ mPacFileUrl = Uri.EMPTY;
+ }
+
+ /**
+ * Create a ProxyProperties that points at a PAC URL.
+ * @hide
+ */
+ public ProxyInfo(Uri pacFileUrl) {
+ mHost = LOCAL_HOST;
+ mPort = LOCAL_PORT;
+ setExclusionList(LOCAL_EXCL_LIST);
+ if (pacFileUrl == null) {
+ throw new NullPointerException();
+ }
+ mPacFileUrl = pacFileUrl;
+ }
+
+ /**
+ * Create a ProxyProperties that points at a PAC URL.
+ * @hide
+ */
+ public ProxyInfo(String pacFileUrl) {
+ mHost = LOCAL_HOST;
+ mPort = LOCAL_PORT;
+ setExclusionList(LOCAL_EXCL_LIST);
+ mPacFileUrl = Uri.parse(pacFileUrl);
+ }
+
+ /**
+ * Only used in PacManager after Local Proxy is bound.
+ * @hide
+ */
+ public ProxyInfo(Uri pacFileUrl, int localProxyPort) {
+ mHost = LOCAL_HOST;
+ mPort = localProxyPort;
+ setExclusionList(LOCAL_EXCL_LIST);
+ if (pacFileUrl == null) {
+ throw new NullPointerException();
+ }
+ mPacFileUrl = pacFileUrl;
+ }
+
+ private ProxyInfo(String host, int port, String exclList, String[] parsedExclList) {
+ mHost = host;
+ mPort = port;
+ mExclusionList = exclList;
+ mParsedExclusionList = parsedExclList;
+ mPacFileUrl = Uri.EMPTY;
+ }
+
+ // copy constructor instead of clone
+ /**
+ * @hide
+ */
+ public ProxyInfo(ProxyInfo source) {
+ if (source != null) {
+ mHost = source.getHost();
+ mPort = source.getPort();
+ mPacFileUrl = source.mPacFileUrl;
+ mExclusionList = source.getExclusionListAsString();
+ mParsedExclusionList = source.mParsedExclusionList;
+ } else {
+ mPacFileUrl = Uri.EMPTY;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public InetSocketAddress getSocketAddress() {
+ InetSocketAddress inetSocketAddress = null;
+ try {
+ inetSocketAddress = new InetSocketAddress(mHost, mPort);
+ } catch (IllegalArgumentException e) { }
+ return inetSocketAddress;
+ }
+
+ /**
+ * Returns the URL of the current PAC script or null if there is
+ * no PAC script.
+ */
+ public Uri getPacFileUrl() {
+ return mPacFileUrl;
+ }
+
+ /**
+ * When configured to use a Direct Proxy this returns the host
+ * of the proxy.
+ */
+ public String getHost() {
+ return mHost;
+ }
+
+ /**
+ * When configured to use a Direct Proxy this returns the port
+ * of the proxy
+ */
+ public int getPort() {
+ return mPort;
+ }
+
+ /**
+ * When configured to use a Direct Proxy this returns the list
+ * of hosts for which the proxy is ignored.
+ */
+ public String[] getExclusionList() {
+ return mParsedExclusionList;
+ }
+
+ /**
+ * comma separated
+ * @hide
+ */
+ public String getExclusionListAsString() {
+ return mExclusionList;
+ }
+
+ // comma separated
+ private void setExclusionList(String exclusionList) {
+ mExclusionList = exclusionList;
+ if (mExclusionList == null) {
+ mParsedExclusionList = new String[0];
+ } else {
+ mParsedExclusionList = exclusionList.toLowerCase(Locale.ROOT).split(",");
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isValid() {
+ if (!Uri.EMPTY.equals(mPacFileUrl)) return true;
+ return Proxy.PROXY_VALID == Proxy.validate(mHost == null ? "" : mHost,
+ mPort == 0 ? "" : Integer.toString(mPort),
+ mExclusionList == null ? "" : mExclusionList);
+ }
+
+ /**
+ * @hide
+ */
+ public java.net.Proxy makeProxy() {
+ java.net.Proxy proxy = java.net.Proxy.NO_PROXY;
+ if (mHost != null) {
+ try {
+ InetSocketAddress inetSocketAddress = new InetSocketAddress(mHost, mPort);
+ proxy = new java.net.Proxy(java.net.Proxy.Type.HTTP, inetSocketAddress);
+ } catch (IllegalArgumentException e) {
+ }
+ }
+ return proxy;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ if (!Uri.EMPTY.equals(mPacFileUrl)) {
+ sb.append("PAC Script: ");
+ sb.append(mPacFileUrl);
+ } else if (mHost != null) {
+ sb.append("[");
+ sb.append(mHost);
+ sb.append("] ");
+ sb.append(Integer.toString(mPort));
+ if (mExclusionList != null) {
+ sb.append(" xl=").append(mExclusionList);
+ }
+ } else {
+ sb.append("[ProxyProperties.mHost == null]");
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof ProxyInfo)) return false;
+ ProxyInfo p = (ProxyInfo)o;
+ // If PAC URL is present in either then they must be equal.
+ // Other parameters will only be for fall back.
+ if (!Uri.EMPTY.equals(mPacFileUrl)) {
+ return mPacFileUrl.equals(p.getPacFileUrl()) && mPort == p.mPort;
+ }
+ if (!Uri.EMPTY.equals(p.mPacFileUrl)) {
+ return false;
+ }
+ if (mExclusionList != null && !mExclusionList.equals(p.getExclusionListAsString())) {
+ return false;
+ }
+ if (mHost != null && p.getHost() != null && mHost.equals(p.getHost()) == false) {
+ return false;
+ }
+ if (mHost != null && p.mHost == null) return false;
+ if (mHost == null && p.mHost != null) return false;
+ if (mPort != p.mPort) return false;
+ return true;
+ }
+
+ /**
+ * Implement the Parcelable interface
+ * @hide
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ /*
+ * generate hashcode based on significant fields
+ */
+ public int hashCode() {
+ return ((null == mHost) ? 0 : mHost.hashCode())
+ + ((null == mExclusionList) ? 0 : mExclusionList.hashCode())
+ + mPort;
+ }
+
+ /**
+ * Implement the Parcelable interface.
+ * @hide
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ if (!Uri.EMPTY.equals(mPacFileUrl)) {
+ dest.writeByte((byte)1);
+ mPacFileUrl.writeToParcel(dest, 0);
+ dest.writeInt(mPort);
+ return;
+ } else {
+ dest.writeByte((byte)0);
+ }
+ if (mHost != null) {
+ dest.writeByte((byte)1);
+ dest.writeString(mHost);
+ dest.writeInt(mPort);
+ } else {
+ dest.writeByte((byte)0);
+ }
+ dest.writeString(mExclusionList);
+ dest.writeStringArray(mParsedExclusionList);
+ }
+
+ /**
+ * Implement the Parcelable interface.
+ * @hide
+ */
+ public static final Creator<ProxyInfo> CREATOR =
+ new Creator<ProxyInfo>() {
+ public ProxyInfo createFromParcel(Parcel in) {
+ String host = null;
+ int port = 0;
+ if (in.readByte() != 0) {
+ Uri url = Uri.CREATOR.createFromParcel(in);
+ int localPort = in.readInt();
+ return new ProxyInfo(url, localPort);
+ }
+ if (in.readByte() != 0) {
+ host = in.readString();
+ port = in.readInt();
+ }
+ String exclList = in.readString();
+ String[] parsedExclList = in.readStringArray();
+ ProxyInfo proxyProperties =
+ new ProxyInfo(host, port, exclList, parsedExclList);
+ return proxyProperties;
+ }
+
+ public ProxyInfo[] newArray(int size) {
+ return new ProxyInfo[size];
+ }
+ };
+}
diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java
new file mode 100644
index 0000000..8b42bcd
--- /dev/null
+++ b/core/java/android/net/RouteInfo.java
@@ -0,0 +1,453 @@
+/*
+ * Copyright (C) 2011 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.os.Parcel;
+import android.os.Parcelable;
+
+import java.net.UnknownHostException;
+import java.net.InetAddress;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+
+import java.util.Collection;
+import java.util.Objects;
+
+/**
+ * Represents a network route.
+ * <p>
+ * This is used both to describe static network configuration and live network
+ * configuration information.
+ *
+ * A route contains three pieces of information:
+ * <ul>
+ * <li>a destination {@link IpPrefix} specifying the network destinations covered by this route.
+ * If this is {@code null} it indicates a default route of the address family (IPv4 or IPv6)
+ * implied by the gateway IP address.
+ * <li>a gateway {@link InetAddress} indicating the next hop to use. If this is {@code null} it
+ * indicates a directly-connected route.
+ * <li>an interface (which may be unspecified).
+ * </ul>
+ * Either the destination or the gateway may be {@code null}, but not both. If the
+ * destination and gateway are both specified, they must be of the same address family
+ * (IPv4 or IPv6).
+ */
+public class RouteInfo implements Parcelable {
+ /**
+ * The IP destination address for this route.
+ * TODO: Make this an IpPrefix.
+ */
+ private final LinkAddress mDestination;
+
+ /**
+ * The gateway address for this route.
+ */
+ private final InetAddress mGateway;
+
+ /**
+ * The interface for this route.
+ */
+ private final String mInterface;
+
+ private final boolean mIsDefault;
+ private final boolean mIsHost;
+ private final boolean mHasGateway;
+
+ /**
+ * Constructs a RouteInfo object.
+ *
+ * If destination is null, then gateway must be specified and the
+ * constructed route is either the IPv4 default route <code>0.0.0.0</code>
+ * if the gateway is an instance of {@link Inet4Address}, or the IPv6 default
+ * route <code>::/0</code> if gateway is an instance of
+ * {@link Inet6Address}.
+ * <p>
+ * destination and gateway may not both be null.
+ *
+ * @param destination the destination prefix
+ * @param gateway the IP address to route packets through
+ * @param iface the interface name to send packets on
+ *
+ * TODO: Convert to use IpPrefix.
+ *
+ * @hide
+ */
+ public RouteInfo(IpPrefix destination, InetAddress gateway, String iface) {
+ this(destination == null ? null :
+ new LinkAddress(destination.getAddress(), destination.getPrefixLength()),
+ gateway, iface);
+ }
+
+ /**
+ * @hide
+ */
+ public RouteInfo(LinkAddress destination, InetAddress gateway, String iface) {
+ if (destination == null) {
+ if (gateway != null) {
+ if (gateway instanceof Inet4Address) {
+ destination = new LinkAddress(Inet4Address.ANY, 0);
+ } else {
+ destination = new LinkAddress(Inet6Address.ANY, 0);
+ }
+ } else {
+ // no destination, no gateway. invalid.
+ throw new IllegalArgumentException("Invalid arguments passed in: " + gateway + "," +
+ destination);
+ }
+ }
+ if (gateway == null) {
+ if (destination.getAddress() instanceof Inet4Address) {
+ gateway = Inet4Address.ANY;
+ } else {
+ gateway = Inet6Address.ANY;
+ }
+ }
+ mHasGateway = (!gateway.isAnyLocalAddress());
+
+ mDestination = new LinkAddress(NetworkUtils.getNetworkPart(destination.getAddress(),
+ destination.getPrefixLength()), destination.getPrefixLength());
+ if ((destination.getAddress() instanceof Inet4Address &&
+ (gateway instanceof Inet4Address == false)) ||
+ (destination.getAddress() instanceof Inet6Address &&
+ (gateway instanceof Inet6Address == false))) {
+ throw new IllegalArgumentException("address family mismatch in RouteInfo constructor");
+ }
+ mGateway = gateway;
+ mInterface = iface;
+ mIsDefault = isDefault();
+ mIsHost = isHost();
+ }
+
+ /**
+ * Constructs a {@code RouteInfo} object.
+ *
+ * If destination is null, then gateway must be specified and the
+ * constructed route is either the IPv4 default route <code>0.0.0.0</code>
+ * if the gateway is an instance of {@link Inet4Address}, or the IPv6 default
+ * route <code>::/0</code> if gateway is an instance of {@link Inet6Address}.
+ * <p>
+ * Destination and gateway may not both be null.
+ *
+ * @param destination the destination address and prefix in an {@link IpPrefix}
+ * @param gateway the {@link InetAddress} to route packets through
+ *
+ * @hide
+ */
+ public RouteInfo(IpPrefix destination, InetAddress gateway) {
+ this(destination, gateway, null);
+ }
+
+ /**
+ * @hide
+ */
+ public RouteInfo(LinkAddress destination, InetAddress gateway) {
+ this(destination, gateway, null);
+ }
+
+ /**
+ * Constructs a default {@code RouteInfo} object.
+ *
+ * @param gateway the {@link InetAddress} to route packets through
+ *
+ * @hide
+ */
+ public RouteInfo(InetAddress gateway) {
+ this((LinkAddress) null, gateway, null);
+ }
+
+ /**
+ * Constructs a {@code RouteInfo} object representing a direct connected subnet.
+ *
+ * @param destination the {@link IpPrefix} describing the address and prefix
+ * length of the subnet.
+ *
+ * @hide
+ */
+ public RouteInfo(IpPrefix destination) {
+ this(destination, null, null);
+ }
+
+ /**
+ * @hide
+ */
+ public RouteInfo(LinkAddress destination) {
+ this(destination, null, null);
+ }
+
+ /**
+ * @hide
+ */
+ public static RouteInfo makeHostRoute(InetAddress host, String iface) {
+ return makeHostRoute(host, null, iface);
+ }
+
+ /**
+ * @hide
+ */
+ public static RouteInfo makeHostRoute(InetAddress host, InetAddress gateway, String iface) {
+ if (host == null) return null;
+
+ if (host instanceof Inet4Address) {
+ return new RouteInfo(new LinkAddress(host, 32), gateway, iface);
+ } else {
+ return new RouteInfo(new LinkAddress(host, 128), gateway, iface);
+ }
+ }
+
+ private boolean isHost() {
+ return (mDestination.getAddress() instanceof Inet4Address &&
+ mDestination.getPrefixLength() == 32) ||
+ (mDestination.getAddress() instanceof Inet6Address &&
+ mDestination.getPrefixLength() == 128);
+ }
+
+ private boolean isDefault() {
+ boolean val = false;
+ if (mGateway != null) {
+ if (mGateway instanceof Inet4Address) {
+ val = (mDestination == null || mDestination.getPrefixLength() == 0);
+ } else {
+ val = (mDestination == null || mDestination.getPrefixLength() == 0);
+ }
+ }
+ return val;
+ }
+
+ /**
+ * Retrieves the destination address and prefix length in the form of an {@link IpPrefix}.
+ *
+ * @return {@link IpPrefix} specifying the destination. This is never {@code null}.
+ */
+ public IpPrefix getDestination() {
+ return new IpPrefix(mDestination.getAddress(), mDestination.getPrefixLength());
+ }
+
+ /**
+ * TODO: Convert callers to use IpPrefix and then remove.
+ * @hide
+ */
+ public LinkAddress getDestinationLinkAddress() {
+ return mDestination;
+ }
+
+ /**
+ * Retrieves the gateway or next hop {@link InetAddress} for this route.
+ *
+ * @return {@link InetAddress} specifying the gateway or next hop. This may be
+ & {@code null} for a directly-connected route."
+ */
+ public InetAddress getGateway() {
+ return mGateway;
+ }
+
+ /**
+ * Retrieves the interface used for this route if specified, else {@code null}.
+ *
+ * @return The name of the interface used for this route.
+ */
+ public String getInterface() {
+ return mInterface;
+ }
+
+ /**
+ * Indicates if this route is a default route (ie, has no destination specified).
+ *
+ * @return {@code true} if the destination has a prefix length of 0.
+ */
+ public boolean isDefaultRoute() {
+ return mIsDefault;
+ }
+
+ /**
+ * Indicates if this route is a host route (ie, matches only a single host address).
+ *
+ * @return {@code true} if the destination has a prefix length of 32 or 128 for IPv4 or IPv6,
+ * respectively.
+ * @hide
+ */
+ public boolean isHostRoute() {
+ return mIsHost;
+ }
+
+ /**
+ * Indicates if this route has a next hop ({@code true}) or is directly-connected
+ * ({@code false}).
+ *
+ * @return {@code true} if a gateway is specified
+ * @hide
+ */
+ public boolean hasGateway() {
+ return mHasGateway;
+ }
+
+ /**
+ * Determines whether the destination and prefix of this route includes the specified
+ * address.
+ *
+ * @param destination A {@link InetAddress} to test to see if it would match this route.
+ * @return {@code true} if the destination and prefix length cover the given address.
+ */
+ public boolean matches(InetAddress destination) {
+ if (destination == null) return false;
+
+ // match the route destination and destination with prefix length
+ InetAddress dstNet = NetworkUtils.getNetworkPart(destination,
+ mDestination.getPrefixLength());
+
+ return mDestination.getAddress().equals(dstNet);
+ }
+
+ /**
+ * Find the route from a Collection of routes that best matches a given address.
+ * May return null if no routes are applicable.
+ * @param routes a Collection of RouteInfos to chose from
+ * @param dest the InetAddress your trying to get to
+ * @return the RouteInfo from the Collection that best fits the given address
+ *
+ * @hide
+ */
+ public static RouteInfo selectBestRoute(Collection<RouteInfo> routes, InetAddress dest) {
+ if ((routes == null) || (dest == null)) return null;
+
+ RouteInfo bestRoute = null;
+ // pick a longest prefix match under same address type
+ for (RouteInfo route : routes) {
+ if (NetworkUtils.addressTypeMatches(route.mDestination.getAddress(), dest)) {
+ if ((bestRoute != null) &&
+ (bestRoute.mDestination.getPrefixLength() >=
+ route.mDestination.getPrefixLength())) {
+ continue;
+ }
+ if (route.matches(dest)) bestRoute = route;
+ }
+ }
+ return bestRoute;
+ }
+
+ /**
+ * Returns a human-readable description of this object.
+ */
+ public String toString() {
+ String val = "";
+ if (mDestination != null) val = mDestination.toString();
+ val += " ->";
+ if (mGateway != null) val += " " + mGateway.getHostAddress();
+ if (mInterface != null) val += " " + mInterface;
+ return val;
+ }
+
+ /**
+ * Compares this RouteInfo object against the specified object and indicates if they are equal.
+ * @return {@code true} if the objects are equal, {@code false} otherwise.
+ */
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+
+ if (!(obj instanceof RouteInfo)) return false;
+
+ RouteInfo target = (RouteInfo) obj;
+
+ return Objects.equals(mDestination, target.getDestinationLinkAddress()) &&
+ Objects.equals(mGateway, target.getGateway()) &&
+ Objects.equals(mInterface, target.getInterface());
+ }
+
+ /**
+ * Returns a hashcode for this <code>RouteInfo</code> object.
+ */
+ public int hashCode() {
+ return (mDestination == null ? 0 : mDestination.hashCode() * 41)
+ + (mGateway == null ? 0 :mGateway.hashCode() * 47)
+ + (mInterface == null ? 0 :mInterface.hashCode() * 67)
+ + (mIsDefault ? 3 : 7);
+ }
+
+ /**
+ * Implement the Parcelable interface
+ * @hide
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Implement the Parcelable interface
+ * @hide
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ if (mDestination == null) {
+ dest.writeByte((byte) 0);
+ } else {
+ dest.writeByte((byte) 1);
+ dest.writeByteArray(mDestination.getAddress().getAddress());
+ dest.writeInt(mDestination.getPrefixLength());
+ }
+
+ if (mGateway == null) {
+ dest.writeByte((byte) 0);
+ } else {
+ dest.writeByte((byte) 1);
+ dest.writeByteArray(mGateway.getAddress());
+ }
+
+ dest.writeString(mInterface);
+ }
+
+ /**
+ * Implement the Parcelable interface.
+ * @hide
+ */
+ public static final Creator<RouteInfo> CREATOR =
+ new Creator<RouteInfo>() {
+ public RouteInfo createFromParcel(Parcel in) {
+ InetAddress destAddr = null;
+ int prefix = 0;
+ InetAddress gateway = null;
+
+ if (in.readByte() == 1) {
+ byte[] addr = in.createByteArray();
+ prefix = in.readInt();
+
+ try {
+ destAddr = InetAddress.getByAddress(addr);
+ } catch (UnknownHostException e) {}
+ }
+
+ if (in.readByte() == 1) {
+ byte[] addr = in.createByteArray();
+
+ try {
+ gateway = InetAddress.getByAddress(addr);
+ } catch (UnknownHostException e) {}
+ }
+
+ String iface = in.readString();
+
+ LinkAddress dest = null;
+
+ if (destAddr != null) {
+ dest = new LinkAddress(destAddr, prefix);
+ }
+
+ return new RouteInfo(dest, gateway, iface);
+ }
+
+ public RouteInfo[] newArray(int size) {
+ return new RouteInfo[size];
+ }
+ };
+}
diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
new file mode 100644
index 0000000..bc5e1b3
--- /dev/null
+++ b/core/jni/android_net_NetUtils.cpp
@@ -0,0 +1,337 @@
+/*
+ * Copyright 2008, 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.
+ */
+
+#define LOG_TAG "NetUtils"
+
+#include "jni.h"
+#include "JNIHelp.h"
+#include "NetdClient.h"
+#include "resolv_netid.h"
+#include <utils/misc.h>
+#include <android_runtime/AndroidRuntime.h>
+#include <utils/Log.h>
+#include <arpa/inet.h>
+#include <cutils/properties.h>
+
+extern "C" {
+int ifc_enable(const char *ifname);
+int ifc_disable(const char *ifname);
+int ifc_reset_connections(const char *ifname, int reset_mask);
+
+int dhcp_do_request(const char * const ifname,
+ const char *ipaddr,
+ const char *gateway,
+ uint32_t *prefixLength,
+ const char *dns[],
+ const char *server,
+ uint32_t *lease,
+ const char *vendorInfo,
+ const char *domains,
+ const char *mtu);
+
+int dhcp_do_request_renew(const char * const ifname,
+ const char *ipaddr,
+ const char *gateway,
+ uint32_t *prefixLength,
+ const char *dns[],
+ const char *server,
+ uint32_t *lease,
+ const char *vendorInfo,
+ const char *domains,
+ const char *mtu);
+
+int dhcp_stop(const char *ifname);
+int dhcp_release_lease(const char *ifname);
+char *dhcp_get_errmsg();
+}
+
+#define NETUTILS_PKG_NAME "android/net/NetworkUtils"
+
+namespace android {
+
+/*
+ * The following remembers the jfieldID's of the fields
+ * of the DhcpInfo Java object, so that we don't have
+ * to look them up every time.
+ */
+static struct fieldIds {
+ jmethodID clear;
+ jmethodID setInterfaceName;
+ jmethodID addLinkAddress;
+ jmethodID addGateway;
+ jmethodID addDns;
+ jmethodID setDomains;
+ jmethodID setServerAddress;
+ jmethodID setLeaseDuration;
+ jmethodID setVendorInfo;
+} dhcpResultsFieldIds;
+
+static jint android_net_utils_enableInterface(JNIEnv* env, jobject clazz, jstring ifname)
+{
+ int result;
+
+ const char *nameStr = env->GetStringUTFChars(ifname, NULL);
+ result = ::ifc_enable(nameStr);
+ env->ReleaseStringUTFChars(ifname, nameStr);
+ return (jint)result;
+}
+
+static jint android_net_utils_disableInterface(JNIEnv* env, jobject clazz, jstring ifname)
+{
+ int result;
+
+ const char *nameStr = env->GetStringUTFChars(ifname, NULL);
+ result = ::ifc_disable(nameStr);
+ env->ReleaseStringUTFChars(ifname, nameStr);
+ return (jint)result;
+}
+
+static jint android_net_utils_resetConnections(JNIEnv* env, jobject clazz,
+ jstring ifname, jint mask)
+{
+ int result;
+
+ const char *nameStr = env->GetStringUTFChars(ifname, NULL);
+
+ ALOGD("android_net_utils_resetConnections in env=%p clazz=%p iface=%s mask=0x%x\n",
+ env, clazz, nameStr, mask);
+
+ result = ::ifc_reset_connections(nameStr, mask);
+ env->ReleaseStringUTFChars(ifname, nameStr);
+ return (jint)result;
+}
+
+static jboolean android_net_utils_runDhcpCommon(JNIEnv* env, jobject clazz, jstring ifname,
+ jobject dhcpResults, bool renew)
+{
+ int result;
+ char ipaddr[PROPERTY_VALUE_MAX];
+ uint32_t prefixLength;
+ char gateway[PROPERTY_VALUE_MAX];
+ char dns1[PROPERTY_VALUE_MAX];
+ char dns2[PROPERTY_VALUE_MAX];
+ char dns3[PROPERTY_VALUE_MAX];
+ char dns4[PROPERTY_VALUE_MAX];
+ const char *dns[5] = {dns1, dns2, dns3, dns4, NULL};
+ char server[PROPERTY_VALUE_MAX];
+ uint32_t lease;
+ char vendorInfo[PROPERTY_VALUE_MAX];
+ char domains[PROPERTY_VALUE_MAX];
+ char mtu[PROPERTY_VALUE_MAX];
+
+ const char *nameStr = env->GetStringUTFChars(ifname, NULL);
+ if (nameStr == NULL) return (jboolean)false;
+
+ if (renew) {
+ result = ::dhcp_do_request_renew(nameStr, ipaddr, gateway, &prefixLength,
+ dns, server, &lease, vendorInfo, domains, mtu);
+ } else {
+ result = ::dhcp_do_request(nameStr, ipaddr, gateway, &prefixLength,
+ dns, server, &lease, vendorInfo, domains, mtu);
+ }
+ if (result != 0) {
+ ALOGD("dhcp_do_request failed : %s (%s)", nameStr, renew ? "renew" : "new");
+ }
+
+ env->ReleaseStringUTFChars(ifname, nameStr);
+ if (result == 0) {
+ env->CallVoidMethod(dhcpResults, dhcpResultsFieldIds.clear);
+
+ // set mIfaceName
+ // dhcpResults->setInterfaceName(ifname)
+ env->CallVoidMethod(dhcpResults, dhcpResultsFieldIds.setInterfaceName, ifname);
+
+ // set the linkAddress
+ // dhcpResults->addLinkAddress(inetAddress, prefixLength)
+ result = env->CallBooleanMethod(dhcpResults, dhcpResultsFieldIds.addLinkAddress,
+ env->NewStringUTF(ipaddr), prefixLength);
+ }
+
+ if (result == 0) {
+ // set the gateway
+ // dhcpResults->addGateway(gateway)
+ result = env->CallBooleanMethod(dhcpResults,
+ dhcpResultsFieldIds.addGateway, env->NewStringUTF(gateway));
+ }
+
+ if (result == 0) {
+ // dhcpResults->addDns(new InetAddress(dns1))
+ result = env->CallBooleanMethod(dhcpResults,
+ dhcpResultsFieldIds.addDns, env->NewStringUTF(dns1));
+ }
+
+ if (result == 0) {
+ env->CallVoidMethod(dhcpResults, dhcpResultsFieldIds.setDomains,
+ env->NewStringUTF(domains));
+
+ result = env->CallBooleanMethod(dhcpResults,
+ dhcpResultsFieldIds.addDns, env->NewStringUTF(dns2));
+
+ if (result == 0) {
+ result = env->CallBooleanMethod(dhcpResults,
+ dhcpResultsFieldIds.addDns, env->NewStringUTF(dns3));
+ if (result == 0) {
+ result = env->CallBooleanMethod(dhcpResults,
+ dhcpResultsFieldIds.addDns, env->NewStringUTF(dns4));
+ }
+ }
+ }
+
+ if (result == 0) {
+ // dhcpResults->setServerAddress(new InetAddress(server))
+ result = env->CallBooleanMethod(dhcpResults, dhcpResultsFieldIds.setServerAddress,
+ env->NewStringUTF(server));
+ }
+
+ if (result == 0) {
+ // dhcpResults->setLeaseDuration(lease)
+ env->CallVoidMethod(dhcpResults,
+ dhcpResultsFieldIds.setLeaseDuration, lease);
+
+ // dhcpResults->setVendorInfo(vendorInfo)
+ env->CallVoidMethod(dhcpResults, dhcpResultsFieldIds.setVendorInfo,
+ env->NewStringUTF(vendorInfo));
+ }
+ return (jboolean)(result == 0);
+}
+
+
+static jboolean android_net_utils_runDhcp(JNIEnv* env, jobject clazz, jstring ifname, jobject info)
+{
+ return android_net_utils_runDhcpCommon(env, clazz, ifname, info, false);
+}
+
+static jboolean android_net_utils_runDhcpRenew(JNIEnv* env, jobject clazz, jstring ifname, jobject info)
+{
+ return android_net_utils_runDhcpCommon(env, clazz, ifname, info, true);
+}
+
+
+static jboolean android_net_utils_stopDhcp(JNIEnv* env, jobject clazz, jstring ifname)
+{
+ int result;
+
+ const char *nameStr = env->GetStringUTFChars(ifname, NULL);
+ result = ::dhcp_stop(nameStr);
+ env->ReleaseStringUTFChars(ifname, nameStr);
+ return (jboolean)(result == 0);
+}
+
+static jboolean android_net_utils_releaseDhcpLease(JNIEnv* env, jobject clazz, jstring ifname)
+{
+ int result;
+
+ const char *nameStr = env->GetStringUTFChars(ifname, NULL);
+ result = ::dhcp_release_lease(nameStr);
+ env->ReleaseStringUTFChars(ifname, nameStr);
+ return (jboolean)(result == 0);
+}
+
+static jstring android_net_utils_getDhcpError(JNIEnv* env, jobject clazz)
+{
+ return env->NewStringUTF(::dhcp_get_errmsg());
+}
+
+static void android_net_utils_markSocket(JNIEnv *env, jobject thiz, jint socket, jint mark)
+{
+ if (setsockopt(socket, SOL_SOCKET, SO_MARK, &mark, sizeof(mark)) < 0) {
+ jniThrowException(env, "java/lang/IllegalStateException", "Error marking socket");
+ }
+}
+
+static void android_net_utils_bindProcessToNetwork(JNIEnv *env, jobject thiz, jint netId)
+{
+ setNetworkForProcess(netId);
+}
+
+static void android_net_utils_unbindProcessToNetwork(JNIEnv *env, jobject thiz)
+{
+ setNetworkForProcess(NETID_UNSET);
+}
+
+static jint android_net_utils_getNetworkBoundToProcess(JNIEnv *env, jobject thiz)
+{
+ return getNetworkForProcess();
+}
+
+static void android_net_utils_bindProcessToNetworkForHostResolution(JNIEnv *env, jobject thiz, jint netId)
+{
+ setNetworkForResolv(netId);
+}
+
+static void android_net_utils_unbindProcessToNetworkForHostResolution(JNIEnv *env, jobject thiz)
+{
+ setNetworkForResolv(NETID_UNSET);
+}
+
+static void android_net_utils_bindSocketToNetwork(JNIEnv *env, jobject thiz, jint socket, jint netId)
+{
+ setNetworkForSocket(netId, socket);
+}
+
+// ----------------------------------------------------------------------------
+
+/*
+ * JNI registration.
+ */
+static JNINativeMethod gNetworkUtilMethods[] = {
+ /* name, signature, funcPtr */
+
+ { "enableInterface", "(Ljava/lang/String;)I", (void *)android_net_utils_enableInterface },
+ { "disableInterface", "(Ljava/lang/String;)I", (void *)android_net_utils_disableInterface },
+ { "resetConnections", "(Ljava/lang/String;I)I", (void *)android_net_utils_resetConnections },
+ { "runDhcp", "(Ljava/lang/String;Landroid/net/DhcpResults;)Z", (void *)android_net_utils_runDhcp },
+ { "runDhcpRenew", "(Ljava/lang/String;Landroid/net/DhcpResults;)Z", (void *)android_net_utils_runDhcpRenew },
+ { "stopDhcp", "(Ljava/lang/String;)Z", (void *)android_net_utils_stopDhcp },
+ { "releaseDhcpLease", "(Ljava/lang/String;)Z", (void *)android_net_utils_releaseDhcpLease },
+ { "getDhcpError", "()Ljava/lang/String;", (void*) android_net_utils_getDhcpError },
+ { "markSocket", "(II)V", (void*) android_net_utils_markSocket },
+ { "bindProcessToNetwork", "(I)V", (void*) android_net_utils_bindProcessToNetwork },
+ { "getNetworkBoundToProcess", "()I", (void*) android_net_utils_getNetworkBoundToProcess },
+ { "unbindProcessToNetwork", "()V", (void*) android_net_utils_unbindProcessToNetwork },
+ { "bindProcessToNetworkForHostResolution", "(I)V", (void*) android_net_utils_bindProcessToNetworkForHostResolution },
+ { "unbindProcessToNetworkForHostResolution", "()V", (void*) android_net_utils_unbindProcessToNetworkForHostResolution },
+ { "bindSocketToNetwork", "(II)V", (void*) android_net_utils_bindSocketToNetwork },
+};
+
+int register_android_net_NetworkUtils(JNIEnv* env)
+{
+ jclass dhcpResultsClass = env->FindClass("android/net/DhcpResults");
+ LOG_FATAL_IF(dhcpResultsClass == NULL, "Unable to find class android/net/DhcpResults");
+ dhcpResultsFieldIds.clear =
+ env->GetMethodID(dhcpResultsClass, "clear", "()V");
+ dhcpResultsFieldIds.setInterfaceName =
+ env->GetMethodID(dhcpResultsClass, "setInterfaceName", "(Ljava/lang/String;)V");
+ dhcpResultsFieldIds.addLinkAddress =
+ env->GetMethodID(dhcpResultsClass, "addLinkAddress", "(Ljava/lang/String;I)Z");
+ dhcpResultsFieldIds.addGateway =
+ env->GetMethodID(dhcpResultsClass, "addGateway", "(Ljava/lang/String;)Z");
+ dhcpResultsFieldIds.addDns =
+ env->GetMethodID(dhcpResultsClass, "addDns", "(Ljava/lang/String;)Z");
+ dhcpResultsFieldIds.setDomains =
+ env->GetMethodID(dhcpResultsClass, "setDomains", "(Ljava/lang/String;)V");
+ dhcpResultsFieldIds.setServerAddress =
+ env->GetMethodID(dhcpResultsClass, "setServerAddress", "(Ljava/lang/String;)Z");
+ dhcpResultsFieldIds.setLeaseDuration =
+ env->GetMethodID(dhcpResultsClass, "setLeaseDuration", "(I)V");
+ dhcpResultsFieldIds.setVendorInfo =
+ env->GetMethodID(dhcpResultsClass, "setVendorInfo", "(Ljava/lang/String;)V");
+
+ return AndroidRuntime::registerNativeMethods(env,
+ NETUTILS_PKG_NAME, gNetworkUtilMethods, NELEM(gNetworkUtilMethods));
+}
+
+}; // namespace android
diff --git a/core/tests/coretests/res/raw/xt_qtaguid_iface_fmt_typical b/core/tests/coretests/res/raw/xt_qtaguid_iface_fmt_typical
new file mode 100644
index 0000000..656d5bb
--- /dev/null
+++ b/core/tests/coretests/res/raw/xt_qtaguid_iface_fmt_typical
@@ -0,0 +1,4 @@
+ifname total_skb_rx_bytes total_skb_rx_packets total_skb_tx_bytes total_skb_tx_packets
+rmnet2 4968 35 3081 39
+rmnet1 11153922 8051 190226 2468
+rmnet0 6824 16 5692 10
diff --git a/core/tests/coretests/res/raw/xt_qtaguid_iface_typical b/core/tests/coretests/res/raw/xt_qtaguid_iface_typical
new file mode 100644
index 0000000..610723a
--- /dev/null
+++ b/core/tests/coretests/res/raw/xt_qtaguid_iface_typical
@@ -0,0 +1,6 @@
+rmnet3 1 0 0 0 0 20822 501 1149991 815
+rmnet2 1 0 0 0 0 1594 15 1313 15
+rmnet1 1 0 0 0 0 207398 458 166918 565
+rmnet0 1 0 0 0 0 2112 24 700 10
+test1 1 1 2 3 4 5 6 7 8
+test2 0 1 2 3 4 5 6 7 8
diff --git a/core/tests/coretests/res/raw/xt_qtaguid_typical b/core/tests/coretests/res/raw/xt_qtaguid_typical
new file mode 100644
index 0000000..c1b0d25
--- /dev/null
+++ b/core/tests/coretests/res/raw/xt_qtaguid_typical
@@ -0,0 +1,71 @@
+idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packets rx_tcp_bytes rx_tcp_packets rx_udp_bytes rx_udp_packets rx_other_bytes rx_other_packets tx_tcp_bytes tx_tcp_packets tx_udp_bytes tx_udp_packets tx_other_bytes tx_other_packets
+2 wlan0 0x0 0 0 18621 96 2898 44 312 6 15897 58 2412 32 312 6 1010 16 1576 22
+3 wlan0 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+4 wlan0 0x0 1000 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+5 wlan0 0x0 1000 1 1949 13 1078 14 0 0 1600 10 349 3 0 0 600 10 478 4
+6 wlan0 0x0 10005 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+7 wlan0 0x0 10005 1 32081 38 5315 50 32081 38 0 0 0 0 5315 50 0 0 0 0
+8 wlan0 0x0 10011 0 35777 53 5718 57 0 0 0 0 35777 53 0 0 0 0 5718 57
+9 wlan0 0x0 10011 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+10 wlan0 0x0 10014 0 0 0 1098 13 0 0 0 0 0 0 0 0 0 0 1098 13
+11 wlan0 0x0 10014 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+12 wlan0 0x0 10021 0 562386 573 49228 549 0 0 0 0 562386 573 0 0 0 0 49228 549
+13 wlan0 0x0 10021 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+14 wlan0 0x0 10031 0 3425 5 586 6 0 0 0 0 3425 5 0 0 0 0 586 6
+15 wlan0 0x0 10031 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+16 wlan0 0x7fffff0100000000 10021 0 562386 573 49228 549 0 0 0 0 562386 573 0 0 0 0 49228 549
+17 wlan0 0x7fffff0100000000 10021 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+18 wlan0 0x7fffff0100000000 10031 0 3425 5 586 6 0 0 0 0 3425 5 0 0 0 0 586 6
+19 wlan0 0x7fffff0100000000 10031 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+20 rmnet2 0x0 0 0 547 5 118 2 40 1 243 1 264 3 0 0 62 1 56 1
+21 rmnet2 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+22 rmnet2 0x0 10001 0 1125899906842624 5 984 11 632 5 0 0 0 0 984 11 0 0 0 0
+23 rmnet2 0x0 10001 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+24 rmnet1 0x0 0 0 26736 174 7098 130 7210 97 18382 64 1144 13 2932 64 4054 64 112 2
+25 rmnet1 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+26 rmnet1 0x0 1000 0 75774 77 18038 78 75335 72 439 5 0 0 17668 73 370 5 0 0
+27 rmnet1 0x0 1000 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+28 rmnet1 0x0 10007 0 269945 578 111632 586 269945 578 0 0 0 0 111632 586 0 0 0 0
+29 rmnet1 0x0 10007 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+30 rmnet1 0x0 10011 0 1741256 6918 769778 7019 1741256 6918 0 0 0 0 769778 7019 0 0 0 0
+31 rmnet1 0x0 10011 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+32 rmnet1 0x0 10014 0 0 0 786 12 0 0 0 0 0 0 786 12 0 0 0 0
+33 rmnet1 0x0 10014 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+34 rmnet1 0x0 10021 0 433533 1454 393420 1604 433533 1454 0 0 0 0 393420 1604 0 0 0 0
+35 rmnet1 0x0 10021 1 21215 33 10278 33 21215 33 0 0 0 0 10278 33 0 0 0 0
+36 rmnet1 0x0 10036 0 6310 25 3284 29 6310 25 0 0 0 0 3284 29 0 0 0 0
+37 rmnet1 0x0 10036 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+38 rmnet1 0x0 10047 0 34264 47 3936 34 34264 47 0 0 0 0 3936 34 0 0 0 0
+39 rmnet1 0x0 10047 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+40 rmnet1 0x4e7700000000 10011 0 9187 27 4248 33 9187 27 0 0 0 0 4248 33 0 0 0 0
+41 rmnet1 0x4e7700000000 10011 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+42 rmnet1 0x1000000000000000 10007 0 2109 4 791 4 2109 4 0 0 0 0 791 4 0 0 0 0
+43 rmnet1 0x1000000000000000 10007 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+44 rmnet1 0x1000000400000000 10007 0 9811 22 6286 22 9811 22 0 0 0 0 6286 22 0 0 0 0
+45 rmnet1 0x1000000400000000 10007 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+46 rmnet1 0x1010000000000000 10021 0 164833 426 135392 527 164833 426 0 0 0 0 135392 527 0 0 0 0
+47 rmnet1 0x1010000000000000 10021 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+48 rmnet1 0x1144000400000000 10011 0 10112 18 3334 17 10112 18 0 0 0 0 3334 17 0 0 0 0
+49 rmnet1 0x1144000400000000 10011 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+50 rmnet1 0x1244000400000000 10011 0 1300 3 848 2 1300 3 0 0 0 0 848 2 0 0 0 0
+51 rmnet1 0x1244000400000000 10011 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+52 rmnet1 0x3000000000000000 10007 0 10389 14 1521 12 10389 14 0 0 0 0 1521 12 0 0 0 0
+53 rmnet1 0x3000000000000000 10007 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+54 rmnet1 0x3000000400000000 10007 0 238070 380 93938 404 238070 380 0 0 0 0 93938 404 0 0 0 0
+55 rmnet1 0x3000000400000000 10007 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+56 rmnet1 0x3010000000000000 10021 0 219110 578 227423 676 219110 578 0 0 0 0 227423 676 0 0 0 0
+57 rmnet1 0x3010000000000000 10021 1 742 3 1265 3 742 3 0 0 0 0 1265 3 0 0 0 0
+58 rmnet1 0x3020000000000000 10021 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+59 rmnet1 0x3020000000000000 10021 1 20473 30 9013 30 20473 30 0 0 0 0 9013 30 0 0 0 0
+60 rmnet1 0x3144000400000000 10011 0 43963 92 34414 116 43963 92 0 0 0 0 34414 116 0 0 0 0
+61 rmnet1 0x3144000400000000 10011 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+62 rmnet1 0x3244000400000000 10011 0 3486 8 1520 9 3486 8 0 0 0 0 1520 9 0 0 0 0
+63 rmnet1 0x3244000400000000 10011 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+64 rmnet1 0x7fffff0100000000 10021 0 29102 56 8865 60 29102 56 0 0 0 0 8865 60 0 0 0 0
+65 rmnet1 0x7fffff0100000000 10021 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+66 rmnet1 0x7fffff0300000000 1000 0 995 13 14145 14 995 13 0 0 0 0 14145 14 0 0 0 0
+67 rmnet1 0x7fffff0300000000 1000 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+68 rmnet0 0x0 0 0 4312 49 1288 23 0 0 0 0 4312 49 0 0 0 0 1288 23
+69 rmnet0 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+70 rmnet0 0x0 10080 0 22266 30 20976 30 0 0 0 0 22266 30 0 0 0 0 20976 30
+71 rmnet0 0x0 10080 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
diff --git a/core/tests/coretests/src/android/net/LinkAddressTest.java b/core/tests/coretests/src/android/net/LinkAddressTest.java
new file mode 100644
index 0000000..814ecdd
--- /dev/null
+++ b/core/tests/coretests/src/android/net/LinkAddressTest.java
@@ -0,0 +1,331 @@
+/*
+ * Copyright (C) 2013 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 java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InterfaceAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import android.net.LinkAddress;
+import android.os.Parcel;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import static android.system.OsConstants.IFA_F_DEPRECATED;
+import static android.system.OsConstants.IFA_F_PERMANENT;
+import static android.system.OsConstants.IFA_F_TENTATIVE;
+import static android.system.OsConstants.RT_SCOPE_HOST;
+import static android.system.OsConstants.RT_SCOPE_LINK;
+import static android.system.OsConstants.RT_SCOPE_SITE;
+import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
+
+/**
+ * Tests for {@link LinkAddress}.
+ */
+public class LinkAddressTest extends AndroidTestCase {
+
+ private static final String V4 = "192.0.2.1";
+ private static final String V6 = "2001:db8::1";
+ private static final InetAddress V4_ADDRESS = NetworkUtils.numericToInetAddress(V4);
+ private static final InetAddress V6_ADDRESS = NetworkUtils.numericToInetAddress(V6);
+
+ public void testConstructors() throws SocketException {
+ LinkAddress address;
+
+ // Valid addresses work as expected.
+ address = new LinkAddress(V4_ADDRESS, 25);
+ assertEquals(V4_ADDRESS, address.getAddress());
+ assertEquals(25, address.getPrefixLength());
+ assertEquals(0, address.getFlags());
+ assertEquals(RT_SCOPE_UNIVERSE, address.getScope());
+
+ address = new LinkAddress(V6_ADDRESS, 127);
+ assertEquals(V6_ADDRESS, address.getAddress());
+ assertEquals(127, address.getPrefixLength());
+ assertEquals(0, address.getFlags());
+ assertEquals(RT_SCOPE_UNIVERSE, address.getScope());
+
+ // Nonsensical flags/scopes or combinations thereof are acceptable.
+ address = new LinkAddress(V6 + "/64", IFA_F_DEPRECATED | IFA_F_PERMANENT, RT_SCOPE_LINK);
+ assertEquals(V6_ADDRESS, address.getAddress());
+ assertEquals(64, address.getPrefixLength());
+ assertEquals(IFA_F_DEPRECATED | IFA_F_PERMANENT, address.getFlags());
+ assertEquals(RT_SCOPE_LINK, address.getScope());
+
+ address = new LinkAddress(V4 + "/23", 123, 456);
+ assertEquals(V4_ADDRESS, address.getAddress());
+ assertEquals(23, address.getPrefixLength());
+ assertEquals(123, address.getFlags());
+ assertEquals(456, address.getScope());
+
+ // InterfaceAddress doesn't have a constructor. Fetch some from an interface.
+ List<InterfaceAddress> addrs = NetworkInterface.getByName("lo").getInterfaceAddresses();
+
+ // We expect to find 127.0.0.1/8 and ::1/128, in any order.
+ LinkAddress ipv4Loopback, ipv6Loopback;
+ assertEquals(2, addrs.size());
+ if (addrs.get(0).getAddress() instanceof Inet4Address) {
+ ipv4Loopback = new LinkAddress(addrs.get(0));
+ ipv6Loopback = new LinkAddress(addrs.get(1));
+ } else {
+ ipv4Loopback = new LinkAddress(addrs.get(1));
+ ipv6Loopback = new LinkAddress(addrs.get(0));
+ }
+
+ assertEquals(NetworkUtils.numericToInetAddress("127.0.0.1"), ipv4Loopback.getAddress());
+ assertEquals(8, ipv4Loopback.getPrefixLength());
+
+ assertEquals(NetworkUtils.numericToInetAddress("::1"), ipv6Loopback.getAddress());
+ assertEquals(128, ipv6Loopback.getPrefixLength());
+
+ // Null addresses are rejected.
+ try {
+ address = new LinkAddress(null, 24);
+ fail("Null InetAddress should cause IllegalArgumentException");
+ } catch(IllegalArgumentException expected) {}
+
+ try {
+ address = new LinkAddress((String) null, IFA_F_PERMANENT, RT_SCOPE_UNIVERSE);
+ fail("Null string should cause IllegalArgumentException");
+ } catch(IllegalArgumentException expected) {}
+
+ try {
+ address = new LinkAddress((InterfaceAddress) null);
+ fail("Null string should cause NullPointerException");
+ } catch(NullPointerException expected) {}
+
+ // Invalid prefix lengths are rejected.
+ try {
+ address = new LinkAddress(V4_ADDRESS, -1);
+ fail("Negative IPv4 prefix length should cause IllegalArgumentException");
+ } catch(IllegalArgumentException expected) {}
+
+ try {
+ address = new LinkAddress(V6_ADDRESS, -1);
+ fail("Negative IPv6 prefix length should cause IllegalArgumentException");
+ } catch(IllegalArgumentException expected) {}
+
+ try {
+ address = new LinkAddress(V4_ADDRESS, 33);
+ fail("/33 IPv4 prefix length should cause IllegalArgumentException");
+ } catch(IllegalArgumentException expected) {}
+
+ try {
+ address = new LinkAddress(V4 + "/33", IFA_F_PERMANENT, RT_SCOPE_UNIVERSE);
+ fail("/33 IPv4 prefix length should cause IllegalArgumentException");
+ } catch(IllegalArgumentException expected) {}
+
+
+ try {
+ address = new LinkAddress(V6_ADDRESS, 129, IFA_F_PERMANENT, RT_SCOPE_UNIVERSE);
+ fail("/129 IPv6 prefix length should cause IllegalArgumentException");
+ } catch(IllegalArgumentException expected) {}
+
+ try {
+ address = new LinkAddress(V6 + "/129", IFA_F_PERMANENT, RT_SCOPE_UNIVERSE);
+ fail("/129 IPv6 prefix length should cause IllegalArgumentException");
+ } catch(IllegalArgumentException expected) {}
+
+ // Multicast addresses are rejected.
+ try {
+ address = new LinkAddress("224.0.0.2/32");
+ fail("IPv4 multicast address should cause IllegalArgumentException");
+ } catch(IllegalArgumentException expected) {}
+
+ try {
+ address = new LinkAddress("ff02::1/128");
+ fail("IPv6 multicast address should cause IllegalArgumentException");
+ } catch(IllegalArgumentException expected) {}
+ }
+
+ public void testAddressScopes() {
+ assertEquals(RT_SCOPE_HOST, new LinkAddress("::/128").getScope());
+ assertEquals(RT_SCOPE_HOST, new LinkAddress("0.0.0.0/32").getScope());
+
+ assertEquals(RT_SCOPE_LINK, new LinkAddress("::1/128").getScope());
+ assertEquals(RT_SCOPE_LINK, new LinkAddress("127.0.0.5/8").getScope());
+ assertEquals(RT_SCOPE_LINK, new LinkAddress("fe80::ace:d00d/64").getScope());
+ assertEquals(RT_SCOPE_LINK, new LinkAddress("169.254.5.12/16").getScope());
+
+ assertEquals(RT_SCOPE_SITE, new LinkAddress("fec0::dead/64").getScope());
+
+ assertEquals(RT_SCOPE_UNIVERSE, new LinkAddress("10.1.2.3/21").getScope());
+ assertEquals(RT_SCOPE_UNIVERSE, new LinkAddress("192.0.2.1/25").getScope());
+ assertEquals(RT_SCOPE_UNIVERSE, new LinkAddress("2001:db8::/64").getScope());
+ assertEquals(RT_SCOPE_UNIVERSE, new LinkAddress("5000::/127").getScope());
+ }
+
+ private void assertIsSameAddressAs(LinkAddress l1, LinkAddress l2) {
+ assertTrue(l1 + " unexpectedly does not have same address as " + l2,
+ l1.isSameAddressAs(l2));
+ assertTrue(l2 + " unexpectedly does not have same address as " + l1,
+ l2.isSameAddressAs(l1));
+ }
+
+ private void assertIsNotSameAddressAs(LinkAddress l1, LinkAddress l2) {
+ assertFalse(l1 + " unexpectedly has same address as " + l2,
+ l1.isSameAddressAs(l2));
+ assertFalse(l2 + " unexpectedly has same address as " + l1,
+ l1.isSameAddressAs(l2));
+ }
+
+ private void assertLinkAddressesEqual(LinkAddress l1, LinkAddress l2) {
+ assertTrue(l1 + " unexpectedly not equal to " + l2, l1.equals(l2));
+ assertTrue(l2 + " unexpectedly not equal to " + l1, l2.equals(l1));
+ assertEquals(l1.hashCode(), l2.hashCode());
+ }
+
+ private void assertLinkAddressesNotEqual(LinkAddress l1, LinkAddress l2) {
+ assertFalse(l1 + " unexpectedly equal to " + l2, l1.equals(l2));
+ assertFalse(l2 + " unexpectedly equal to " + l1, l2.equals(l1));
+ }
+
+ public void testEqualsAndSameAddressAs() {
+ LinkAddress l1, l2, l3;
+
+ l1 = new LinkAddress("2001:db8::1/64");
+ l2 = new LinkAddress("2001:db8::1/64");
+ assertLinkAddressesEqual(l1, l2);
+ assertIsSameAddressAs(l1, l2);
+
+ l2 = new LinkAddress("2001:db8::1/65");
+ assertLinkAddressesNotEqual(l1, l2);
+ assertIsNotSameAddressAs(l1, l2);
+
+ l2 = new LinkAddress("2001:db8::2/64");
+ assertLinkAddressesNotEqual(l1, l2);
+ assertIsNotSameAddressAs(l1, l2);
+
+
+ l1 = new LinkAddress("192.0.2.1/24");
+ l2 = new LinkAddress("192.0.2.1/24");
+ assertLinkAddressesEqual(l1, l2);
+ assertIsSameAddressAs(l1, l2);
+
+ l2 = new LinkAddress("192.0.2.1/23");
+ assertLinkAddressesNotEqual(l1, l2);
+ assertIsNotSameAddressAs(l1, l2);
+
+ l2 = new LinkAddress("192.0.2.2/24");
+ assertLinkAddressesNotEqual(l1, l2);
+ assertIsNotSameAddressAs(l1, l2);
+
+
+ // Check equals() and isSameAddressAs() on identical addresses with different flags.
+ l1 = new LinkAddress(V6_ADDRESS, 64);
+ l2 = new LinkAddress(V6_ADDRESS, 64, 0, RT_SCOPE_UNIVERSE);
+ assertLinkAddressesEqual(l1, l2);
+ assertIsSameAddressAs(l1, l2);
+
+ l2 = new LinkAddress(V6_ADDRESS, 64, IFA_F_DEPRECATED, RT_SCOPE_UNIVERSE);
+ assertLinkAddressesNotEqual(l1, l2);
+ assertIsSameAddressAs(l1, l2);
+
+ // Check equals() and isSameAddressAs() on identical addresses with different scope.
+ l1 = new LinkAddress(V4_ADDRESS, 24);
+ l2 = new LinkAddress(V4_ADDRESS, 24, 0, RT_SCOPE_UNIVERSE);
+ assertLinkAddressesEqual(l1, l2);
+ assertIsSameAddressAs(l1, l2);
+
+ l2 = new LinkAddress(V4_ADDRESS, 24, 0, RT_SCOPE_HOST);
+ assertLinkAddressesNotEqual(l1, l2);
+ assertIsSameAddressAs(l1, l2);
+
+ // Addresses with the same start or end bytes aren't equal between families.
+ l1 = new LinkAddress("32.1.13.184/24");
+ l2 = new LinkAddress("2001:db8::1/24");
+ l3 = new LinkAddress("::2001:db8/24");
+
+ byte[] ipv4Bytes = l1.getAddress().getAddress();
+ byte[] l2FirstIPv6Bytes = Arrays.copyOf(l2.getAddress().getAddress(), 4);
+ byte[] l3LastIPv6Bytes = Arrays.copyOfRange(l3.getAddress().getAddress(), 12, 16);
+ assertTrue(Arrays.equals(ipv4Bytes, l2FirstIPv6Bytes));
+ assertTrue(Arrays.equals(ipv4Bytes, l3LastIPv6Bytes));
+
+ assertLinkAddressesNotEqual(l1, l2);
+ assertIsNotSameAddressAs(l1, l2);
+
+ assertLinkAddressesNotEqual(l1, l3);
+ assertIsNotSameAddressAs(l1, l3);
+
+ // Because we use InetAddress, an IPv4 address is equal to its IPv4-mapped address.
+ // TODO: Investigate fixing this.
+ String addressString = V4 + "/24";
+ l1 = new LinkAddress(addressString);
+ l2 = new LinkAddress("::ffff:" + addressString);
+ assertLinkAddressesEqual(l1, l2);
+ assertIsSameAddressAs(l1, l2);
+ }
+
+ public void testHashCode() {
+ LinkAddress l;
+
+ l = new LinkAddress(V4_ADDRESS, 23);
+ assertEquals(-982787, l.hashCode());
+
+ l = new LinkAddress(V4_ADDRESS, 23, 0, RT_SCOPE_HOST);
+ assertEquals(-971865, l.hashCode());
+
+ l = new LinkAddress(V4_ADDRESS, 27);
+ assertEquals(-982743, l.hashCode());
+
+ l = new LinkAddress(V6_ADDRESS, 64);
+ assertEquals(1076522926, l.hashCode());
+
+ l = new LinkAddress(V6_ADDRESS, 128);
+ assertEquals(1076523630, l.hashCode());
+
+ l = new LinkAddress(V6_ADDRESS, 128, IFA_F_TENTATIVE, RT_SCOPE_UNIVERSE);
+ assertEquals(1076524846, l.hashCode());
+ }
+
+ private LinkAddress passThroughParcel(LinkAddress l) {
+ Parcel p = Parcel.obtain();
+ LinkAddress l2 = null;
+ try {
+ l.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ l2 = LinkAddress.CREATOR.createFromParcel(p);
+ } finally {
+ p.recycle();
+ }
+ assertNotNull(l2);
+ return l2;
+ }
+
+ private void assertParcelingIsLossless(LinkAddress l) {
+ LinkAddress l2 = passThroughParcel(l);
+ assertEquals(l, l2);
+ }
+
+ public void testParceling() {
+ LinkAddress l;
+
+ l = new LinkAddress(V6_ADDRESS, 64, 123, 456);
+ assertParcelingIsLossless(l);
+
+ l = new LinkAddress(V4 + "/28", IFA_F_PERMANENT, RT_SCOPE_LINK);
+ assertParcelingIsLossless(l);
+ }
+}
diff --git a/core/tests/coretests/src/android/net/LinkPropertiesTest.java b/core/tests/coretests/src/android/net/LinkPropertiesTest.java
new file mode 100644
index 0000000..e649baa
--- /dev/null
+++ b/core/tests/coretests/src/android/net/LinkPropertiesTest.java
@@ -0,0 +1,440 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.net.LinkProperties;
+import android.net.RouteInfo;
+import android.system.OsConstants;
+import android.test.suitebuilder.annotation.SmallTest;
+import junit.framework.TestCase;
+
+import java.net.InetAddress;
+import java.util.ArrayList;
+
+public class LinkPropertiesTest extends TestCase {
+ private static InetAddress ADDRV4 = NetworkUtils.numericToInetAddress("75.208.6.1");
+ private static InetAddress ADDRV6 = NetworkUtils.numericToInetAddress(
+ "2001:0db8:85a3:0000:0000:8a2e:0370:7334");
+ private static InetAddress DNS1 = NetworkUtils.numericToInetAddress("75.208.7.1");
+ private static InetAddress DNS2 = NetworkUtils.numericToInetAddress("69.78.7.1");
+ private static InetAddress GATEWAY1 = NetworkUtils.numericToInetAddress("75.208.8.1");
+ private static InetAddress GATEWAY2 = NetworkUtils.numericToInetAddress("69.78.8.1");
+ private static String NAME = "qmi0";
+ private static int MTU = 1500;
+
+ private static LinkAddress LINKADDRV4 = new LinkAddress(ADDRV4, 32);
+ private static LinkAddress LINKADDRV6 = new LinkAddress(ADDRV6, 128);
+
+ public void assertLinkPropertiesEqual(LinkProperties source, LinkProperties target) {
+ // Check implementation of equals(), element by element.
+ assertTrue(source.isIdenticalInterfaceName(target));
+ assertTrue(target.isIdenticalInterfaceName(source));
+
+ assertTrue(source.isIdenticalAddresses(target));
+ assertTrue(target.isIdenticalAddresses(source));
+
+ assertTrue(source.isIdenticalDnses(target));
+ assertTrue(target.isIdenticalDnses(source));
+
+ assertTrue(source.isIdenticalRoutes(target));
+ assertTrue(target.isIdenticalRoutes(source));
+
+ assertTrue(source.isIdenticalHttpProxy(target));
+ assertTrue(target.isIdenticalHttpProxy(source));
+
+ assertTrue(source.isIdenticalStackedLinks(target));
+ assertTrue(target.isIdenticalStackedLinks(source));
+
+ assertTrue(source.isIdenticalMtu(target));
+ assertTrue(target.isIdenticalMtu(source));
+
+ // Check result of equals().
+ assertTrue(source.equals(target));
+ assertTrue(target.equals(source));
+
+ // Check hashCode.
+ assertEquals(source.hashCode(), target.hashCode());
+ }
+
+ @SmallTest
+ public void testEqualsNull() {
+ LinkProperties source = new LinkProperties();
+ LinkProperties target = new LinkProperties();
+
+ assertFalse(source == target);
+ assertLinkPropertiesEqual(source, target);
+ }
+
+ @SmallTest
+ public void testEqualsSameOrder() {
+ try {
+ LinkProperties source = new LinkProperties();
+ source.setInterfaceName(NAME);
+ // set 2 link addresses
+ source.addLinkAddress(LINKADDRV4);
+ source.addLinkAddress(LINKADDRV6);
+ // set 2 dnses
+ source.addDnsServer(DNS1);
+ source.addDnsServer(DNS2);
+ // set 2 gateways
+ source.addRoute(new RouteInfo(GATEWAY1));
+ source.addRoute(new RouteInfo(GATEWAY2));
+ source.setMtu(MTU);
+
+ LinkProperties target = new LinkProperties();
+
+ // All fields are same
+ target.setInterfaceName(NAME);
+ target.addLinkAddress(LINKADDRV4);
+ target.addLinkAddress(LINKADDRV6);
+ target.addDnsServer(DNS1);
+ target.addDnsServer(DNS2);
+ target.addRoute(new RouteInfo(GATEWAY1));
+ target.addRoute(new RouteInfo(GATEWAY2));
+ target.setMtu(MTU);
+
+ assertLinkPropertiesEqual(source, target);
+
+ target.clear();
+ // change Interface Name
+ target.setInterfaceName("qmi1");
+ target.addLinkAddress(LINKADDRV4);
+ target.addLinkAddress(LINKADDRV6);
+ target.addDnsServer(DNS1);
+ target.addDnsServer(DNS2);
+ target.addRoute(new RouteInfo(GATEWAY1));
+ target.addRoute(new RouteInfo(GATEWAY2));
+ target.setMtu(MTU);
+ assertFalse(source.equals(target));
+
+ target.clear();
+ target.setInterfaceName(NAME);
+ // change link addresses
+ target.addLinkAddress(new LinkAddress(
+ NetworkUtils.numericToInetAddress("75.208.6.2"), 32));
+ target.addLinkAddress(LINKADDRV6);
+ target.addDnsServer(DNS1);
+ target.addDnsServer(DNS2);
+ target.addRoute(new RouteInfo(GATEWAY1));
+ target.addRoute(new RouteInfo(GATEWAY2));
+ target.setMtu(MTU);
+ assertFalse(source.equals(target));
+
+ target.clear();
+ target.setInterfaceName(NAME);
+ target.addLinkAddress(LINKADDRV4);
+ target.addLinkAddress(LINKADDRV6);
+ // change dnses
+ target.addDnsServer(NetworkUtils.numericToInetAddress("75.208.7.2"));
+ target.addDnsServer(DNS2);
+ target.addRoute(new RouteInfo(GATEWAY1));
+ target.addRoute(new RouteInfo(GATEWAY2));
+ target.setMtu(MTU);
+ assertFalse(source.equals(target));
+
+ target.clear();
+ target.setInterfaceName(NAME);
+ target.addLinkAddress(LINKADDRV4);
+ target.addLinkAddress(LINKADDRV6);
+ target.addDnsServer(DNS1);
+ target.addDnsServer(DNS2);
+ // change gateway
+ target.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress("75.208.8.2")));
+ target.addRoute(new RouteInfo(GATEWAY2));
+ target.setMtu(MTU);
+ assertFalse(source.equals(target));
+
+ target.clear();
+ target.setInterfaceName(NAME);
+ target.addLinkAddress(LINKADDRV4);
+ target.addLinkAddress(LINKADDRV6);
+ target.addDnsServer(DNS1);
+ target.addDnsServer(DNS2);
+ target.addRoute(new RouteInfo(GATEWAY1));
+ target.addRoute(new RouteInfo(GATEWAY2));
+ // change mtu
+ target.setMtu(1440);
+ assertFalse(source.equals(target));
+
+ } catch (Exception e) {
+ throw new RuntimeException(e.toString());
+ //fail();
+ }
+ }
+
+ @SmallTest
+ public void testEqualsDifferentOrder() {
+ try {
+ LinkProperties source = new LinkProperties();
+ source.setInterfaceName(NAME);
+ // set 2 link addresses
+ source.addLinkAddress(LINKADDRV4);
+ source.addLinkAddress(LINKADDRV6);
+ // set 2 dnses
+ source.addDnsServer(DNS1);
+ source.addDnsServer(DNS2);
+ // set 2 gateways
+ source.addRoute(new RouteInfo(GATEWAY1));
+ source.addRoute(new RouteInfo(GATEWAY2));
+ source.setMtu(MTU);
+
+ LinkProperties target = new LinkProperties();
+ // Exchange order
+ target.setInterfaceName(NAME);
+ target.addLinkAddress(LINKADDRV6);
+ target.addLinkAddress(LINKADDRV4);
+ target.addDnsServer(DNS2);
+ target.addDnsServer(DNS1);
+ target.addRoute(new RouteInfo(GATEWAY2));
+ target.addRoute(new RouteInfo(GATEWAY1));
+ target.setMtu(MTU);
+
+ assertLinkPropertiesEqual(source, target);
+ } catch (Exception e) {
+ fail();
+ }
+ }
+
+ @SmallTest
+ public void testEqualsDuplicated() {
+ try {
+ LinkProperties source = new LinkProperties();
+ // set 3 link addresses, eg, [A, A, B]
+ source.addLinkAddress(LINKADDRV4);
+ source.addLinkAddress(LINKADDRV4);
+ source.addLinkAddress(LINKADDRV6);
+
+ LinkProperties target = new LinkProperties();
+ // set 3 link addresses, eg, [A, B, B]
+ target.addLinkAddress(LINKADDRV4);
+ target.addLinkAddress(LINKADDRV6);
+ target.addLinkAddress(LINKADDRV6);
+
+ assertLinkPropertiesEqual(source, target);
+ } catch (Exception e) {
+ fail();
+ }
+ }
+
+ private void assertAllRoutesHaveInterface(String iface, LinkProperties lp) {
+ for (RouteInfo r : lp.getRoutes()) {
+ assertEquals(iface, r.getInterface());
+ }
+ }
+
+ @SmallTest
+ public void testRouteInterfaces() {
+ LinkAddress prefix = new LinkAddress(
+ NetworkUtils.numericToInetAddress("2001:db8::"), 32);
+ InetAddress address = ADDRV6;
+
+ // Add a route with no interface to a LinkProperties with no interface. No errors.
+ LinkProperties lp = new LinkProperties();
+ RouteInfo r = new RouteInfo(prefix, address, null);
+ lp.addRoute(r);
+ assertEquals(1, lp.getRoutes().size());
+ assertAllRoutesHaveInterface(null, lp);
+
+ // Add a route with an interface. Except an exception.
+ r = new RouteInfo(prefix, address, "wlan0");
+ try {
+ lp.addRoute(r);
+ fail("Adding wlan0 route to LP with no interface, expect exception");
+ } catch (IllegalArgumentException expected) {}
+
+ // Change the interface name. All the routes should change their interface name too.
+ lp.setInterfaceName("rmnet0");
+ assertAllRoutesHaveInterface("rmnet0", lp);
+
+ // Now add a route with the wrong interface. This causes an exception too.
+ try {
+ lp.addRoute(r);
+ fail("Adding wlan0 route to rmnet0 LP, expect exception");
+ } catch (IllegalArgumentException expected) {}
+
+ // If the interface name matches, the route is added.
+ lp.setInterfaceName("wlan0");
+ lp.addRoute(r);
+ assertEquals(2, lp.getRoutes().size());
+ assertAllRoutesHaveInterface("wlan0", lp);
+
+ // Routes with null interfaces are converted to wlan0.
+ r = RouteInfo.makeHostRoute(ADDRV6, null);
+ lp.addRoute(r);
+ assertEquals(3, lp.getRoutes().size());
+ assertAllRoutesHaveInterface("wlan0", lp);
+
+ // Check comparisons work.
+ LinkProperties lp2 = new LinkProperties(lp);
+ assertAllRoutesHaveInterface("wlan0", lp);
+ assertEquals(0, lp.compareAllRoutes(lp2).added.size());
+ assertEquals(0, lp.compareAllRoutes(lp2).removed.size());
+
+ lp2.setInterfaceName("p2p0");
+ assertAllRoutesHaveInterface("p2p0", lp2);
+ assertEquals(3, lp.compareAllRoutes(lp2).added.size());
+ assertEquals(3, lp.compareAllRoutes(lp2).removed.size());
+ }
+
+ @SmallTest
+ public void testStackedInterfaces() {
+ LinkProperties rmnet0 = new LinkProperties();
+ rmnet0.setInterfaceName("rmnet0");
+ rmnet0.addLinkAddress(LINKADDRV6);
+
+ LinkProperties clat4 = new LinkProperties();
+ clat4.setInterfaceName("clat4");
+ clat4.addLinkAddress(LINKADDRV4);
+
+ assertEquals(0, rmnet0.getStackedLinks().size());
+ assertEquals(1, rmnet0.getAddresses().size());
+ assertEquals(1, rmnet0.getLinkAddresses().size());
+ assertEquals(1, rmnet0.getAllAddresses().size());
+ assertEquals(1, rmnet0.getAllLinkAddresses().size());
+
+ rmnet0.addStackedLink(clat4);
+ assertEquals(1, rmnet0.getStackedLinks().size());
+ assertEquals(1, rmnet0.getAddresses().size());
+ assertEquals(1, rmnet0.getLinkAddresses().size());
+ assertEquals(2, rmnet0.getAllAddresses().size());
+ assertEquals(2, rmnet0.getAllLinkAddresses().size());
+
+ rmnet0.addStackedLink(clat4);
+ assertEquals(1, rmnet0.getStackedLinks().size());
+ assertEquals(1, rmnet0.getAddresses().size());
+ assertEquals(1, rmnet0.getLinkAddresses().size());
+ assertEquals(2, rmnet0.getAllAddresses().size());
+ assertEquals(2, rmnet0.getAllLinkAddresses().size());
+
+ assertEquals(0, clat4.getStackedLinks().size());
+
+ // Modify an item in the returned collection to see what happens.
+ for (LinkProperties link : rmnet0.getStackedLinks()) {
+ if (link.getInterfaceName().equals("clat4")) {
+ link.setInterfaceName("newname");
+ }
+ }
+ for (LinkProperties link : rmnet0.getStackedLinks()) {
+ assertFalse("newname".equals(link.getInterfaceName()));
+ }
+
+ assertTrue(rmnet0.removeStackedLink(clat4));
+ assertEquals(0, rmnet0.getStackedLinks().size());
+ assertEquals(1, rmnet0.getAddresses().size());
+ assertEquals(1, rmnet0.getLinkAddresses().size());
+ assertEquals(1, rmnet0.getAllAddresses().size());
+ assertEquals(1, rmnet0.getAllLinkAddresses().size());
+
+ assertFalse(rmnet0.removeStackedLink(clat4));
+ }
+
+ private LinkAddress getFirstLinkAddress(LinkProperties lp) {
+ return lp.getLinkAddresses().iterator().next();
+ }
+
+ @SmallTest
+ public void testAddressMethods() {
+ LinkProperties lp = new LinkProperties();
+
+ // No addresses.
+ assertFalse(lp.hasIPv4Address());
+ assertFalse(lp.hasIPv6Address());
+
+ // Addresses on stacked links don't count.
+ LinkProperties stacked = new LinkProperties();
+ stacked.setInterfaceName("stacked");
+ lp.addStackedLink(stacked);
+ stacked.addLinkAddress(LINKADDRV4);
+ stacked.addLinkAddress(LINKADDRV6);
+ assertTrue(stacked.hasIPv4Address());
+ assertTrue(stacked.hasIPv6Address());
+ assertFalse(lp.hasIPv4Address());
+ assertFalse(lp.hasIPv6Address());
+ lp.removeStackedLink(stacked);
+ assertFalse(lp.hasIPv4Address());
+ assertFalse(lp.hasIPv6Address());
+
+ // Addresses on the base link.
+ // Check the return values of hasIPvXAddress and ensure the add/remove methods return true
+ // iff something changes.
+ assertEquals(0, lp.getLinkAddresses().size());
+ assertTrue(lp.addLinkAddress(LINKADDRV6));
+ assertEquals(1, lp.getLinkAddresses().size());
+ assertFalse(lp.hasIPv4Address());
+ assertTrue(lp.hasIPv6Address());
+
+ assertTrue(lp.removeLinkAddress(LINKADDRV6));
+ assertEquals(0, lp.getLinkAddresses().size());
+ assertTrue(lp.addLinkAddress(LINKADDRV4));
+ assertEquals(1, lp.getLinkAddresses().size());
+ assertTrue(lp.hasIPv4Address());
+ assertFalse(lp.hasIPv6Address());
+
+ assertTrue(lp.addLinkAddress(LINKADDRV6));
+ assertEquals(2, lp.getLinkAddresses().size());
+ assertTrue(lp.hasIPv4Address());
+ assertTrue(lp.hasIPv6Address());
+
+ // Adding an address twice has no effect.
+ // Removing an address that's not present has no effect.
+ assertFalse(lp.addLinkAddress(LINKADDRV4));
+ assertEquals(2, lp.getLinkAddresses().size());
+ assertTrue(lp.hasIPv4Address());
+ assertTrue(lp.removeLinkAddress(LINKADDRV4));
+ assertEquals(1, lp.getLinkAddresses().size());
+ assertFalse(lp.hasIPv4Address());
+ assertFalse(lp.removeLinkAddress(LINKADDRV4));
+ assertEquals(1, lp.getLinkAddresses().size());
+
+ // Adding an address that's already present but with different properties causes the
+ // existing address to be updated and returns true.
+ // Start with only LINKADDRV6.
+ assertEquals(1, lp.getLinkAddresses().size());
+ assertEquals(LINKADDRV6, getFirstLinkAddress(lp));
+
+ // Create a LinkAddress object for the same address, but with different flags.
+ LinkAddress deprecated = new LinkAddress(ADDRV6, 128,
+ OsConstants.IFA_F_DEPRECATED, OsConstants.RT_SCOPE_UNIVERSE);
+ assertTrue(deprecated.isSameAddressAs(LINKADDRV6));
+ assertFalse(deprecated.equals(LINKADDRV6));
+
+ // Check that adding it updates the existing address instead of adding a new one.
+ assertTrue(lp.addLinkAddress(deprecated));
+ assertEquals(1, lp.getLinkAddresses().size());
+ assertEquals(deprecated, getFirstLinkAddress(lp));
+ assertFalse(LINKADDRV6.equals(getFirstLinkAddress(lp)));
+
+ // Removing LINKADDRV6 removes deprecated, because removing addresses ignores properties.
+ assertTrue(lp.removeLinkAddress(LINKADDRV6));
+ assertEquals(0, lp.getLinkAddresses().size());
+ }
+
+ @SmallTest
+ public void testSetLinkAddresses() {
+ LinkProperties lp = new LinkProperties();
+ lp.addLinkAddress(LINKADDRV4);
+ lp.addLinkAddress(LINKADDRV6);
+
+ LinkProperties lp2 = new LinkProperties();
+ lp2.addLinkAddress(LINKADDRV6);
+
+ assertFalse(lp.equals(lp2));
+
+ lp2.setLinkAddresses(lp.getLinkAddresses());
+ assertTrue(lp.equals(lp));
+ }
+}
diff --git a/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java b/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java
new file mode 100644
index 0000000..b181122
--- /dev/null
+++ b/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java
@@ -0,0 +1,521 @@
+/*
+ * Copyright (C) 2011 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 static android.net.NetworkStatsHistory.FIELD_ALL;
+import static android.net.NetworkStatsHistory.FIELD_OPERATIONS;
+import static android.net.NetworkStatsHistory.FIELD_RX_BYTES;
+import static android.net.NetworkStatsHistory.FIELD_RX_PACKETS;
+import static android.net.NetworkStatsHistory.FIELD_TX_BYTES;
+import static android.net.NetworkStatsHistory.DataStreamUtils.readVarLong;
+import static android.net.NetworkStatsHistory.DataStreamUtils.writeVarLong;
+import static android.net.NetworkStatsHistory.Entry.UNKNOWN;
+import static android.net.TrafficStats.GB_IN_BYTES;
+import static android.net.TrafficStats.MB_IN_BYTES;
+import static android.text.format.DateUtils.DAY_IN_MILLIS;
+import static android.text.format.DateUtils.HOUR_IN_MILLIS;
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+import static android.text.format.DateUtils.SECOND_IN_MILLIS;
+import static android.text.format.DateUtils.WEEK_IN_MILLIS;
+import static android.text.format.DateUtils.YEAR_IN_MILLIS;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.Suppress;
+import android.util.Log;
+
+import com.android.frameworks.coretests.R;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.util.Random;
+
+@SmallTest
+public class NetworkStatsHistoryTest extends AndroidTestCase {
+ private static final String TAG = "NetworkStatsHistoryTest";
+
+ private static final long TEST_START = 1194220800000L;
+
+ private NetworkStatsHistory stats;
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ if (stats != null) {
+ assertConsistent(stats);
+ }
+ }
+
+ public void testReadOriginalVersion() throws Exception {
+ final DataInputStream in = new DataInputStream(
+ getContext().getResources().openRawResource(R.raw.history_v1));
+
+ NetworkStatsHistory.Entry entry = null;
+ try {
+ final NetworkStatsHistory history = new NetworkStatsHistory(in);
+ assertEquals(15 * SECOND_IN_MILLIS, history.getBucketDuration());
+
+ entry = history.getValues(0, entry);
+ assertEquals(29143L, entry.rxBytes);
+ assertEquals(6223L, entry.txBytes);
+
+ entry = history.getValues(history.size() - 1, entry);
+ assertEquals(1476L, entry.rxBytes);
+ assertEquals(838L, entry.txBytes);
+
+ entry = history.getValues(Long.MIN_VALUE, Long.MAX_VALUE, entry);
+ assertEquals(332401L, entry.rxBytes);
+ assertEquals(64314L, entry.txBytes);
+
+ } finally {
+ in.close();
+ }
+ }
+
+ public void testRecordSingleBucket() throws Exception {
+ final long BUCKET_SIZE = HOUR_IN_MILLIS;
+ stats = new NetworkStatsHistory(BUCKET_SIZE);
+
+ // record data into narrow window to get single bucket
+ stats.recordData(TEST_START, TEST_START + SECOND_IN_MILLIS,
+ new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 2L));
+
+ assertEquals(1, stats.size());
+ assertValues(stats, 0, SECOND_IN_MILLIS, 1024L, 10L, 2048L, 20L, 2L);
+ }
+
+ public void testRecordEqualBuckets() throws Exception {
+ final long bucketDuration = HOUR_IN_MILLIS;
+ stats = new NetworkStatsHistory(bucketDuration);
+
+ // split equally across two buckets
+ final long recordStart = TEST_START + (bucketDuration / 2);
+ stats.recordData(recordStart, recordStart + bucketDuration,
+ new NetworkStats.Entry(1024L, 10L, 128L, 2L, 2L));
+
+ assertEquals(2, stats.size());
+ assertValues(stats, 0, HOUR_IN_MILLIS / 2, 512L, 5L, 64L, 1L, 1L);
+ assertValues(stats, 1, HOUR_IN_MILLIS / 2, 512L, 5L, 64L, 1L, 1L);
+ }
+
+ public void testRecordTouchingBuckets() throws Exception {
+ final long BUCKET_SIZE = 15 * MINUTE_IN_MILLIS;
+ stats = new NetworkStatsHistory(BUCKET_SIZE);
+
+ // split almost completely into middle bucket, but with a few minutes
+ // overlap into neighboring buckets. total record is 20 minutes.
+ final long recordStart = (TEST_START + BUCKET_SIZE) - MINUTE_IN_MILLIS;
+ final long recordEnd = (TEST_START + (BUCKET_SIZE * 2)) + (MINUTE_IN_MILLIS * 4);
+ stats.recordData(recordStart, recordEnd,
+ new NetworkStats.Entry(1000L, 2000L, 5000L, 10000L, 100L));
+
+ assertEquals(3, stats.size());
+ // first bucket should have (1/20 of value)
+ assertValues(stats, 0, MINUTE_IN_MILLIS, 50L, 100L, 250L, 500L, 5L);
+ // second bucket should have (15/20 of value)
+ assertValues(stats, 1, 15 * MINUTE_IN_MILLIS, 750L, 1500L, 3750L, 7500L, 75L);
+ // final bucket should have (4/20 of value)
+ assertValues(stats, 2, 4 * MINUTE_IN_MILLIS, 200L, 400L, 1000L, 2000L, 20L);
+ }
+
+ public void testRecordGapBuckets() throws Exception {
+ final long BUCKET_SIZE = HOUR_IN_MILLIS;
+ stats = new NetworkStatsHistory(BUCKET_SIZE);
+
+ // record some data today and next week with large gap
+ final long firstStart = TEST_START;
+ final long lastStart = TEST_START + WEEK_IN_MILLIS;
+ stats.recordData(firstStart, firstStart + SECOND_IN_MILLIS,
+ new NetworkStats.Entry(128L, 2L, 256L, 4L, 1L));
+ stats.recordData(lastStart, lastStart + SECOND_IN_MILLIS,
+ new NetworkStats.Entry(64L, 1L, 512L, 8L, 2L));
+
+ // we should have two buckets, far apart from each other
+ assertEquals(2, stats.size());
+ assertValues(stats, 0, SECOND_IN_MILLIS, 128L, 2L, 256L, 4L, 1L);
+ assertValues(stats, 1, SECOND_IN_MILLIS, 64L, 1L, 512L, 8L, 2L);
+
+ // now record something in middle, spread across two buckets
+ final long middleStart = TEST_START + DAY_IN_MILLIS;
+ final long middleEnd = middleStart + (HOUR_IN_MILLIS * 2);
+ stats.recordData(middleStart, middleEnd,
+ new NetworkStats.Entry(2048L, 4L, 2048L, 4L, 2L));
+
+ // now should have four buckets, with new record in middle two buckets
+ assertEquals(4, stats.size());
+ assertValues(stats, 0, SECOND_IN_MILLIS, 128L, 2L, 256L, 4L, 1L);
+ assertValues(stats, 1, HOUR_IN_MILLIS, 1024L, 2L, 1024L, 2L, 1L);
+ assertValues(stats, 2, HOUR_IN_MILLIS, 1024L, 2L, 1024L, 2L, 1L);
+ assertValues(stats, 3, SECOND_IN_MILLIS, 64L, 1L, 512L, 8L, 2L);
+ }
+
+ public void testRecordOverlapBuckets() throws Exception {
+ final long BUCKET_SIZE = HOUR_IN_MILLIS;
+ stats = new NetworkStatsHistory(BUCKET_SIZE);
+
+ // record some data in one bucket, and another overlapping buckets
+ stats.recordData(TEST_START, TEST_START + SECOND_IN_MILLIS,
+ new NetworkStats.Entry(256L, 2L, 256L, 2L, 1L));
+ final long midStart = TEST_START + (HOUR_IN_MILLIS / 2);
+ stats.recordData(midStart, midStart + HOUR_IN_MILLIS,
+ new NetworkStats.Entry(1024L, 10L, 1024L, 10L, 10L));
+
+ // should have two buckets, with some data mixed together
+ assertEquals(2, stats.size());
+ assertValues(stats, 0, SECOND_IN_MILLIS + (HOUR_IN_MILLIS / 2), 768L, 7L, 768L, 7L, 6L);
+ assertValues(stats, 1, (HOUR_IN_MILLIS / 2), 512L, 5L, 512L, 5L, 5L);
+ }
+
+ public void testRecordEntireGapIdentical() throws Exception {
+ // first, create two separate histories far apart
+ final NetworkStatsHistory stats1 = new NetworkStatsHistory(HOUR_IN_MILLIS);
+ stats1.recordData(TEST_START, TEST_START + 2 * HOUR_IN_MILLIS, 2000L, 1000L);
+
+ final long TEST_START_2 = TEST_START + DAY_IN_MILLIS;
+ final NetworkStatsHistory stats2 = new NetworkStatsHistory(HOUR_IN_MILLIS);
+ stats2.recordData(TEST_START_2, TEST_START_2 + 2 * HOUR_IN_MILLIS, 1000L, 500L);
+
+ // combine together with identical bucket size
+ stats = new NetworkStatsHistory(HOUR_IN_MILLIS);
+ stats.recordEntireHistory(stats1);
+ stats.recordEntireHistory(stats2);
+
+ // first verify that totals match up
+ assertValues(stats, TEST_START - WEEK_IN_MILLIS, TEST_START + WEEK_IN_MILLIS, 3000L, 1500L);
+
+ // now inspect internal buckets
+ assertValues(stats, 0, 1000L, 500L);
+ assertValues(stats, 1, 1000L, 500L);
+ assertValues(stats, 2, 500L, 250L);
+ assertValues(stats, 3, 500L, 250L);
+ }
+
+ public void testRecordEntireOverlapVaryingBuckets() throws Exception {
+ // create history just over hour bucket boundary
+ final NetworkStatsHistory stats1 = new NetworkStatsHistory(HOUR_IN_MILLIS);
+ stats1.recordData(TEST_START, TEST_START + MINUTE_IN_MILLIS * 60, 600L, 600L);
+
+ final long TEST_START_2 = TEST_START + MINUTE_IN_MILLIS;
+ final NetworkStatsHistory stats2 = new NetworkStatsHistory(MINUTE_IN_MILLIS);
+ stats2.recordData(TEST_START_2, TEST_START_2 + MINUTE_IN_MILLIS * 5, 50L, 50L);
+
+ // combine together with minute bucket size
+ stats = new NetworkStatsHistory(MINUTE_IN_MILLIS);
+ stats.recordEntireHistory(stats1);
+ stats.recordEntireHistory(stats2);
+
+ // first verify that totals match up
+ assertValues(stats, TEST_START - WEEK_IN_MILLIS, TEST_START + WEEK_IN_MILLIS, 650L, 650L);
+
+ // now inspect internal buckets
+ assertValues(stats, 0, 10L, 10L);
+ assertValues(stats, 1, 20L, 20L);
+ assertValues(stats, 2, 20L, 20L);
+ assertValues(stats, 3, 20L, 20L);
+ assertValues(stats, 4, 20L, 20L);
+ assertValues(stats, 5, 20L, 20L);
+ assertValues(stats, 6, 10L, 10L);
+
+ // now combine using 15min buckets
+ stats = new NetworkStatsHistory(HOUR_IN_MILLIS / 4);
+ stats.recordEntireHistory(stats1);
+ stats.recordEntireHistory(stats2);
+
+ // first verify that totals match up
+ assertValues(stats, TEST_START - WEEK_IN_MILLIS, TEST_START + WEEK_IN_MILLIS, 650L, 650L);
+
+ // and inspect buckets
+ assertValues(stats, 0, 200L, 200L);
+ assertValues(stats, 1, 150L, 150L);
+ assertValues(stats, 2, 150L, 150L);
+ assertValues(stats, 3, 150L, 150L);
+ }
+
+ public void testRemove() throws Exception {
+ stats = new NetworkStatsHistory(HOUR_IN_MILLIS);
+
+ // record some data across 24 buckets
+ stats.recordData(TEST_START, TEST_START + DAY_IN_MILLIS, 24L, 24L);
+ assertEquals(24, stats.size());
+
+ // try removing invalid data; should be no change
+ stats.removeBucketsBefore(0 - DAY_IN_MILLIS);
+ assertEquals(24, stats.size());
+
+ // try removing far before buckets; should be no change
+ stats.removeBucketsBefore(TEST_START - YEAR_IN_MILLIS);
+ assertEquals(24, stats.size());
+
+ // try removing just moments into first bucket; should be no change
+ // since that bucket contains data beyond the cutoff
+ stats.removeBucketsBefore(TEST_START + SECOND_IN_MILLIS);
+ assertEquals(24, stats.size());
+
+ // try removing single bucket
+ stats.removeBucketsBefore(TEST_START + HOUR_IN_MILLIS);
+ assertEquals(23, stats.size());
+
+ // try removing multiple buckets
+ stats.removeBucketsBefore(TEST_START + (4 * HOUR_IN_MILLIS));
+ assertEquals(20, stats.size());
+
+ // try removing all buckets
+ stats.removeBucketsBefore(TEST_START + YEAR_IN_MILLIS);
+ assertEquals(0, stats.size());
+ }
+
+ public void testTotalData() throws Exception {
+ final long BUCKET_SIZE = HOUR_IN_MILLIS;
+ stats = new NetworkStatsHistory(BUCKET_SIZE);
+
+ // record uniform data across day
+ stats.recordData(TEST_START, TEST_START + DAY_IN_MILLIS, 2400L, 4800L);
+
+ // verify that total outside range is 0
+ assertValues(stats, TEST_START - WEEK_IN_MILLIS, TEST_START - DAY_IN_MILLIS, 0L, 0L);
+
+ // verify total in first hour
+ assertValues(stats, TEST_START, TEST_START + HOUR_IN_MILLIS, 100L, 200L);
+
+ // verify total across 1.5 hours
+ assertValues(stats, TEST_START, TEST_START + (long) (1.5 * HOUR_IN_MILLIS), 150L, 300L);
+
+ // verify total beyond end
+ assertValues(stats, TEST_START + (23 * HOUR_IN_MILLIS), TEST_START + WEEK_IN_MILLIS, 100L, 200L);
+
+ // verify everything total
+ assertValues(stats, TEST_START - WEEK_IN_MILLIS, TEST_START + WEEK_IN_MILLIS, 2400L, 4800L);
+
+ }
+
+ @Suppress
+ public void testFuzzing() throws Exception {
+ try {
+ // fuzzing with random events, looking for crashes
+ final NetworkStats.Entry entry = new NetworkStats.Entry();
+ final Random r = new Random();
+ for (int i = 0; i < 500; i++) {
+ stats = new NetworkStatsHistory(r.nextLong());
+ for (int j = 0; j < 10000; j++) {
+ if (r.nextBoolean()) {
+ // add range
+ final long start = r.nextLong();
+ final long end = start + r.nextInt();
+ entry.rxBytes = nextPositiveLong(r);
+ entry.rxPackets = nextPositiveLong(r);
+ entry.txBytes = nextPositiveLong(r);
+ entry.txPackets = nextPositiveLong(r);
+ entry.operations = nextPositiveLong(r);
+ stats.recordData(start, end, entry);
+ } else {
+ // trim something
+ stats.removeBucketsBefore(r.nextLong());
+ }
+ }
+ assertConsistent(stats);
+ }
+ } catch (Throwable e) {
+ Log.e(TAG, String.valueOf(stats));
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static long nextPositiveLong(Random r) {
+ final long value = r.nextLong();
+ return value < 0 ? -value : value;
+ }
+
+ public void testIgnoreFields() throws Exception {
+ final NetworkStatsHistory history = new NetworkStatsHistory(
+ MINUTE_IN_MILLIS, 0, FIELD_RX_BYTES | FIELD_TX_BYTES);
+
+ history.recordData(0, MINUTE_IN_MILLIS,
+ new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 4L));
+ history.recordData(0, 2 * MINUTE_IN_MILLIS,
+ new NetworkStats.Entry(2L, 2L, 2L, 2L, 2L));
+
+ assertFullValues(history, UNKNOWN, 1026L, UNKNOWN, 2050L, UNKNOWN, UNKNOWN);
+ }
+
+ public void testIgnoreFieldsRecordIn() throws Exception {
+ final NetworkStatsHistory full = new NetworkStatsHistory(MINUTE_IN_MILLIS, 0, FIELD_ALL);
+ final NetworkStatsHistory partial = new NetworkStatsHistory(
+ MINUTE_IN_MILLIS, 0, FIELD_RX_PACKETS | FIELD_OPERATIONS);
+
+ full.recordData(0, MINUTE_IN_MILLIS,
+ new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 4L));
+ partial.recordEntireHistory(full);
+
+ assertFullValues(partial, UNKNOWN, UNKNOWN, 10L, UNKNOWN, UNKNOWN, 4L);
+ }
+
+ public void testIgnoreFieldsRecordOut() throws Exception {
+ final NetworkStatsHistory full = new NetworkStatsHistory(MINUTE_IN_MILLIS, 0, FIELD_ALL);
+ final NetworkStatsHistory partial = new NetworkStatsHistory(
+ MINUTE_IN_MILLIS, 0, FIELD_RX_PACKETS | FIELD_OPERATIONS);
+
+ partial.recordData(0, MINUTE_IN_MILLIS,
+ new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 4L));
+ full.recordEntireHistory(partial);
+
+ assertFullValues(full, MINUTE_IN_MILLIS, 0L, 10L, 0L, 0L, 4L);
+ }
+
+ public void testSerialize() throws Exception {
+ final NetworkStatsHistory before = new NetworkStatsHistory(MINUTE_IN_MILLIS, 40, FIELD_ALL);
+ before.recordData(0, 4 * MINUTE_IN_MILLIS,
+ new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 4L));
+ before.recordData(DAY_IN_MILLIS, DAY_IN_MILLIS + MINUTE_IN_MILLIS,
+ new NetworkStats.Entry(10L, 20L, 30L, 40L, 50L));
+
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+ before.writeToStream(new DataOutputStream(out));
+ out.close();
+
+ final ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+ final NetworkStatsHistory after = new NetworkStatsHistory(new DataInputStream(in));
+
+ // must have identical totals before and after
+ assertFullValues(before, 5 * MINUTE_IN_MILLIS, 1034L, 30L, 2078L, 60L, 54L);
+ assertFullValues(after, 5 * MINUTE_IN_MILLIS, 1034L, 30L, 2078L, 60L, 54L);
+ }
+
+ public void testVarLong() throws Exception {
+ assertEquals(0L, performVarLong(0L));
+ assertEquals(-1L, performVarLong(-1L));
+ assertEquals(1024L, performVarLong(1024L));
+ assertEquals(-1024L, performVarLong(-1024L));
+ assertEquals(40 * MB_IN_BYTES, performVarLong(40 * MB_IN_BYTES));
+ assertEquals(512 * GB_IN_BYTES, performVarLong(512 * GB_IN_BYTES));
+ assertEquals(Long.MIN_VALUE, performVarLong(Long.MIN_VALUE));
+ assertEquals(Long.MAX_VALUE, performVarLong(Long.MAX_VALUE));
+ assertEquals(Long.MIN_VALUE + 40, performVarLong(Long.MIN_VALUE + 40));
+ assertEquals(Long.MAX_VALUE - 40, performVarLong(Long.MAX_VALUE - 40));
+ }
+
+ public void testIndexBeforeAfter() throws Exception {
+ final long BUCKET_SIZE = HOUR_IN_MILLIS;
+ stats = new NetworkStatsHistory(BUCKET_SIZE);
+
+ final long FIRST_START = TEST_START;
+ final long FIRST_END = FIRST_START + (2 * HOUR_IN_MILLIS);
+ final long SECOND_START = TEST_START + WEEK_IN_MILLIS;
+ final long SECOND_END = SECOND_START + HOUR_IN_MILLIS;
+ final long THIRD_START = TEST_START + (2 * WEEK_IN_MILLIS);
+ final long THIRD_END = THIRD_START + (2 * HOUR_IN_MILLIS);
+
+ stats.recordData(FIRST_START, FIRST_END,
+ new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 2L));
+ stats.recordData(SECOND_START, SECOND_END,
+ new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 2L));
+ stats.recordData(THIRD_START, THIRD_END,
+ new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 2L));
+
+ // should have buckets: 2+1+2
+ assertEquals(5, stats.size());
+
+ assertIndexBeforeAfter(stats, 0, 0, Long.MIN_VALUE);
+ assertIndexBeforeAfter(stats, 0, 1, FIRST_START);
+ assertIndexBeforeAfter(stats, 0, 1, FIRST_START + MINUTE_IN_MILLIS);
+ assertIndexBeforeAfter(stats, 0, 2, FIRST_START + HOUR_IN_MILLIS);
+ assertIndexBeforeAfter(stats, 1, 2, FIRST_START + HOUR_IN_MILLIS + MINUTE_IN_MILLIS);
+ assertIndexBeforeAfter(stats, 1, 2, FIRST_END - MINUTE_IN_MILLIS);
+ assertIndexBeforeAfter(stats, 1, 2, FIRST_END);
+ assertIndexBeforeAfter(stats, 1, 2, FIRST_END + MINUTE_IN_MILLIS);
+ assertIndexBeforeAfter(stats, 1, 2, SECOND_START - MINUTE_IN_MILLIS);
+ assertIndexBeforeAfter(stats, 1, 3, SECOND_START);
+ assertIndexBeforeAfter(stats, 2, 3, SECOND_END);
+ assertIndexBeforeAfter(stats, 2, 3, SECOND_END + MINUTE_IN_MILLIS);
+ assertIndexBeforeAfter(stats, 2, 3, THIRD_START - MINUTE_IN_MILLIS);
+ assertIndexBeforeAfter(stats, 2, 4, THIRD_START);
+ assertIndexBeforeAfter(stats, 3, 4, THIRD_START + MINUTE_IN_MILLIS);
+ assertIndexBeforeAfter(stats, 3, 4, THIRD_START + HOUR_IN_MILLIS);
+ assertIndexBeforeAfter(stats, 4, 4, THIRD_END);
+ assertIndexBeforeAfter(stats, 4, 4, THIRD_END + MINUTE_IN_MILLIS);
+ assertIndexBeforeAfter(stats, 4, 4, Long.MAX_VALUE);
+ }
+
+ private static void assertIndexBeforeAfter(
+ NetworkStatsHistory stats, int before, int after, long time) {
+ assertEquals("unexpected before", before, stats.getIndexBefore(time));
+ assertEquals("unexpected after", after, stats.getIndexAfter(time));
+ }
+
+ private static long performVarLong(long before) throws Exception {
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+ writeVarLong(new DataOutputStream(out), before);
+
+ final ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+ return readVarLong(new DataInputStream(in));
+ }
+
+ private static void assertConsistent(NetworkStatsHistory stats) {
+ // verify timestamps are monotonic
+ long lastStart = Long.MIN_VALUE;
+ NetworkStatsHistory.Entry entry = null;
+ for (int i = 0; i < stats.size(); i++) {
+ entry = stats.getValues(i, entry);
+ assertTrue(lastStart < entry.bucketStart);
+ lastStart = entry.bucketStart;
+ }
+ }
+
+ private static void assertValues(
+ NetworkStatsHistory stats, int index, long rxBytes, long txBytes) {
+ final NetworkStatsHistory.Entry entry = stats.getValues(index, null);
+ assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
+ assertEquals("unexpected txBytes", txBytes, entry.txBytes);
+ }
+
+ private static void assertValues(
+ NetworkStatsHistory stats, long start, long end, long rxBytes, long txBytes) {
+ final NetworkStatsHistory.Entry entry = stats.getValues(start, end, null);
+ assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
+ assertEquals("unexpected txBytes", txBytes, entry.txBytes);
+ }
+
+ private static void assertValues(NetworkStatsHistory stats, int index, long activeTime,
+ long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) {
+ final NetworkStatsHistory.Entry entry = stats.getValues(index, null);
+ assertEquals("unexpected activeTime", activeTime, entry.activeTime);
+ assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
+ assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets);
+ assertEquals("unexpected txBytes", txBytes, entry.txBytes);
+ assertEquals("unexpected txPackets", txPackets, entry.txPackets);
+ assertEquals("unexpected operations", operations, entry.operations);
+ }
+
+ private static void assertFullValues(NetworkStatsHistory stats, long activeTime, long rxBytes,
+ long rxPackets, long txBytes, long txPackets, long operations) {
+ assertValues(stats, Long.MIN_VALUE, Long.MAX_VALUE, activeTime, rxBytes, rxPackets, txBytes,
+ txPackets, operations);
+ }
+
+ private static void assertValues(NetworkStatsHistory stats, long start, long end,
+ long activeTime, long rxBytes, long rxPackets, long txBytes, long txPackets,
+ long operations) {
+ final NetworkStatsHistory.Entry entry = stats.getValues(start, end, null);
+ assertEquals("unexpected activeTime", activeTime, entry.activeTime);
+ assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
+ assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets);
+ assertEquals("unexpected txBytes", txBytes, entry.txBytes);
+ assertEquals("unexpected txPackets", txPackets, entry.txPackets);
+ assertEquals("unexpected operations", operations, entry.operations);
+ }
+}
diff --git a/core/tests/coretests/src/android/net/NetworkStatsTest.java b/core/tests/coretests/src/android/net/NetworkStatsTest.java
new file mode 100644
index 0000000..6331964
--- /dev/null
+++ b/core/tests/coretests/src/android/net/NetworkStatsTest.java
@@ -0,0 +1,337 @@
+/*
+ * Copyright (C) 2011 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 static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.SET_FOREGROUND;
+import static android.net.NetworkStats.SET_ALL;
+import static android.net.NetworkStats.IFACE_ALL;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkStats.UID_ALL;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.google.android.collect.Sets;
+
+import junit.framework.TestCase;
+
+import java.util.HashSet;
+
+@SmallTest
+public class NetworkStatsTest extends TestCase {
+
+ private static final String TEST_IFACE = "test0";
+ private static final String TEST_IFACE2 = "test2";
+ private static final int TEST_UID = 1001;
+ private static final long TEST_START = 1194220800000L;
+
+ public void testFindIndex() throws Exception {
+ final NetworkStats stats = new NetworkStats(TEST_START, 3)
+ .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 10)
+ .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 11)
+ .addValues(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 12);
+
+ assertEquals(2, stats.findIndex(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE));
+ assertEquals(2, stats.findIndex(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE));
+ assertEquals(0, stats.findIndex(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE));
+ assertEquals(-1, stats.findIndex(TEST_IFACE, 6, SET_DEFAULT, TAG_NONE));
+ }
+
+ public void testFindIndexHinted() {
+ final NetworkStats stats = new NetworkStats(TEST_START, 3)
+ .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 10)
+ .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 11)
+ .addValues(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 12)
+ .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 1024L, 8L, 0L, 0L, 10)
+ .addValues(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D, 0L, 0L, 1024L, 8L, 11)
+ .addValues(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 12);
+
+ // verify that we correctly find across regardless of hinting
+ for (int hint = 0; hint < stats.size(); hint++) {
+ assertEquals(0, stats.findIndexHinted(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, hint));
+ assertEquals(1, stats.findIndexHinted(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, hint));
+ assertEquals(2, stats.findIndexHinted(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, hint));
+ assertEquals(3, stats.findIndexHinted(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, hint));
+ assertEquals(4, stats.findIndexHinted(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D, hint));
+ assertEquals(5, stats.findIndexHinted(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, hint));
+ assertEquals(-1, stats.findIndexHinted(TEST_IFACE, 6, SET_DEFAULT, TAG_NONE, hint));
+ }
+ }
+
+ public void testAddEntryGrow() throws Exception {
+ final NetworkStats stats = new NetworkStats(TEST_START, 2);
+
+ assertEquals(0, stats.size());
+ assertEquals(2, stats.internalSize());
+
+ stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, 1L, 1L, 2L, 2L, 3);
+ stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, 2L, 2L, 2L, 2L, 4);
+
+ assertEquals(2, stats.size());
+ assertEquals(2, stats.internalSize());
+
+ stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, 3L, 30L, 4L, 40L, 7);
+ stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, 4L, 40L, 4L, 40L, 8);
+ stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, 5L, 50L, 5L, 50L, 10);
+
+ assertEquals(5, stats.size());
+ assertTrue(stats.internalSize() >= 5);
+
+ assertValues(stats, 0, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, 1L, 1L, 2L, 2L, 3);
+ assertValues(stats, 1, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, 2L, 2L, 2L, 2L, 4);
+ assertValues(stats, 2, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, 3L, 30L, 4L, 40L, 7);
+ assertValues(stats, 3, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, 4L, 40L, 4L, 40L, 8);
+ assertValues(stats, 4, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, 5L, 50L, 5L, 50L, 10);
+ }
+
+ public void testCombineExisting() throws Exception {
+ final NetworkStats stats = new NetworkStats(TEST_START, 10);
+
+ stats.addValues(TEST_IFACE, 1001, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 10);
+ stats.addValues(TEST_IFACE, 1001, SET_DEFAULT, 0xff, 128L, 1L, 128L, 1L, 2);
+ stats.combineValues(TEST_IFACE, 1001, SET_DEFAULT, TAG_NONE, -128L, -1L, -128L, -1L, -1);
+
+ assertValues(stats, 0, TEST_IFACE, 1001, SET_DEFAULT, TAG_NONE, 384L, 3L, 128L, 1L, 9);
+ assertValues(stats, 1, TEST_IFACE, 1001, SET_DEFAULT, 0xff, 128L, 1L, 128L, 1L, 2);
+
+ // now try combining that should create row
+ stats.combineValues(TEST_IFACE, 5005, SET_DEFAULT, TAG_NONE, 128L, 1L, 128L, 1L, 3);
+ assertValues(stats, 2, TEST_IFACE, 5005, SET_DEFAULT, TAG_NONE, 128L, 1L, 128L, 1L, 3);
+ stats.combineValues(TEST_IFACE, 5005, SET_DEFAULT, TAG_NONE, 128L, 1L, 128L, 1L, 3);
+ assertValues(stats, 2, TEST_IFACE, 5005, SET_DEFAULT, TAG_NONE, 256L, 2L, 256L, 2L, 6);
+ }
+
+ public void testSubtractIdenticalData() throws Exception {
+ final NetworkStats before = new NetworkStats(TEST_START, 2)
+ .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
+ .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12);
+
+ final NetworkStats after = new NetworkStats(TEST_START, 2)
+ .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
+ .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12);
+
+ final NetworkStats result = after.subtract(before);
+
+ // identical data should result in zero delta
+ assertValues(result, 0, TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0);
+ assertValues(result, 1, TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0);
+ }
+
+ public void testSubtractIdenticalRows() throws Exception {
+ final NetworkStats before = new NetworkStats(TEST_START, 2)
+ .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
+ .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12);
+
+ final NetworkStats after = new NetworkStats(TEST_START, 2)
+ .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1025L, 9L, 2L, 1L, 15)
+ .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 3L, 1L, 1028L, 9L, 20);
+
+ final NetworkStats result = after.subtract(before);
+
+ // expect delta between measurements
+ assertValues(result, 0, TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1L, 1L, 2L, 1L, 4);
+ assertValues(result, 1, TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 3L, 1L, 4L, 1L, 8);
+ }
+
+ public void testSubtractNewRows() throws Exception {
+ final NetworkStats before = new NetworkStats(TEST_START, 2)
+ .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
+ .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12);
+
+ final NetworkStats after = new NetworkStats(TEST_START, 3)
+ .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
+ .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12)
+ .addValues(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 20);
+
+ final NetworkStats result = after.subtract(before);
+
+ // its okay to have new rows
+ assertValues(result, 0, TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0);
+ assertValues(result, 1, TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0);
+ assertValues(result, 2, TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 20);
+ }
+
+ public void testSubtractMissingRows() throws Exception {
+ final NetworkStats before = new NetworkStats(TEST_START, 2)
+ .addValues(TEST_IFACE, UID_ALL, SET_DEFAULT, TAG_NONE, 1024L, 0L, 0L, 0L, 0)
+ .addValues(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 2048L, 0L, 0L, 0L, 0);
+
+ final NetworkStats after = new NetworkStats(TEST_START, 1)
+ .addValues(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 2049L, 2L, 3L, 4L, 0);
+
+ final NetworkStats result = after.subtract(before);
+
+ // should silently drop omitted rows
+ assertEquals(1, result.size());
+ assertValues(result, 0, TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 1L, 2L, 3L, 4L, 0);
+ assertEquals(4L, result.getTotalBytes());
+ }
+
+ public void testTotalBytes() throws Exception {
+ final NetworkStats iface = new NetworkStats(TEST_START, 2)
+ .addValues(TEST_IFACE, UID_ALL, SET_DEFAULT, TAG_NONE, 128L, 0L, 0L, 0L, 0L)
+ .addValues(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 256L, 0L, 0L, 0L, 0L);
+ assertEquals(384L, iface.getTotalBytes());
+
+ final NetworkStats uidSet = new NetworkStats(TEST_START, 3)
+ .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 32L, 0L, 0L, 0L, 0L)
+ .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 32L, 0L, 0L, 0L, 0L)
+ .addValues(TEST_IFACE, 101, SET_FOREGROUND, TAG_NONE, 32L, 0L, 0L, 0L, 0L);
+ assertEquals(96L, uidSet.getTotalBytes());
+
+ final NetworkStats uidTag = new NetworkStats(TEST_START, 3)
+ .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L)
+ .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L)
+ .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 8L, 0L, 0L, 0L, 0L)
+ .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 16L, 0L, 0L, 0L, 0L)
+ .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L)
+ .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 8L, 0L, 0L, 0L, 0L);
+ assertEquals(64L, uidTag.getTotalBytes());
+ }
+
+ public void testGroupedByIfaceEmpty() throws Exception {
+ final NetworkStats uidStats = new NetworkStats(TEST_START, 3);
+ final NetworkStats grouped = uidStats.groupedByIface();
+
+ assertEquals(0, uidStats.size());
+ assertEquals(0, grouped.size());
+ }
+
+ public void testGroupedByIfaceAll() throws Exception {
+ final NetworkStats uidStats = new NetworkStats(TEST_START, 3)
+ .addValues(IFACE_ALL, 100, SET_ALL, TAG_NONE, 128L, 8L, 0L, 2L, 20L)
+ .addValues(IFACE_ALL, 101, SET_FOREGROUND, TAG_NONE, 128L, 8L, 0L, 2L, 20L);
+ final NetworkStats grouped = uidStats.groupedByIface();
+
+ assertEquals(2, uidStats.size());
+ assertEquals(1, grouped.size());
+
+ assertValues(grouped, 0, IFACE_ALL, UID_ALL, SET_ALL, TAG_NONE, 256L, 16L, 0L, 4L, 0L);
+ }
+
+ public void testGroupedByIface() throws Exception {
+ final NetworkStats uidStats = new NetworkStats(TEST_START, 3)
+ .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 2L, 20L)
+ .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 512L, 32L, 0L, 0L, 0L)
+ .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 64L, 4L, 0L, 0L, 0L)
+ .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 512L, 32L, 0L, 0L, 0L)
+ .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 0L, 0L)
+ .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 128L, 8L, 0L, 0L, 0L);
+
+ final NetworkStats grouped = uidStats.groupedByIface();
+
+ assertEquals(6, uidStats.size());
+
+ assertEquals(2, grouped.size());
+ assertValues(grouped, 0, TEST_IFACE, UID_ALL, SET_ALL, TAG_NONE, 256L, 16L, 0L, 2L, 0L);
+ assertValues(grouped, 1, TEST_IFACE2, UID_ALL, SET_ALL, TAG_NONE, 1024L, 64L, 0L, 0L, 0L);
+ }
+
+ public void testAddAllValues() {
+ final NetworkStats first = new NetworkStats(TEST_START, 5)
+ .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 32L, 0L, 0L, 0L, 0L)
+ .addValues(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, 32L, 0L, 0L, 0L, 0L);
+
+ final NetworkStats second = new NetworkStats(TEST_START, 2)
+ .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 32L, 0L, 0L, 0L, 0L)
+ .addValues(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 32L, 0L, 0L, 0L, 0L);
+
+ first.combineAllValues(second);
+
+ assertEquals(3, first.size());
+ assertValues(first, 0, TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 64L, 0L, 0L, 0L, 0L);
+ assertValues(first, 1, TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, 32L, 0L, 0L, 0L, 0L);
+ assertValues(first, 2, TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 32L, 0L, 0L, 0L, 0L);
+ }
+
+ public void testGetTotal() {
+ final NetworkStats stats = new NetworkStats(TEST_START, 3)
+ .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 2L, 20L)
+ .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 512L, 32L, 0L, 0L, 0L)
+ .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 64L, 4L, 0L, 0L, 0L)
+ .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 512L, 32L, 0L, 0L, 0L)
+ .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 0L, 0L)
+ .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 128L, 8L, 0L, 0L, 0L);
+
+ assertValues(stats.getTotal(null), 1280L, 80L, 0L, 2L, 20L);
+ assertValues(stats.getTotal(null, 100), 1152L, 72L, 0L, 2L, 20L);
+ assertValues(stats.getTotal(null, 101), 128L, 8L, 0L, 0L, 0L);
+
+ final HashSet<String> ifaces = Sets.newHashSet();
+ assertValues(stats.getTotal(null, ifaces), 0L, 0L, 0L, 0L, 0L);
+
+ ifaces.add(TEST_IFACE2);
+ assertValues(stats.getTotal(null, ifaces), 1024L, 64L, 0L, 0L, 0L);
+ }
+
+ public void testWithoutUid() throws Exception {
+ final NetworkStats before = new NetworkStats(TEST_START, 3)
+ .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 2L, 20L)
+ .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 512L, 32L, 0L, 0L, 0L)
+ .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 64L, 4L, 0L, 0L, 0L)
+ .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 512L, 32L, 0L, 0L, 0L)
+ .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 0L, 0L)
+ .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 128L, 8L, 0L, 0L, 0L);
+
+ final NetworkStats after = before.withoutUids(new int[] { 100 });
+ assertEquals(6, before.size());
+ assertEquals(2, after.size());
+ assertValues(after, 0, TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 0L, 0L);
+ assertValues(after, 1, TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 128L, 8L, 0L, 0L, 0L);
+ }
+
+ public void testClone() throws Exception {
+ final NetworkStats original = new NetworkStats(TEST_START, 5)
+ .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 2L, 20L)
+ .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 512L, 32L, 0L, 0L, 0L);
+
+ // make clone and mutate original
+ final NetworkStats clone = original.clone();
+ original.addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 0L, 0L);
+
+ assertEquals(3, original.size());
+ assertEquals(2, clone.size());
+
+ assertEquals(128L + 512L + 128L, original.getTotalBytes());
+ assertEquals(128L + 512L, clone.getTotalBytes());
+ }
+
+ private static void assertValues(NetworkStats stats, int index, String iface, int uid, int set,
+ int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) {
+ final NetworkStats.Entry entry = stats.getValues(index, null);
+ assertValues(entry, iface, uid, set, tag);
+ assertValues(entry, rxBytes, rxPackets, txBytes, txPackets, operations);
+ }
+
+ private static void assertValues(
+ NetworkStats.Entry entry, String iface, int uid, int set, int tag) {
+ assertEquals(iface, entry.iface);
+ assertEquals(uid, entry.uid);
+ assertEquals(set, entry.set);
+ assertEquals(tag, entry.tag);
+ }
+
+ private static void assertValues(NetworkStats.Entry entry, long rxBytes, long rxPackets,
+ long txBytes, long txPackets, long operations) {
+ assertEquals(rxBytes, entry.rxBytes);
+ assertEquals(rxPackets, entry.rxPackets);
+ assertEquals(txBytes, entry.txBytes);
+ assertEquals(txPackets, entry.txPackets);
+ assertEquals(operations, entry.operations);
+ }
+
+}
diff --git a/core/tests/coretests/src/android/net/RouteInfoTest.java b/core/tests/coretests/src/android/net/RouteInfoTest.java
new file mode 100644
index 0000000..01283a6
--- /dev/null
+++ b/core/tests/coretests/src/android/net/RouteInfoTest.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2010 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 java.lang.reflect.Method;
+import java.net.InetAddress;
+
+import android.net.LinkAddress;
+import android.net.RouteInfo;
+import android.os.Parcel;
+
+import junit.framework.TestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+public class RouteInfoTest extends TestCase {
+
+ private InetAddress Address(String addr) {
+ return InetAddress.parseNumericAddress(addr);
+ }
+
+ private LinkAddress Prefix(String prefix) {
+ String[] parts = prefix.split("/");
+ return new LinkAddress(Address(parts[0]), Integer.parseInt(parts[1]));
+ }
+
+ @SmallTest
+ public void testConstructor() {
+ RouteInfo r;
+
+ // Invalid input.
+ try {
+ r = new RouteInfo((LinkAddress) null, null, "rmnet0");
+ fail("Expected RuntimeException: destination and gateway null");
+ } catch(RuntimeException e) {}
+
+ // Null destination is default route.
+ r = new RouteInfo((LinkAddress) null, Address("2001:db8::1"), null);
+ assertEquals(Prefix("::/0"), r.getDestination());
+ assertEquals(Address("2001:db8::1"), r.getGateway());
+ assertNull(r.getInterface());
+
+ r = new RouteInfo((LinkAddress) null, Address("192.0.2.1"), "wlan0");
+ assertEquals(Prefix("0.0.0.0/0"), r.getDestination());
+ assertEquals(Address("192.0.2.1"), r.getGateway());
+ assertEquals("wlan0", r.getInterface());
+
+ // Null gateway sets gateway to unspecified address (why?).
+ r = new RouteInfo(Prefix("2001:db8:beef:cafe::/48"), null, "lo");
+ assertEquals(Prefix("2001:db8:beef::/48"), r.getDestination());
+ assertEquals(Address("::"), r.getGateway());
+ assertEquals("lo", r.getInterface());
+
+ r = new RouteInfo(Prefix("192.0.2.5/24"), null);
+ assertEquals(Prefix("192.0.2.0/24"), r.getDestination());
+ assertEquals(Address("0.0.0.0"), r.getGateway());
+ assertNull(r.getInterface());
+ }
+
+ public void testMatches() {
+ class PatchedRouteInfo extends RouteInfo {
+ public PatchedRouteInfo(LinkAddress destination, InetAddress gateway, String iface) {
+ super(destination, gateway, iface);
+ }
+
+ public boolean matches(InetAddress destination) {
+ return super.matches(destination);
+ }
+ }
+
+ RouteInfo r;
+
+ r = new PatchedRouteInfo(Prefix("2001:db8:f00::ace:d00d/127"), null, "rmnet0");
+ assertTrue(r.matches(Address("2001:db8:f00::ace:d00c")));
+ assertTrue(r.matches(Address("2001:db8:f00::ace:d00d")));
+ assertFalse(r.matches(Address("2001:db8:f00::ace:d00e")));
+ assertFalse(r.matches(Address("2001:db8:f00::bad:d00d")));
+ assertFalse(r.matches(Address("2001:4868:4860::8888")));
+
+ r = new PatchedRouteInfo(Prefix("192.0.2.0/23"), null, "wlan0");
+ assertTrue(r.matches(Address("192.0.2.43")));
+ assertTrue(r.matches(Address("192.0.3.21")));
+ assertFalse(r.matches(Address("192.0.0.21")));
+ assertFalse(r.matches(Address("8.8.8.8")));
+
+ RouteInfo ipv6Default = new PatchedRouteInfo(Prefix("::/0"), null, "rmnet0");
+ assertTrue(ipv6Default.matches(Address("2001:db8::f00")));
+ assertFalse(ipv6Default.matches(Address("192.0.2.1")));
+
+ RouteInfo ipv4Default = new PatchedRouteInfo(Prefix("0.0.0.0/0"), null, "rmnet0");
+ assertTrue(ipv4Default.matches(Address("255.255.255.255")));
+ assertTrue(ipv4Default.matches(Address("192.0.2.1")));
+ assertFalse(ipv4Default.matches(Address("2001:db8::f00")));
+ }
+
+ private void assertAreEqual(Object o1, Object o2) {
+ assertTrue(o1.equals(o2));
+ assertTrue(o2.equals(o1));
+ }
+
+ private void assertAreNotEqual(Object o1, Object o2) {
+ assertFalse(o1.equals(o2));
+ assertFalse(o2.equals(o1));
+ }
+
+ public void testEquals() {
+ // IPv4
+ RouteInfo r1 = new RouteInfo(Prefix("2001:db8:ace::/48"), Address("2001:db8::1"), "wlan0");
+ RouteInfo r2 = new RouteInfo(Prefix("2001:db8:ace::/48"), Address("2001:db8::1"), "wlan0");
+ assertAreEqual(r1, r2);
+
+ RouteInfo r3 = new RouteInfo(Prefix("2001:db8:ace::/49"), Address("2001:db8::1"), "wlan0");
+ RouteInfo r4 = new RouteInfo(Prefix("2001:db8:ace::/48"), Address("2001:db8::2"), "wlan0");
+ RouteInfo r5 = new RouteInfo(Prefix("2001:db8:ace::/48"), Address("2001:db8::1"), "rmnet0");
+ assertAreNotEqual(r1, r3);
+ assertAreNotEqual(r1, r4);
+ assertAreNotEqual(r1, r5);
+
+ // IPv6
+ r1 = new RouteInfo(Prefix("192.0.2.0/25"), Address("192.0.2.1"), "wlan0");
+ r2 = new RouteInfo(Prefix("192.0.2.0/25"), Address("192.0.2.1"), "wlan0");
+ assertAreEqual(r1, r2);
+
+ r3 = new RouteInfo(Prefix("192.0.2.0/24"), Address("192.0.2.1"), "wlan0");
+ r4 = new RouteInfo(Prefix("192.0.2.0/25"), Address("192.0.2.2"), "wlan0");
+ r5 = new RouteInfo(Prefix("192.0.2.0/25"), Address("192.0.2.1"), "rmnet0");
+ assertAreNotEqual(r1, r3);
+ assertAreNotEqual(r1, r4);
+ assertAreNotEqual(r1, r5);
+
+ // Interfaces (but not destinations or gateways) can be null.
+ r1 = new RouteInfo(Prefix("192.0.2.0/25"), Address("192.0.2.1"), null);
+ r2 = new RouteInfo(Prefix("192.0.2.0/25"), Address("192.0.2.1"), null);
+ r3 = new RouteInfo(Prefix("192.0.2.0/24"), Address("192.0.2.1"), "wlan0");
+ assertAreEqual(r1, r2);
+ assertAreNotEqual(r1, r3);
+ }
+
+ public void testHostRoute() {
+ RouteInfo r;
+
+ r = new RouteInfo(Prefix("0.0.0.0/0"), Address("0.0.0.0"), "wlan0");
+ assertFalse(r.isHostRoute());
+
+ r = new RouteInfo(Prefix("::/0"), Address("::"), "wlan0");
+ assertFalse(r.isHostRoute());
+
+ r = new RouteInfo(Prefix("192.0.2.0/24"), null, "wlan0");
+ assertFalse(r.isHostRoute());
+
+ r = new RouteInfo(Prefix("2001:db8::/48"), null, "wlan0");
+ assertFalse(r.isHostRoute());
+
+ r = new RouteInfo(Prefix("192.0.2.0/32"), Address("0.0.0.0"), "wlan0");
+ assertTrue(r.isHostRoute());
+
+ r = new RouteInfo(Prefix("2001:db8::/128"), Address("::"), "wlan0");
+ assertTrue(r.isHostRoute());
+
+ r = new RouteInfo(Prefix("192.0.2.0/32"), null, "wlan0");
+ assertTrue(r.isHostRoute());
+
+ r = new RouteInfo(Prefix("2001:db8::/128"), null, "wlan0");
+ assertTrue(r.isHostRoute());
+
+ r = new RouteInfo(Prefix("::/128"), Address("fe80::"), "wlan0");
+ assertTrue(r.isHostRoute());
+
+ r = new RouteInfo(Prefix("0.0.0.0/32"), Address("192.0.2.1"), "wlan0");
+ assertTrue(r.isHostRoute());
+ }
+
+ public RouteInfo passThroughParcel(RouteInfo r) {
+ Parcel p = Parcel.obtain();
+ RouteInfo r2 = null;
+ try {
+ r.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ r2 = RouteInfo.CREATOR.createFromParcel(p);
+ } finally {
+ p.recycle();
+ }
+ assertNotNull(r2);
+ return r2;
+ }
+
+ public void assertParcelingIsLossless(RouteInfo r) {
+ RouteInfo r2 = passThroughParcel(r);
+ assertEquals(r, r2);
+ }
+
+ public void testParceling() {
+ RouteInfo r;
+
+ r = new RouteInfo(Prefix("::/0"), Address("2001:db8::"), null);
+ assertParcelingIsLossless(r);
+
+ r = new RouteInfo(Prefix("192.0.2.0/24"), null, "wlan0");
+ assertParcelingIsLossless(r);
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/net/NetworkStatsFactoryTest.java b/core/tests/coretests/src/com/android/internal/net/NetworkStatsFactoryTest.java
new file mode 100644
index 0000000..d3dd01a
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/net/NetworkStatsFactoryTest.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2011 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.internal.net;
+
+import static android.net.NetworkStats.SET_ALL;
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.SET_FOREGROUND;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkStats.UID_ALL;
+import static com.android.server.NetworkManagementSocketTagger.kernelToTag;
+
+import android.content.res.Resources;
+import android.net.NetworkStats;
+import android.net.TrafficStats;
+import android.test.AndroidTestCase;
+
+import com.android.frameworks.coretests.R;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import libcore.io.IoUtils;
+import libcore.io.Streams;
+
+/**
+ * Tests for {@link NetworkStatsFactory}.
+ */
+public class NetworkStatsFactoryTest extends AndroidTestCase {
+ private File mTestProc;
+ private NetworkStatsFactory mFactory;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ mTestProc = new File(getContext().getFilesDir(), "proc");
+ if (mTestProc.exists()) {
+ IoUtils.deleteContents(mTestProc);
+ }
+
+ mFactory = new NetworkStatsFactory(mTestProc);
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ mFactory = null;
+
+ if (mTestProc.exists()) {
+ IoUtils.deleteContents(mTestProc);
+ }
+
+ super.tearDown();
+ }
+
+ public void testNetworkStatsDetail() throws Exception {
+ stageFile(R.raw.xt_qtaguid_typical, new File(mTestProc, "net/xt_qtaguid/stats"));
+
+ final NetworkStats stats = mFactory.readNetworkStatsDetail();
+ assertEquals(70, stats.size());
+ assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, 18621L, 2898L);
+ assertStatsEntry(stats, "wlan0", 10011, SET_DEFAULT, 0x0, 35777L, 5718L);
+ assertStatsEntry(stats, "wlan0", 10021, SET_DEFAULT, 0x7fffff01, 562386L, 49228L);
+ assertStatsEntry(stats, "rmnet1", 10021, SET_DEFAULT, 0x30100000, 219110L, 227423L);
+ assertStatsEntry(stats, "rmnet2", 10001, SET_DEFAULT, 0x0, 1125899906842624L, 984L);
+ }
+
+ public void testKernelTags() throws Exception {
+ assertEquals(0, kernelToTag("0x0000000000000000"));
+ assertEquals(0x32, kernelToTag("0x0000003200000000"));
+ assertEquals(2147483647, kernelToTag("0x7fffffff00000000"));
+ assertEquals(0, kernelToTag("0x0000000000000000"));
+ assertEquals(2147483136, kernelToTag("0x7FFFFE0000000000"));
+
+ assertEquals(0, kernelToTag("0x0"));
+ assertEquals(0, kernelToTag("0xf00d"));
+ assertEquals(1, kernelToTag("0x100000000"));
+ assertEquals(14438007, kernelToTag("0xdc4e7700000000"));
+ assertEquals(TrafficStats.TAG_SYSTEM_DOWNLOAD, kernelToTag("0xffffff0100000000"));
+ }
+
+ public void testNetworkStatsWithSet() throws Exception {
+ stageFile(R.raw.xt_qtaguid_typical, new File(mTestProc, "net/xt_qtaguid/stats"));
+
+ final NetworkStats stats = mFactory.readNetworkStatsDetail();
+ assertEquals(70, stats.size());
+ assertStatsEntry(stats, "rmnet1", 10021, SET_DEFAULT, 0x30100000, 219110L, 578L, 227423L, 676L);
+ assertStatsEntry(stats, "rmnet1", 10021, SET_FOREGROUND, 0x30100000, 742L, 3L, 1265L, 3L);
+ }
+
+ public void testNetworkStatsSingle() throws Exception {
+ stageFile(R.raw.xt_qtaguid_iface_typical, new File(mTestProc, "net/xt_qtaguid/iface_stat_all"));
+
+ final NetworkStats stats = mFactory.readNetworkStatsSummaryDev();
+ assertEquals(6, stats.size());
+ assertStatsEntry(stats, "rmnet0", UID_ALL, SET_ALL, TAG_NONE, 2112L, 24L, 700L, 10L);
+ assertStatsEntry(stats, "test1", UID_ALL, SET_ALL, TAG_NONE, 6L, 8L, 10L, 12L);
+ assertStatsEntry(stats, "test2", UID_ALL, SET_ALL, TAG_NONE, 1L, 2L, 3L, 4L);
+ }
+
+ public void testNetworkStatsXt() throws Exception {
+ stageFile(R.raw.xt_qtaguid_iface_fmt_typical,
+ new File(mTestProc, "net/xt_qtaguid/iface_stat_fmt"));
+
+ final NetworkStats stats = mFactory.readNetworkStatsSummaryXt();
+ assertEquals(3, stats.size());
+ assertStatsEntry(stats, "rmnet0", UID_ALL, SET_ALL, TAG_NONE, 6824L, 16L, 5692L, 10L);
+ assertStatsEntry(stats, "rmnet1", UID_ALL, SET_ALL, TAG_NONE, 11153922L, 8051L, 190226L, 2468L);
+ assertStatsEntry(stats, "rmnet2", UID_ALL, SET_ALL, TAG_NONE, 4968L, 35L, 3081L, 39L);
+ }
+
+ /**
+ * Copy a {@link Resources#openRawResource(int)} into {@link File} for
+ * testing purposes.
+ */
+ private void stageFile(int rawId, File file) throws Exception {
+ new File(file.getParent()).mkdirs();
+ InputStream in = null;
+ OutputStream out = null;
+ try {
+ in = getContext().getResources().openRawResource(rawId);
+ out = new FileOutputStream(file);
+ Streams.copy(in, out);
+ } finally {
+ IoUtils.closeQuietly(in);
+ IoUtils.closeQuietly(out);
+ }
+ }
+
+ private void stageLong(long value, File file) throws Exception {
+ new File(file.getParent()).mkdirs();
+ FileWriter out = null;
+ try {
+ out = new FileWriter(file);
+ out.write(Long.toString(value));
+ } finally {
+ IoUtils.closeQuietly(out);
+ }
+ }
+
+ private static void assertStatsEntry(NetworkStats stats, String iface, int uid, int set,
+ int tag, long rxBytes, long txBytes) {
+ final int i = stats.findIndex(iface, uid, set, tag);
+ final NetworkStats.Entry entry = stats.getValues(i, null);
+ assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
+ assertEquals("unexpected txBytes", txBytes, entry.txBytes);
+ }
+
+ private static void assertStatsEntry(NetworkStats stats, String iface, int uid, int set,
+ int tag, long rxBytes, long rxPackets, long txBytes, long txPackets) {
+ final int i = stats.findIndex(iface, uid, set, tag);
+ final NetworkStats.Entry entry = stats.getValues(i, null);
+ assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
+ assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets);
+ assertEquals("unexpected txBytes", txBytes, entry.txBytes);
+ assertEquals("unexpected txPackets", txPackets, entry.txPackets);
+ }
+
+}
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index 20f3853..b8f8aae 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -936,7 +936,17 @@
private final Context mContext;
- private final TetheringManager mTetheringManager;
+ @GuardedBy("mTetheringEventCallbacks")
+ private TetheringManager mTetheringManager;
+
+ private TetheringManager getTetheringManager() {
+ synchronized (mTetheringEventCallbacks) {
+ if (mTetheringManager == null) {
+ mTetheringManager = mContext.getSystemService(TetheringManager.class);
+ }
+ return mTetheringManager;
+ }
+ }
/**
* Tests if a given integer represents a valid network type.
@@ -2386,7 +2396,6 @@
public ConnectivityManager(Context context, IConnectivityManager service) {
mContext = Objects.requireNonNull(context, "missing context");
mService = Objects.requireNonNull(service, "missing IConnectivityManager");
- mTetheringManager = (TetheringManager) mContext.getSystemService(Context.TETHERING_SERVICE);
sInstance = this;
}
@@ -2457,7 +2466,7 @@
@UnsupportedAppUsage
@Deprecated
public String[] getTetherableIfaces() {
- return mTetheringManager.getTetherableIfaces();
+ return getTetheringManager().getTetherableIfaces();
}
/**
@@ -2472,7 +2481,7 @@
@UnsupportedAppUsage
@Deprecated
public String[] getTetheredIfaces() {
- return mTetheringManager.getTetheredIfaces();
+ return getTetheringManager().getTetheredIfaces();
}
/**
@@ -2493,7 +2502,7 @@
@UnsupportedAppUsage
@Deprecated
public String[] getTetheringErroredIfaces() {
- return mTetheringManager.getTetheringErroredIfaces();
+ return getTetheringManager().getTetheringErroredIfaces();
}
/**
@@ -2537,7 +2546,7 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@Deprecated
public int tether(String iface) {
- return mTetheringManager.tether(iface);
+ return getTetheringManager().tether(iface);
}
/**
@@ -2561,7 +2570,7 @@
@UnsupportedAppUsage
@Deprecated
public int untether(String iface) {
- return mTetheringManager.untether(iface);
+ return getTetheringManager().untether(iface);
}
/**
@@ -2587,7 +2596,7 @@
@RequiresPermission(anyOf = {android.Manifest.permission.TETHER_PRIVILEGED,
android.Manifest.permission.WRITE_SETTINGS})
public boolean isTetheringSupported() {
- return mTetheringManager.isTetheringSupported();
+ return getTetheringManager().isTetheringSupported();
}
/**
@@ -2680,7 +2689,7 @@
final TetheringRequest request = new TetheringRequest.Builder(type)
.setShouldShowEntitlementUi(showProvisioningUi).build();
- mTetheringManager.startTethering(request, executor, tetheringCallback);
+ getTetheringManager().startTethering(request, executor, tetheringCallback);
}
/**
@@ -2699,7 +2708,7 @@
@Deprecated
@RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
public void stopTethering(int type) {
- mTetheringManager.stopTethering(type);
+ getTetheringManager().stopTethering(type);
}
/**
@@ -2757,7 +2766,7 @@
synchronized (mTetheringEventCallbacks) {
mTetheringEventCallbacks.put(callback, tetherCallback);
- mTetheringManager.registerTetheringEventCallback(executor, tetherCallback);
+ getTetheringManager().registerTetheringEventCallback(executor, tetherCallback);
}
}
@@ -2779,7 +2788,7 @@
synchronized (mTetheringEventCallbacks) {
final TetheringEventCallback tetherCallback =
mTetheringEventCallbacks.remove(callback);
- mTetheringManager.unregisterTetheringEventCallback(tetherCallback);
+ getTetheringManager().unregisterTetheringEventCallback(tetherCallback);
}
}
@@ -2799,7 +2808,7 @@
@UnsupportedAppUsage
@Deprecated
public String[] getTetherableUsbRegexs() {
- return mTetheringManager.getTetherableUsbRegexs();
+ return getTetheringManager().getTetherableUsbRegexs();
}
/**
@@ -2817,7 +2826,7 @@
@UnsupportedAppUsage
@Deprecated
public String[] getTetherableWifiRegexs() {
- return mTetheringManager.getTetherableWifiRegexs();
+ return getTetheringManager().getTetherableWifiRegexs();
}
/**
@@ -2836,7 +2845,7 @@
@UnsupportedAppUsage
@Deprecated
public String[] getTetherableBluetoothRegexs() {
- return mTetheringManager.getTetherableBluetoothRegexs();
+ return getTetheringManager().getTetherableBluetoothRegexs();
}
/**
@@ -2860,7 +2869,7 @@
@UnsupportedAppUsage
@Deprecated
public int setUsbTethering(boolean enable) {
- return mTetheringManager.setUsbTethering(enable);
+ return getTetheringManager().setUsbTethering(enable);
}
/**
@@ -2976,7 +2985,7 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@Deprecated
public int getLastTetherError(String iface) {
- int error = mTetheringManager.getLastTetherError(iface);
+ int error = getTetheringManager().getLastTetherError(iface);
if (error == TetheringManager.TETHER_ERROR_UNKNOWN_TYPE) {
// TETHER_ERROR_UNKNOWN_TYPE was introduced with TetheringManager and has never been
// returned by ConnectivityManager. Convert it to the legacy TETHER_ERROR_UNKNOWN_IFACE
@@ -3058,7 +3067,7 @@
}
};
- mTetheringManager.requestLatestTetheringEntitlementResult(type, wrappedListener,
+ getTetheringManager().requestLatestTetheringEntitlementResult(type, wrappedListener,
showEntitlementUi);
}
@@ -4840,7 +4849,7 @@
public void factoryReset() {
try {
mService.factoryReset();
- mTetheringManager.stopAllTethering();
+ getTetheringManager().stopAllTethering();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/framework/src/android/net/ConnectivitySettingsManager.java b/framework/src/android/net/ConnectivitySettingsManager.java
index 03c3600..4644e4f 100644
--- a/framework/src/android/net/ConnectivitySettingsManager.java
+++ b/framework/src/android/net/ConnectivitySettingsManager.java
@@ -20,12 +20,13 @@
import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_PERFORMANCE;
import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_RELIABILITY;
+import static com.android.net.module.util.ConnectivitySettingsUtils.getPrivateDnsModeAsString;
+
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.content.ContentResolver;
import android.content.Context;
import android.net.ConnectivityManager.MultipathPreference;
import android.os.Process;
@@ -35,6 +36,7 @@
import android.util.ArraySet;
import android.util.Range;
+import com.android.net.module.util.ConnectivitySettingsUtils;
import com.android.net.module.util.ProxyUtils;
import java.lang.annotation.Retention;
@@ -345,20 +347,22 @@
/**
* One of the private DNS modes that indicates the private DNS mode is off.
*/
- public static final int PRIVATE_DNS_MODE_OFF = 1;
+ public static final int PRIVATE_DNS_MODE_OFF = ConnectivitySettingsUtils.PRIVATE_DNS_MODE_OFF;
/**
* One of the private DNS modes that indicates the private DNS mode is automatic, which
* will try to use the current DNS as private DNS.
*/
- public static final int PRIVATE_DNS_MODE_OPPORTUNISTIC = 2;
+ public static final int PRIVATE_DNS_MODE_OPPORTUNISTIC =
+ ConnectivitySettingsUtils.PRIVATE_DNS_MODE_OPPORTUNISTIC;
/**
* One of the private DNS modes that indicates the private DNS mode is strict and the
* {@link #PRIVATE_DNS_SPECIFIER} is required, which will try to use the value of
* {@link #PRIVATE_DNS_SPECIFIER} as private DNS.
*/
- public static final int PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = 3;
+ public static final int PRIVATE_DNS_MODE_PROVIDER_HOSTNAME =
+ ConnectivitySettingsUtils.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -369,10 +373,6 @@
})
public @interface PrivateDnsMode {}
- private static final String PRIVATE_DNS_MODE_OFF_STRING = "off";
- private static final String PRIVATE_DNS_MODE_OPPORTUNISTIC_STRING = "opportunistic";
- private static final String PRIVATE_DNS_MODE_PROVIDER_HOSTNAME_STRING = "hostname";
-
/**
* A list of uids that is allowed to use restricted networks.
*
@@ -730,32 +730,6 @@
context.getContentResolver(), GLOBAL_HTTP_PROXY_PAC, "" /* value */);
}
- private static String getPrivateDnsModeAsString(@PrivateDnsMode int mode) {
- switch (mode) {
- case PRIVATE_DNS_MODE_OFF:
- return PRIVATE_DNS_MODE_OFF_STRING;
- case PRIVATE_DNS_MODE_OPPORTUNISTIC:
- return PRIVATE_DNS_MODE_OPPORTUNISTIC_STRING;
- case PRIVATE_DNS_MODE_PROVIDER_HOSTNAME:
- return PRIVATE_DNS_MODE_PROVIDER_HOSTNAME_STRING;
- default:
- throw new IllegalArgumentException("Invalid private dns mode: " + mode);
- }
- }
-
- private static int getPrivateDnsModeAsInt(String mode) {
- switch (mode) {
- case "off":
- return PRIVATE_DNS_MODE_OFF;
- case "hostname":
- return PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
- case "opportunistic":
- return PRIVATE_DNS_MODE_OPPORTUNISTIC;
- default:
- throw new IllegalArgumentException("Invalid private dns mode: " + mode);
- }
- }
-
/**
* Get private DNS mode from settings.
*
@@ -764,13 +738,7 @@
*/
@PrivateDnsMode
public static int getPrivateDnsMode(@NonNull Context context) {
- final ContentResolver cr = context.getContentResolver();
- String mode = Settings.Global.getString(cr, PRIVATE_DNS_MODE);
- if (TextUtils.isEmpty(mode)) mode = Settings.Global.getString(cr, PRIVATE_DNS_DEFAULT_MODE);
- // If both PRIVATE_DNS_MODE and PRIVATE_DNS_DEFAULT_MODE are not set, choose
- // PRIVATE_DNS_MODE_OPPORTUNISTIC as default mode.
- if (TextUtils.isEmpty(mode)) return PRIVATE_DNS_MODE_OPPORTUNISTIC;
- return getPrivateDnsModeAsInt(mode);
+ return ConnectivitySettingsUtils.getPrivateDnsMode(context);
}
/**
@@ -780,13 +748,7 @@
* @param mode The private dns mode. This should be one of the PRIVATE_DNS_MODE_* constants.
*/
public static void setPrivateDnsMode(@NonNull Context context, @PrivateDnsMode int mode) {
- if (!(mode == PRIVATE_DNS_MODE_OFF
- || mode == PRIVATE_DNS_MODE_OPPORTUNISTIC
- || mode == PRIVATE_DNS_MODE_PROVIDER_HOSTNAME)) {
- throw new IllegalArgumentException("Invalid private dns mode: " + mode);
- }
- Settings.Global.putString(context.getContentResolver(), PRIVATE_DNS_MODE,
- getPrivateDnsModeAsString(mode));
+ ConnectivitySettingsUtils.setPrivateDnsMode(context, mode);
}
/**
@@ -797,7 +759,7 @@
*/
@Nullable
public static String getPrivateDnsHostname(@NonNull Context context) {
- return Settings.Global.getString(context.getContentResolver(), PRIVATE_DNS_SPECIFIER);
+ return ConnectivitySettingsUtils.getPrivateDnsHostname(context);
}
/**
@@ -806,9 +768,8 @@
* @param context The {@link Context} to set the setting.
* @param specifier The specific private dns provider name.
*/
- public static void setPrivateDnsHostname(@NonNull Context context,
- @Nullable String specifier) {
- Settings.Global.putString(context.getContentResolver(), PRIVATE_DNS_SPECIFIER, specifier);
+ public static void setPrivateDnsHostname(@NonNull Context context, @Nullable String specifier) {
+ ConnectivitySettingsUtils.setPrivateDnsHostname(context, specifier);
}
/**
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
new file mode 100644
index 0000000..657d5ec
--- /dev/null
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -0,0 +1,6047 @@
+/*
+ * Copyright (C) 2008 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.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.NetworkCallbackListener;
+import static android.net.ConnectivityManager.TYPE_BLUETOOTH;
+import static android.net.ConnectivityManager.TYPE_DUMMY;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_MOBILE_MMS;
+import static android.net.ConnectivityManager.TYPE_MOBILE_SUPL;
+import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
+import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA;
+import static android.net.ConnectivityManager.TYPE_MOBILE_IMS;
+import static android.net.ConnectivityManager.TYPE_MOBILE_CBS;
+import static android.net.ConnectivityManager.TYPE_MOBILE_IA;
+import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
+import static android.net.ConnectivityManager.TYPE_NONE;
+import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.net.ConnectivityManager.TYPE_WIMAX;
+import static android.net.ConnectivityManager.TYPE_PROXY;
+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;
+
+import android.app.AlarmManager;
+import android.app.AppOpsManager;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.bluetooth.BluetoothTetheringDataTracker;
+import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.net.CaptivePortalTracker;
+import android.net.ConnectivityManager;
+import android.net.DummyDataStateTracker;
+import android.net.IConnectivityManager;
+import android.net.INetworkManagementEventObserver;
+import android.net.INetworkPolicyListener;
+import android.net.INetworkPolicyManager;
+import android.net.INetworkStatsService;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.LinkProperties.CompareResult;
+import android.net.LinkQualityInfo;
+import android.net.MobileDataStateTracker;
+import android.net.Network;
+import android.net.NetworkAgent;
+import android.net.NetworkCapabilities;
+import android.net.NetworkConfig;
+import android.net.NetworkInfo;
+import android.net.NetworkInfo.DetailedState;
+import android.net.NetworkFactory;
+import android.net.NetworkQuotaInfo;
+import android.net.NetworkRequest;
+import android.net.NetworkState;
+import android.net.NetworkStateTracker;
+import android.net.NetworkUtils;
+import android.net.Proxy;
+import android.net.ProxyDataTracker;
+import android.net.ProxyInfo;
+import android.net.RouteInfo;
+import android.net.SamplingDataTracker;
+import android.net.Uri;
+import android.net.wimax.WimaxManagerConstants;
+import android.os.AsyncTask;
+import android.os.Binder;
+import android.os.Build;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.INetworkManagementService;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.ParcelFileDescriptor;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.security.Credentials;
+import android.security.KeyStore;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.util.Xml;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.net.LegacyVpnInfo;
+import com.android.internal.net.VpnConfig;
+import com.android.internal.net.VpnProfile;
+import com.android.internal.telephony.DctConstants;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.util.AsyncChannel;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.XmlUtils;
+import com.android.server.am.BatteryStatsService;
+import com.android.server.connectivity.DataConnectionStats;
+import com.android.server.connectivity.Nat464Xlat;
+import com.android.server.connectivity.NetworkAgentInfo;
+import com.android.server.connectivity.NetworkMonitor;
+import com.android.server.connectivity.PacManager;
+import com.android.server.connectivity.Tethering;
+import com.android.server.connectivity.Vpn;
+import com.android.server.net.BaseNetworkObserver;
+import com.android.server.net.LockdownVpnTracker;
+import com.google.android.collect.Lists;
+import com.google.android.collect.Sets;
+
+import dalvik.system.DexClassLoader;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.reflect.Constructor;
+import java.net.HttpURLConnection;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLSession;
+
+import static android.net.ConnectivityManager.INVALID_NET_ID;
+
+/**
+ * @hide
+ */
+public class ConnectivityService extends IConnectivityManager.Stub {
+ private static final String TAG = "ConnectivityService";
+
+ private static final boolean DBG = true;
+ private static final boolean VDBG = true; // STOPSHIP
+
+ // network sampling debugging
+ private static final boolean SAMPLE_DBG = false;
+
+ private static final boolean LOGD_RULES = false;
+
+ // TODO: create better separation between radio types and network types
+
+ // how long to wait before switching back to a radio's default network
+ private static final int RESTORE_DEFAULT_NETWORK_DELAY = 1 * 60 * 1000;
+ // system property that can override the above value
+ private static final String NETWORK_RESTORE_DELAY_PROP_NAME =
+ "android.telephony.apn-restore";
+
+ // Default value if FAIL_FAST_TIME_MS is not set
+ private static final int DEFAULT_FAIL_FAST_TIME_MS = 1 * 60 * 1000;
+ // system property that can override DEFAULT_FAIL_FAST_TIME_MS
+ private static final String FAIL_FAST_TIME_MS =
+ "persist.radio.fail_fast_time_ms";
+
+ private static final String ACTION_PKT_CNT_SAMPLE_INTERVAL_ELAPSED =
+ "android.net.ConnectivityService.action.PKT_CNT_SAMPLE_INTERVAL_ELAPSED";
+
+ private static final int SAMPLE_INTERVAL_ELAPSED_REQUEST_CODE = 0;
+
+ private PendingIntent mSampleIntervalElapsedIntent;
+
+ // Set network sampling interval at 12 minutes, this way, even if the timers get
+ // aggregated, it will fire at around 15 minutes, which should allow us to
+ // aggregate this timer with other timers (specially the socket keep alive timers)
+ private static final int DEFAULT_SAMPLING_INTERVAL_IN_SECONDS = (SAMPLE_DBG ? 30 : 12 * 60);
+
+ // start network sampling a minute after booting ...
+ private static final int DEFAULT_START_SAMPLING_INTERVAL_IN_SECONDS = (SAMPLE_DBG ? 30 : 60);
+
+ AlarmManager mAlarmManager;
+
+ // used in recursive route setting to add gateways for the host for which
+ // a host route was requested.
+ private static final int MAX_HOSTROUTE_CYCLE_COUNT = 10;
+
+ private Tethering mTethering;
+
+ private KeyStore mKeyStore;
+
+ @GuardedBy("mVpns")
+ private final SparseArray<Vpn> mVpns = new SparseArray<Vpn>();
+ private VpnCallback mVpnCallback = new VpnCallback();
+
+ private boolean mLockdownEnabled;
+ private LockdownVpnTracker mLockdownTracker;
+
+ private Nat464Xlat mClat;
+
+ /** Lock around {@link #mUidRules} and {@link #mMeteredIfaces}. */
+ private Object mRulesLock = new Object();
+ /** Currently active network rules by UID. */
+ private SparseIntArray mUidRules = new SparseIntArray();
+ /** Set of ifaces that are costly. */
+ private HashSet<String> mMeteredIfaces = Sets.newHashSet();
+
+ /**
+ * Sometimes we want to refer to the individual network state
+ * trackers separately, and sometimes we just want to treat them
+ * abstractly.
+ */
+ private NetworkStateTracker mNetTrackers[];
+
+ /* Handles captive portal check on a network */
+ private CaptivePortalTracker mCaptivePortalTracker;
+
+ /**
+ * The link properties that define the current links
+ */
+ private LinkProperties mCurrentLinkProperties[];
+
+ /**
+ * A per Net list of the PID's that requested access to the net
+ * used both as a refcount and for per-PID DNS selection
+ */
+ private List<Integer> mNetRequestersPids[];
+
+ // priority order of the nettrackers
+ // (excluding dynamically set mNetworkPreference)
+ // TODO - move mNetworkTypePreference into this
+ private int[] mPriorityList;
+
+ private Context mContext;
+ private int mNetworkPreference;
+ private int mActiveDefaultNetwork = -1;
+ // 0 is full bad, 100 is full good
+ private int mDefaultInetCondition = 0;
+ private int mDefaultInetConditionPublished = 0;
+ private boolean mInetConditionChangeInFlight = false;
+ private int mDefaultConnectionSequence = 0;
+
+ private Object mDnsLock = new Object();
+ private int mNumDnsEntries;
+
+ private boolean mTestMode;
+ private static ConnectivityService sServiceInstance;
+
+ private INetworkManagementService mNetd;
+ private INetworkPolicyManager mPolicyManager;
+
+ private static final int ENABLED = 1;
+ private static final int DISABLED = 0;
+
+ private static final boolean ADD = true;
+ private static final boolean REMOVE = false;
+
+ private static final boolean TO_DEFAULT_TABLE = true;
+ private static final boolean TO_SECONDARY_TABLE = false;
+
+ private static final boolean EXEMPT = true;
+ private static final boolean UNEXEMPT = false;
+
+ /**
+ * used internally as a delayed event to make us switch back to the
+ * default network
+ */
+ private static final int EVENT_RESTORE_DEFAULT_NETWORK = 1;
+
+ /**
+ * used internally to change our mobile data enabled flag
+ */
+ private static final int EVENT_CHANGE_MOBILE_DATA_ENABLED = 2;
+
+ /**
+ * used internally to synchronize inet condition reports
+ * arg1 = networkType
+ * arg2 = condition (0 bad, 100 good)
+ */
+ private static final int EVENT_INET_CONDITION_CHANGE = 4;
+
+ /**
+ * used internally to mark the end of inet condition hold periods
+ * arg1 = networkType
+ */
+ private static final int EVENT_INET_CONDITION_HOLD_END = 5;
+
+ /**
+ * used internally to clear a wakelock when transitioning
+ * from one net to another
+ */
+ private static final int EVENT_CLEAR_NET_TRANSITION_WAKELOCK = 8;
+
+ /**
+ * used internally to reload global proxy settings
+ */
+ private static final int EVENT_APPLY_GLOBAL_HTTP_PROXY = 9;
+
+ /**
+ * used internally to set external dependency met/unmet
+ * arg1 = ENABLED (met) or DISABLED (unmet)
+ * arg2 = NetworkType
+ */
+ private static final int EVENT_SET_DEPENDENCY_MET = 10;
+
+ /**
+ * used internally to send a sticky broadcast delayed.
+ */
+ private static final int EVENT_SEND_STICKY_BROADCAST_INTENT = 11;
+
+ /**
+ * Used internally to
+ * {@link NetworkStateTracker#setPolicyDataEnable(boolean)}.
+ */
+ private static final int EVENT_SET_POLICY_DATA_ENABLE = 12;
+
+ private static final int EVENT_VPN_STATE_CHANGED = 13;
+
+ /**
+ * Used internally to disable fail fast of mobile data
+ */
+ private static final int EVENT_ENABLE_FAIL_FAST_MOBILE_DATA = 14;
+
+ /**
+ * used internally to indicate that data sampling interval is up
+ */
+ private static final int EVENT_SAMPLE_INTERVAL_ELAPSED = 15;
+
+ /**
+ * PAC manager has received new port.
+ */
+ private static final int EVENT_PROXY_HAS_CHANGED = 16;
+
+ /**
+ * used internally when registering NetworkFactories
+ * obj = NetworkFactoryInfo
+ */
+ private static final int EVENT_REGISTER_NETWORK_FACTORY = 17;
+
+ /**
+ * used internally when registering NetworkAgents
+ * obj = Messenger
+ */
+ private static final int EVENT_REGISTER_NETWORK_AGENT = 18;
+
+ /**
+ * used to add a network request
+ * includes a NetworkRequestInfo
+ */
+ private static final int EVENT_REGISTER_NETWORK_REQUEST = 19;
+
+ /**
+ * indicates a timeout period is over - check if we had a network yet or not
+ * and if not, call the timeout calback (but leave the request live until they
+ * cancel it.
+ * includes a NetworkRequestInfo
+ */
+ private static final int EVENT_TIMEOUT_NETWORK_REQUEST = 20;
+
+ /**
+ * used to add a network listener - no request
+ * includes a NetworkRequestInfo
+ */
+ private static final int EVENT_REGISTER_NETWORK_LISTENER = 21;
+
+ /**
+ * used to remove a network request, either a listener or a real request
+ * includes a NetworkRequest
+ */
+ private static final int EVENT_RELEASE_NETWORK_REQUEST = 22;
+
+ /**
+ * used internally when registering NetworkFactories
+ * obj = Messenger
+ */
+ private static final int EVENT_UNREGISTER_NETWORK_FACTORY = 23;
+
+
+ /** Handler used for internal events. */
+ final private InternalHandler mHandler;
+ /** Handler used for incoming {@link NetworkStateTracker} events. */
+ final private NetworkStateTrackerHandler mTrackerHandler;
+
+ // list of DeathRecipients used to make sure features are turned off when
+ // a process dies
+ private List<FeatureUser> mFeatureUsers;
+
+ private boolean mSystemReady;
+ private Intent mInitialBroadcast;
+
+ private PowerManager.WakeLock mNetTransitionWakeLock;
+ private String mNetTransitionWakeLockCausedBy = "";
+ private int mNetTransitionWakeLockSerialNumber;
+ private int mNetTransitionWakeLockTimeout;
+
+ private InetAddress mDefaultDns;
+
+ // Lock for protecting access to mAddedRoutes and mExemptAddresses
+ private final Object mRoutesLock = new Object();
+
+ // this collection is used to refcount the added routes - if there are none left
+ // it's time to remove the route from the route table
+ @GuardedBy("mRoutesLock")
+ private Collection<RouteInfo> mAddedRoutes = new ArrayList<RouteInfo>();
+
+ // this collection corresponds to the entries of mAddedRoutes that have routing exemptions
+ // used to handle cleanup of exempt rules
+ @GuardedBy("mRoutesLock")
+ private Collection<LinkAddress> mExemptAddresses = new ArrayList<LinkAddress>();
+
+ // used in DBG mode to track inet condition reports
+ private static final int INET_CONDITION_LOG_MAX_SIZE = 15;
+ private ArrayList mInetLog;
+
+ // track the current default http proxy - tell the world if we get a new one (real change)
+ private ProxyInfo mDefaultProxy = null;
+ private Object mProxyLock = new Object();
+ private boolean mDefaultProxyDisabled = false;
+
+ // track the global proxy.
+ private ProxyInfo mGlobalProxy = null;
+
+ private PacManager mPacManager = null;
+
+ private SettingsObserver mSettingsObserver;
+
+ private AppOpsManager mAppOpsManager;
+
+ NetworkConfig[] mNetConfigs;
+ int mNetworksDefined;
+
+ private static class RadioAttributes {
+ public int mSimultaneity;
+ public int mType;
+ public RadioAttributes(String init) {
+ String fragments[] = init.split(",");
+ mType = Integer.parseInt(fragments[0]);
+ mSimultaneity = Integer.parseInt(fragments[1]);
+ }
+ }
+ RadioAttributes[] mRadioAttributes;
+
+ // the set of network types that can only be enabled by system/sig apps
+ List mProtectedNetworks;
+
+ private DataConnectionStats mDataConnectionStats;
+
+ private AtomicInteger mEnableFailFastMobileDataTag = new AtomicInteger(0);
+
+ TelephonyManager mTelephonyManager;
+
+ // sequence number for Networks
+ private final static int MIN_NET_ID = 10; // some reserved marks
+ private final static int MAX_NET_ID = 65535;
+ private int mNextNetId = MIN_NET_ID;
+
+ // sequence number of NetworkRequests
+ private int mNextNetworkRequestId = 1;
+
+ private static final int UID_UNUSED = -1;
+
+ /**
+ * Implements support for the legacy "one network per network type" model.
+ *
+ * We used to have a static array of NetworkStateTrackers, one for each
+ * network type, but that doesn't work any more now that we can have,
+ * for example, more that one wifi network. This class stores all the
+ * NetworkAgentInfo objects that support a given type, but the legacy
+ * API will only see the first one.
+ *
+ * It serves two main purposes:
+ *
+ * 1. Provide information about "the network for a given type" (since this
+ * API only supports one).
+ * 2. Send legacy connectivity change broadcasts. Broadcasts are sent if
+ * the first network for a given type changes, or if the default network
+ * changes.
+ */
+ private class LegacyTypeTracker {
+ /**
+ * Array of lists, one per legacy network type (e.g., TYPE_MOBILE_MMS).
+ * Each list holds references to all NetworkAgentInfos that are used to
+ * satisfy requests for that network type.
+ *
+ * This array is built out at startup such that an unsupported network
+ * doesn't get an ArrayList instance, making this a tristate:
+ * unsupported, supported but not active and active.
+ *
+ * The actual lists are populated when we scan the network types that
+ * are supported on this device.
+ */
+ private ArrayList<NetworkAgentInfo> mTypeLists[];
+
+ public LegacyTypeTracker() {
+ mTypeLists = (ArrayList<NetworkAgentInfo>[])
+ new ArrayList[ConnectivityManager.MAX_NETWORK_TYPE + 1];
+ }
+
+ public void addSupportedType(int type) {
+ if (mTypeLists[type] != null) {
+ throw new IllegalStateException(
+ "legacy list for type " + type + "already initialized");
+ }
+ mTypeLists[type] = new ArrayList<NetworkAgentInfo>();
+ }
+
+ private boolean isDefaultNetwork(NetworkAgentInfo nai) {
+ return mNetworkForRequestId.get(mDefaultRequest.requestId) == nai;
+ }
+
+ public boolean isTypeSupported(int type) {
+ return isNetworkTypeValid(type) && mTypeLists[type] != null;
+ }
+
+ public NetworkAgentInfo getNetworkForType(int type) {
+ if (isTypeSupported(type) && !mTypeLists[type].isEmpty()) {
+ return mTypeLists[type].get(0);
+ } else {
+ return null;
+ }
+ }
+
+ public void add(int type, NetworkAgentInfo nai) {
+ if (!isTypeSupported(type)) {
+ return; // Invalid network type.
+ }
+ if (VDBG) log("Adding agent " + nai + " for legacy network type " + type);
+
+ ArrayList<NetworkAgentInfo> list = mTypeLists[type];
+ if (list.contains(nai)) {
+ loge("Attempting to register duplicate agent for type " + type + ": " + nai);
+ return;
+ }
+
+ if (list.isEmpty() || isDefaultNetwork(nai)) {
+ if (VDBG) log("Sending connected broadcast for type " + type +
+ "isDefaultNetwork=" + isDefaultNetwork(nai));
+ sendLegacyNetworkBroadcast(nai, true, type);
+ }
+ list.add(nai);
+ }
+
+ public void remove(NetworkAgentInfo nai) {
+ if (VDBG) log("Removing agent " + nai);
+ for (int type = 0; type < mTypeLists.length; type++) {
+ ArrayList<NetworkAgentInfo> list = mTypeLists[type];
+ if (list == null || list.isEmpty()) {
+ continue;
+ }
+
+ boolean wasFirstNetwork = false;
+ if (list.get(0).equals(nai)) {
+ // This network was the first in the list. Send broadcast.
+ wasFirstNetwork = true;
+ }
+ list.remove(nai);
+
+ if (wasFirstNetwork || isDefaultNetwork(nai)) {
+ if (VDBG) log("Sending disconnected broadcast for type " + type +
+ "isDefaultNetwork=" + isDefaultNetwork(nai));
+ sendLegacyNetworkBroadcast(nai, false, type);
+ }
+
+ if (!list.isEmpty() && wasFirstNetwork) {
+ if (VDBG) log("Other network available for type " + type +
+ ", sending connected broadcast");
+ sendLegacyNetworkBroadcast(list.get(0), false, type);
+ }
+ }
+ }
+ }
+ private LegacyTypeTracker mLegacyTypeTracker = new LegacyTypeTracker();
+
+ 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 netManager,
+ INetworkStatsService statsService, INetworkPolicyManager policyManager,
+ NetworkFactory netFactory) {
+ if (DBG) log("ConnectivityService starting up");
+
+ NetworkCapabilities netCap = new NetworkCapabilities();
+ netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+ netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+ mDefaultRequest = new NetworkRequest(netCap, TYPE_NONE, nextNetworkRequestId());
+ NetworkRequestInfo nri = new NetworkRequestInfo(null, mDefaultRequest, new Binder(),
+ NetworkRequestInfo.REQUEST);
+ mNetworkRequests.put(mDefaultRequest, nri);
+
+ HandlerThread handlerThread = new HandlerThread("ConnectivityServiceThread");
+ handlerThread.start();
+ 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(),
+ Settings.Secure.ANDROID_ID);
+ if (id != null && id.length() > 0) {
+ String name = new String("android-").concat(id);
+ SystemProperties.set("net.hostname", name);
+ }
+ }
+
+ // read our default dns server ip
+ String dns = Settings.Global.getString(context.getContentResolver(),
+ Settings.Global.DEFAULT_DNS_SERVER);
+ if (dns == null || dns.length() == 0) {
+ dns = context.getResources().getString(
+ com.android.internal.R.string.config_default_dns_server);
+ }
+ try {
+ mDefaultDns = NetworkUtils.numericToInetAddress(dns);
+ } catch (IllegalArgumentException e) {
+ loge("Error setting defaultDns using " + dns);
+ }
+
+ mContext = checkNotNull(context, "missing Context");
+ mNetd = checkNotNull(netManager, "missing INetworkManagementService");
+ mPolicyManager = checkNotNull(policyManager, "missing INetworkPolicyManager");
+ mKeyStore = KeyStore.getInstance();
+ mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+
+ try {
+ mPolicyManager.registerListener(mPolicyListener);
+ } catch (RemoteException e) {
+ // ouch, no rules updates means some processes may never get network
+ loge("unable to register INetworkPolicyListener" + e.toString());
+ }
+
+ final PowerManager powerManager = (PowerManager) context.getSystemService(
+ Context.POWER_SERVICE);
+ mNetTransitionWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+ mNetTransitionWakeLockTimeout = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_networkTransitionTimeout);
+
+ mNetTrackers = new NetworkStateTracker[
+ ConnectivityManager.MAX_NETWORK_TYPE+1];
+ mCurrentLinkProperties = new LinkProperties[ConnectivityManager.MAX_NETWORK_TYPE+1];
+
+ mRadioAttributes = new RadioAttributes[ConnectivityManager.MAX_RADIO_TYPE+1];
+ mNetConfigs = new NetworkConfig[ConnectivityManager.MAX_NETWORK_TYPE+1];
+
+ // Load device network attributes from resources
+ String[] raStrings = context.getResources().getStringArray(
+ com.android.internal.R.array.radioAttributes);
+ for (String raString : raStrings) {
+ RadioAttributes r = new RadioAttributes(raString);
+ if (VDBG) log("raString=" + raString + " r=" + r);
+ if (r.mType > ConnectivityManager.MAX_RADIO_TYPE) {
+ loge("Error in radioAttributes - ignoring attempt to define type " + r.mType);
+ continue;
+ }
+ if (mRadioAttributes[r.mType] != null) {
+ loge("Error in radioAttributes - ignoring attempt to redefine type " +
+ r.mType);
+ continue;
+ }
+ mRadioAttributes[r.mType] = r;
+ }
+
+ // TODO: What is the "correct" way to do determine if this is a wifi only device?
+ boolean wifiOnly = SystemProperties.getBoolean("ro.radio.noril", false);
+ log("wifiOnly=" + wifiOnly);
+ String[] naStrings = context.getResources().getStringArray(
+ com.android.internal.R.array.networkAttributes);
+ for (String naString : naStrings) {
+ try {
+ NetworkConfig n = new NetworkConfig(naString);
+ if (VDBG) log("naString=" + naString + " config=" + n);
+ if (n.type > ConnectivityManager.MAX_NETWORK_TYPE) {
+ loge("Error in networkAttributes - ignoring attempt to define type " +
+ n.type);
+ continue;
+ }
+ if (wifiOnly && ConnectivityManager.isNetworkTypeMobile(n.type)) {
+ log("networkAttributes - ignoring mobile as this dev is wifiOnly " +
+ n.type);
+ continue;
+ }
+ if (mNetConfigs[n.type] != null) {
+ loge("Error in networkAttributes - ignoring attempt to redefine type " +
+ n.type);
+ continue;
+ }
+ if (mRadioAttributes[n.radio] == null) {
+ loge("Error in networkAttributes - ignoring attempt to use undefined " +
+ "radio " + n.radio + " in network type " + n.type);
+ continue;
+ }
+ mLegacyTypeTracker.addSupportedType(n.type);
+
+ mNetConfigs[n.type] = n;
+ mNetworksDefined++;
+ } catch(Exception e) {
+ // ignore it - leave the entry null
+ }
+ }
+ if (VDBG) log("mNetworksDefined=" + mNetworksDefined);
+
+ mProtectedNetworks = new ArrayList<Integer>();
+ int[] protectedNetworks = context.getResources().getIntArray(
+ com.android.internal.R.array.config_protectedNetworks);
+ for (int p : protectedNetworks) {
+ if ((mNetConfigs[p] != null) && (mProtectedNetworks.contains(p) == false)) {
+ mProtectedNetworks.add(p);
+ } else {
+ if (DBG) loge("Ignoring protectedNetwork " + p);
+ }
+ }
+
+ // high priority first
+ mPriorityList = new int[mNetworksDefined];
+ {
+ int insertionPoint = mNetworksDefined-1;
+ int currentLowest = 0;
+ int nextLowest = 0;
+ while (insertionPoint > -1) {
+ for (NetworkConfig na : mNetConfigs) {
+ if (na == null) continue;
+ if (na.priority < currentLowest) continue;
+ if (na.priority > currentLowest) {
+ if (na.priority < nextLowest || nextLowest == 0) {
+ nextLowest = na.priority;
+ }
+ continue;
+ }
+ mPriorityList[insertionPoint--] = na.type;
+ }
+ currentLowest = nextLowest;
+ nextLowest = 0;
+ }
+ }
+
+ mNetRequestersPids =
+ (List<Integer> [])new ArrayList[ConnectivityManager.MAX_NETWORK_TYPE+1];
+ for (int i : mPriorityList) {
+ mNetRequestersPids[i] = new ArrayList<Integer>();
+ }
+
+ mFeatureUsers = new ArrayList<FeatureUser>();
+
+ mTestMode = SystemProperties.get("cm.test.mode").equals("true")
+ && SystemProperties.get("ro.build.type").equals("eng");
+
+ // 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;
+ }
+
+ tracker.startMonitoring(context, mTrackerHandler);
+ if (config.isDefault()) {
+ tracker.reconnect();
+ }
+ }
+
+ mTethering = new Tethering(mContext, mNetd, statsService, this, mHandler.getLooper());
+
+ //set up the listener for user state for creating user VPNs
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_USER_STARTING);
+ intentFilter.addAction(Intent.ACTION_USER_STOPPING);
+ mContext.registerReceiverAsUser(
+ mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null);
+ mClat = new Nat464Xlat(mContext, mNetd, this, mTrackerHandler);
+
+ try {
+ mNetd.registerObserver(mTethering);
+ mNetd.registerObserver(mDataActivityObserver);
+ mNetd.registerObserver(mClat);
+ } catch (RemoteException e) {
+ loge("Error registering observer :" + e);
+ }
+
+ if (DBG) {
+ mInetLog = new ArrayList();
+ }
+
+ mSettingsObserver = new SettingsObserver(mHandler, EVENT_APPLY_GLOBAL_HTTP_PROXY);
+ mSettingsObserver.observe(mContext);
+
+ mDataConnectionStats = new DataConnectionStats(mContext);
+ mDataConnectionStats.startMonitoring();
+
+ // start network sampling ..
+ Intent intent = new Intent(ACTION_PKT_CNT_SAMPLE_INTERVAL_ELAPSED, null);
+ mSampleIntervalElapsedIntent = PendingIntent.getBroadcast(mContext,
+ SAMPLE_INTERVAL_ELAPSED_REQUEST_CODE, intent, 0);
+
+ mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
+ setAlarm(DEFAULT_START_SAMPLING_INTERVAL_IN_SECONDS * 1000, mSampleIntervalElapsedIntent);
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_PKT_CNT_SAMPLE_INTERVAL_ELAPSED);
+ mContext.registerReceiver(
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(ACTION_PKT_CNT_SAMPLE_INTERVAL_ELAPSED)) {
+ mHandler.sendMessage(mHandler.obtainMessage
+ (EVENT_SAMPLE_INTERVAL_ELAPSED));
+ }
+ }
+ },
+ new IntentFilter(filter));
+
+ mPacManager = new PacManager(mContext, mHandler, EVENT_PROXY_HAS_CHANGED);
+
+ filter = new IntentFilter();
+ filter.addAction(CONNECTED_TO_PROVISIONING_NETWORK_ACTION);
+ mContext.registerReceiver(mProvisioningReceiver, filter);
+
+ mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
+ }
+
+ private synchronized int nextNetworkRequestId() {
+ return mNextNetworkRequestId++;
+ }
+
+ private synchronized int nextNetId() {
+ int netId = mNextNetId;
+ if (++mNextNetId > MAX_NET_ID) mNextNetId = MIN_NET_ID;
+ return netId;
+ }
+
+ /**
+ * Factory that creates {@link NetworkStateTracker} instances using given
+ * {@link NetworkConfig}.
+ *
+ * TODO - this is obsolete and will be deleted. It's replaced by the
+ * registerNetworkFactory call and protocol.
+ * @Deprecated in favor of registerNetworkFactory dynamic bindings
+ */
+ 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_DUMMY:
+ return new DummyDataStateTracker(targetNetworkType, config.name);
+ case TYPE_BLUETOOTH:
+ return BluetoothTetheringDataTracker.getInstance();
+ case TYPE_WIMAX:
+ return makeWimaxStateTracker(mContext, mTrackerHandler);
+ case TYPE_PROXY:
+ return new ProxyDataTracker();
+ 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;
+ Class wimaxServiceClass = null;
+ Class wimaxManagerClass;
+ String wimaxJarLocation;
+ String wimaxLibLocation;
+ String wimaxManagerClassName;
+ String wimaxServiceClassName;
+ String wimaxStateTrackerClassName;
+
+ NetworkStateTracker wimaxStateTracker = null;
+
+ boolean isWimaxEnabled = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_wimaxEnabled);
+
+ if (isWimaxEnabled) {
+ try {
+ wimaxJarLocation = context.getResources().getString(
+ com.android.internal.R.string.config_wimaxServiceJarLocation);
+ wimaxLibLocation = context.getResources().getString(
+ com.android.internal.R.string.config_wimaxNativeLibLocation);
+ wimaxManagerClassName = context.getResources().getString(
+ com.android.internal.R.string.config_wimaxManagerClassname);
+ wimaxServiceClassName = context.getResources().getString(
+ com.android.internal.R.string.config_wimaxServiceClassname);
+ wimaxStateTrackerClassName = context.getResources().getString(
+ com.android.internal.R.string.config_wimaxStateTrackerClassname);
+
+ if (DBG) log("wimaxJarLocation: " + wimaxJarLocation);
+ wimaxClassLoader = new DexClassLoader(wimaxJarLocation,
+ new ContextWrapper(context).getCacheDir().getAbsolutePath(),
+ wimaxLibLocation, ClassLoader.getSystemClassLoader());
+
+ try {
+ wimaxManagerClass = wimaxClassLoader.loadClass(wimaxManagerClassName);
+ wimaxStateTrackerClass = wimaxClassLoader.loadClass(wimaxStateTrackerClassName);
+ wimaxServiceClass = wimaxClassLoader.loadClass(wimaxServiceClassName);
+ } catch (ClassNotFoundException ex) {
+ loge("Exception finding Wimax classes: " + ex.toString());
+ return null;
+ }
+ } catch(Resources.NotFoundException ex) {
+ loge("Wimax Resources does not exist!!! ");
+ return null;
+ }
+
+ try {
+ if (DBG) log("Starting Wimax Service... ");
+
+ Constructor wmxStTrkrConst = wimaxStateTrackerClass.getConstructor
+ (new Class[] {Context.class, Handler.class});
+ wimaxStateTracker = (NetworkStateTracker) wmxStTrkrConst.newInstance(
+ context, trackerHandler);
+
+ Constructor wmxSrvConst = wimaxServiceClass.getDeclaredConstructor
+ (new Class[] {Context.class, wimaxStateTrackerClass});
+ wmxSrvConst.setAccessible(true);
+ IBinder svcInvoker = (IBinder)wmxSrvConst.newInstance(context, wimaxStateTracker);
+ wmxSrvConst.setAccessible(false);
+
+ ServiceManager.addService(WimaxManagerConstants.WIMAX_SERVICE, svcInvoker);
+
+ } catch(Exception ex) {
+ loge("Exception creating Wimax classes: " + ex.toString());
+ return null;
+ }
+ } else {
+ loge("Wimax is not enabled or not added to the network attributes!!! ");
+ return null;
+ }
+
+ return wimaxStateTracker;
+ }
+
+ private int getConnectivityChangeDelay() {
+ final ContentResolver cr = mContext.getContentResolver();
+
+ /** Check system properties for the default value then use secure settings value, if any. */
+ int defaultDelay = SystemProperties.getInt(
+ "conn." + Settings.Global.CONNECTIVITY_CHANGE_DELAY,
+ ConnectivityManager.CONNECTIVITY_CHANGE_DELAY_DEFAULT);
+ return Settings.Global.getInt(cr, Settings.Global.CONNECTIVITY_CHANGE_DELAY,
+ defaultDelay);
+ }
+
+ private boolean teardown(NetworkStateTracker netTracker) {
+ if (netTracker.teardown()) {
+ netTracker.setTeardownRequested(true);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Check if UID should be blocked from using the network represented by the
+ * given {@link NetworkStateTracker}.
+ */
+ private boolean isNetworkBlocked(int networkType, int uid) {
+ final boolean networkCostly;
+ final int uidRules;
+
+ LinkProperties lp = getLinkPropertiesForType(networkType);
+ final String iface = (lp == null ? "" : lp.getInterfaceName());
+ synchronized (mRulesLock) {
+ networkCostly = mMeteredIfaces.contains(iface);
+ uidRules = mUidRules.get(uid, RULE_ALLOW_ALL);
+ }
+
+ if (networkCostly && (uidRules & RULE_REJECT_METERED) != 0) {
+ return true;
+ }
+
+ // no restrictive rules; network is visible
+ return false;
+ }
+
+ /**
+ * Return a filtered {@link NetworkInfo}, potentially marked
+ * {@link DetailedState#BLOCKED} based on
+ * {@link #isNetworkBlocked}.
+ */
+ private NetworkInfo getFilteredNetworkInfo(int networkType, int uid) {
+ NetworkInfo info = getNetworkInfoForType(networkType);
+ if (isNetworkBlocked(networkType, uid)) {
+ // network is blocked; clone and override state
+ info = new NetworkInfo(info);
+ info.setDetailedState(DetailedState.BLOCKED, null, null);
+ }
+ if (mLockdownTracker != null) {
+ info = mLockdownTracker.augmentNetworkInfo(info);
+ }
+ return info;
+ }
+
+ /**
+ * Return NetworkInfo for the active (i.e., connected) network interface.
+ * It is assumed that at most one network is active at a time. If more
+ * than one is active, it is indeterminate which will be returned.
+ * @return the info for the active network, or {@code null} if none is
+ * active
+ */
+ @Override
+ public NetworkInfo getActiveNetworkInfo() {
+ enforceAccessPermission();
+ final int uid = Binder.getCallingUid();
+ return getNetworkInfo(mActiveDefaultNetwork, uid);
+ }
+
+ // only called when the default request is satisfied
+ private void updateActiveDefaultNetwork(NetworkAgentInfo nai) {
+ if (nai != null) {
+ mActiveDefaultNetwork = nai.networkInfo.getType();
+ } else {
+ mActiveDefaultNetwork = TYPE_NONE;
+ }
+ }
+
+ /**
+ * Find the first Provisioning network.
+ *
+ * @return NetworkInfo or null if none.
+ */
+ private NetworkInfo getProvisioningNetworkInfo() {
+ enforceAccessPermission();
+
+ // Find the first Provisioning Network
+ NetworkInfo provNi = null;
+ for (NetworkInfo ni : getAllNetworkInfo()) {
+ if (ni.isConnectedToProvisioningNetwork()) {
+ provNi = ni;
+ break;
+ }
+ }
+ if (DBG) log("getProvisioningNetworkInfo: X provNi=" + provNi);
+ return provNi;
+ }
+
+ /**
+ * Find the first Provisioning network or the ActiveDefaultNetwork
+ * if there is no Provisioning network
+ *
+ * @return NetworkInfo or null if none.
+ */
+ @Override
+ public NetworkInfo getProvisioningOrActiveNetworkInfo() {
+ enforceAccessPermission();
+
+ NetworkInfo provNi = getProvisioningNetworkInfo();
+ if (provNi == null) {
+ final int uid = Binder.getCallingUid();
+ provNi = getNetworkInfo(mActiveDefaultNetwork, uid);
+ }
+ if (DBG) log("getProvisioningOrActiveNetworkInfo: X provNi=" + provNi);
+ return provNi;
+ }
+
+ public NetworkInfo getActiveNetworkInfoUnfiltered() {
+ enforceAccessPermission();
+ if (isNetworkTypeValid(mActiveDefaultNetwork)) {
+ return getNetworkInfoForType(mActiveDefaultNetwork);
+ }
+ return null;
+ }
+
+ @Override
+ public NetworkInfo getActiveNetworkInfoForUid(int uid) {
+ enforceConnectivityInternalPermission();
+ return getNetworkInfo(mActiveDefaultNetwork, uid);
+ }
+
+ @Override
+ public NetworkInfo getNetworkInfo(int networkType) {
+ enforceAccessPermission();
+ final int uid = Binder.getCallingUid();
+ return getNetworkInfo(networkType, uid);
+ }
+
+ private NetworkInfo getNetworkInfo(int networkType, int uid) {
+ NetworkInfo info = null;
+ if (isNetworkTypeValid(networkType)) {
+ if (getNetworkInfoForType(networkType) != null) {
+ info = getFilteredNetworkInfo(networkType, uid);
+ }
+ }
+ return info;
+ }
+
+ @Override
+ public NetworkInfo[] getAllNetworkInfo() {
+ enforceAccessPermission();
+ final int uid = Binder.getCallingUid();
+ final ArrayList<NetworkInfo> result = Lists.newArrayList();
+ synchronized (mRulesLock) {
+ for (int networkType = 0; networkType <= ConnectivityManager.MAX_NETWORK_TYPE;
+ networkType++) {
+ if (getNetworkInfoForType(networkType) != null) {
+ result.add(getFilteredNetworkInfo(networkType, uid));
+ }
+ }
+ }
+ return result.toArray(new NetworkInfo[result.size()]);
+ }
+
+ @Override
+ public boolean isNetworkSupported(int networkType) {
+ enforceAccessPermission();
+ return (isNetworkTypeValid(networkType) && (getNetworkInfoForType(networkType) != null));
+ }
+
+ /**
+ * Return LinkProperties for the active (i.e., connected) default
+ * network interface. It is assumed that at most one default network
+ * is active at a time. If more than one is active, it is indeterminate
+ * which will be returned.
+ * @return the ip properties for the active network, or {@code null} if
+ * none is active
+ */
+ @Override
+ public LinkProperties getActiveLinkProperties() {
+ return getLinkPropertiesForType(mActiveDefaultNetwork);
+ }
+
+ @Override
+ public LinkProperties getLinkPropertiesForType(int networkType) {
+ enforceAccessPermission();
+ if (isNetworkTypeValid(networkType)) {
+ return getLinkPropertiesForTypeInternal(networkType);
+ }
+ return null;
+ }
+
+ // TODO - this should be ALL networks
+ @Override
+ public LinkProperties getLinkProperties(Network network) {
+ enforceAccessPermission();
+ NetworkAgentInfo nai = mNetworkForNetId.get(network.netId);
+ if (nai != null) return new LinkProperties(nai.linkProperties);
+ return null;
+ }
+
+ @Override
+ public NetworkCapabilities getNetworkCapabilities(Network network) {
+ enforceAccessPermission();
+ NetworkAgentInfo nai = mNetworkForNetId.get(network.netId);
+ if (nai != null) return new NetworkCapabilities(nai.networkCapabilities);
+ return null;
+ }
+
+ @Override
+ public NetworkState[] getAllNetworkState() {
+ enforceAccessPermission();
+ final int uid = Binder.getCallingUid();
+ final ArrayList<NetworkState> result = Lists.newArrayList();
+ synchronized (mRulesLock) {
+ for (int networkType = 0; networkType <= ConnectivityManager.MAX_NETWORK_TYPE;
+ networkType++) {
+ if (getNetworkInfoForType(networkType) != null) {
+ final NetworkInfo info = getFilteredNetworkInfo(networkType, uid);
+ final LinkProperties lp = getLinkPropertiesForTypeInternal(networkType);
+ final NetworkCapabilities netcap = getNetworkCapabilitiesForType(networkType);
+ result.add(new NetworkState(info, lp, netcap));
+ }
+ }
+ }
+ return result.toArray(new NetworkState[result.size()]);
+ }
+
+ private NetworkState getNetworkStateUnchecked(int networkType) {
+ if (isNetworkTypeValid(networkType)) {
+ NetworkInfo info = getNetworkInfoForType(networkType);
+ if (info != null) {
+ return new NetworkState(info,
+ getLinkPropertiesForTypeInternal(networkType),
+ getNetworkCapabilitiesForType(networkType));
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public NetworkQuotaInfo getActiveNetworkQuotaInfo() {
+ enforceAccessPermission();
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ final NetworkState state = getNetworkStateUnchecked(mActiveDefaultNetwork);
+ if (state != null) {
+ try {
+ return mPolicyManager.getNetworkQuotaInfo(state);
+ } catch (RemoteException e) {
+ }
+ }
+ return null;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public boolean isActiveNetworkMetered() {
+ enforceAccessPermission();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return isNetworkMeteredUnchecked(mActiveDefaultNetwork);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ private boolean isNetworkMeteredUnchecked(int networkType) {
+ final NetworkState state = getNetworkStateUnchecked(networkType);
+ if (state != null) {
+ try {
+ return mPolicyManager.isNetworkMetered(state);
+ } catch (RemoteException e) {
+ }
+ }
+ return false;
+ }
+
+ private INetworkManagementEventObserver mDataActivityObserver = new BaseNetworkObserver() {
+ @Override
+ public void interfaceClassDataActivityChanged(String label, boolean active, long tsNanos) {
+ int deviceType = Integer.parseInt(label);
+ sendDataActivityBroadcast(deviceType, active, tsNanos);
+ }
+ };
+
+ /**
+ * Used to notice when the calling process dies so we can self-expire
+ *
+ * Also used to know if the process has cleaned up after itself when
+ * our auto-expire timer goes off. The timer has a link to an object.
+ *
+ */
+ private class FeatureUser implements IBinder.DeathRecipient {
+ int mNetworkType;
+ String mFeature;
+ IBinder mBinder;
+ int mPid;
+ int mUid;
+ long mCreateTime;
+
+ FeatureUser(int type, String feature, IBinder binder) {
+ super();
+ mNetworkType = type;
+ mFeature = feature;
+ mBinder = binder;
+ mPid = getCallingPid();
+ mUid = getCallingUid();
+ mCreateTime = System.currentTimeMillis();
+
+ try {
+ mBinder.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ binderDied();
+ }
+ }
+
+ void unlinkDeathRecipient() {
+ mBinder.unlinkToDeath(this, 0);
+ }
+
+ public void binderDied() {
+ log("ConnectivityService FeatureUser binderDied(" +
+ mNetworkType + ", " + mFeature + ", " + mBinder + "), created " +
+ (System.currentTimeMillis() - mCreateTime) + " mSec ago");
+ stopUsingNetworkFeature(this, false);
+ }
+
+ public void expire() {
+ if (VDBG) {
+ log("ConnectivityService FeatureUser expire(" +
+ mNetworkType + ", " + mFeature + ", " + mBinder +"), created " +
+ (System.currentTimeMillis() - mCreateTime) + " mSec ago");
+ }
+ stopUsingNetworkFeature(this, false);
+ }
+
+ public boolean isSameUser(FeatureUser u) {
+ if (u == null) return false;
+
+ return isSameUser(u.mPid, u.mUid, u.mNetworkType, u.mFeature);
+ }
+
+ public boolean isSameUser(int pid, int uid, int networkType, String feature) {
+ if ((mPid == pid) && (mUid == uid) && (mNetworkType == networkType) &&
+ TextUtils.equals(mFeature, feature)) {
+ return true;
+ }
+ return false;
+ }
+
+ public String toString() {
+ return "FeatureUser("+mNetworkType+","+mFeature+","+mPid+","+mUid+"), created " +
+ (System.currentTimeMillis() - mCreateTime) + " mSec ago";
+ }
+ }
+
+ // javadoc from interface
+ public int startUsingNetworkFeature(int networkType, String feature,
+ IBinder binder) {
+ long startTime = 0;
+ if (DBG) {
+ startTime = SystemClock.elapsedRealtime();
+ }
+ if (VDBG) {
+ log("startUsingNetworkFeature for net " + networkType + ": " + feature + ", uid="
+ + Binder.getCallingUid());
+ }
+ enforceChangePermission();
+ try {
+ if (!ConnectivityManager.isNetworkTypeValid(networkType) ||
+ mNetConfigs[networkType] == null) {
+ return PhoneConstants.APN_REQUEST_FAILED;
+ }
+
+ FeatureUser f = new FeatureUser(networkType, feature, binder);
+
+ // TODO - move this into individual networktrackers
+ int usedNetworkType = convertFeatureToNetworkType(networkType, feature);
+
+ if (mLockdownEnabled) {
+ // Since carrier APNs usually aren't available from VPN
+ // endpoint, mark them as unavailable.
+ return PhoneConstants.APN_TYPE_NOT_AVAILABLE;
+ }
+
+ if (mProtectedNetworks.contains(usedNetworkType)) {
+ enforceConnectivityInternalPermission();
+ }
+
+ // if UID is restricted, don't allow them to bring up metered APNs
+ final boolean networkMetered = isNetworkMeteredUnchecked(usedNetworkType);
+ final int uidRules;
+ synchronized (mRulesLock) {
+ uidRules = mUidRules.get(Binder.getCallingUid(), RULE_ALLOW_ALL);
+ }
+ if (networkMetered && (uidRules & RULE_REJECT_METERED) != 0) {
+ return PhoneConstants.APN_REQUEST_FAILED;
+ }
+
+ NetworkStateTracker network = mNetTrackers[usedNetworkType];
+ if (network != null) {
+ Integer currentPid = new Integer(getCallingPid());
+ if (usedNetworkType != networkType) {
+ NetworkInfo ni = network.getNetworkInfo();
+
+ if (ni.isAvailable() == false) {
+ if (!TextUtils.equals(feature,Phone.FEATURE_ENABLE_DUN_ALWAYS)) {
+ if (DBG) log("special network not available ni=" + ni.getTypeName());
+ return PhoneConstants.APN_TYPE_NOT_AVAILABLE;
+ } else {
+ // else make the attempt anyway - probably giving REQUEST_STARTED below
+ if (DBG) {
+ log("special network not available, but try anyway ni=" +
+ ni.getTypeName());
+ }
+ }
+ }
+
+ int restoreTimer = getRestoreDefaultNetworkDelay(usedNetworkType);
+
+ synchronized(this) {
+ boolean addToList = true;
+ if (restoreTimer < 0) {
+ // In case there is no timer is specified for the feature,
+ // make sure we don't add duplicate entry with the same request.
+ for (FeatureUser u : mFeatureUsers) {
+ if (u.isSameUser(f)) {
+ // Duplicate user is found. Do not add.
+ addToList = false;
+ break;
+ }
+ }
+ }
+
+ if (addToList) mFeatureUsers.add(f);
+ if (!mNetRequestersPids[usedNetworkType].contains(currentPid)) {
+ // this gets used for per-pid dns when connected
+ mNetRequestersPids[usedNetworkType].add(currentPid);
+ }
+ }
+
+ if (restoreTimer >= 0) {
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(
+ EVENT_RESTORE_DEFAULT_NETWORK, f), restoreTimer);
+ }
+
+ if ((ni.isConnectedOrConnecting() == true) &&
+ !network.isTeardownRequested()) {
+ if (ni.isConnected() == true) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ // add the pid-specific dns
+ handleDnsConfigurationChange(usedNetworkType);
+ if (VDBG) log("special network already active");
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return PhoneConstants.APN_ALREADY_ACTIVE;
+ }
+ if (VDBG) log("special network already connecting");
+ return PhoneConstants.APN_REQUEST_STARTED;
+ }
+
+ // check if the radio in play can make another contact
+ // assume if cannot for now
+
+ if (DBG) {
+ log("startUsingNetworkFeature reconnecting to " + networkType + ": " +
+ feature);
+ }
+ if (network.reconnect()) {
+ if (DBG) log("startUsingNetworkFeature X: return APN_REQUEST_STARTED");
+ return PhoneConstants.APN_REQUEST_STARTED;
+ } else {
+ if (DBG) log("startUsingNetworkFeature X: return APN_REQUEST_FAILED");
+ return PhoneConstants.APN_REQUEST_FAILED;
+ }
+ } else {
+ // need to remember this unsupported request so we respond appropriately on stop
+ synchronized(this) {
+ mFeatureUsers.add(f);
+ if (!mNetRequestersPids[usedNetworkType].contains(currentPid)) {
+ // this gets used for per-pid dns when connected
+ mNetRequestersPids[usedNetworkType].add(currentPid);
+ }
+ }
+ if (DBG) log("startUsingNetworkFeature X: return -1 unsupported feature.");
+ return -1;
+ }
+ }
+ if (DBG) log("startUsingNetworkFeature X: return APN_TYPE_NOT_AVAILABLE");
+ return PhoneConstants.APN_TYPE_NOT_AVAILABLE;
+ } finally {
+ if (DBG) {
+ final long execTime = SystemClock.elapsedRealtime() - startTime;
+ if (execTime > 250) {
+ loge("startUsingNetworkFeature took too long: " + execTime + "ms");
+ } else {
+ if (VDBG) log("startUsingNetworkFeature took " + execTime + "ms");
+ }
+ }
+ }
+ }
+
+ // javadoc from interface
+ public int stopUsingNetworkFeature(int networkType, String feature) {
+ enforceChangePermission();
+
+ int pid = getCallingPid();
+ int uid = getCallingUid();
+
+ FeatureUser u = null;
+ boolean found = false;
+
+ synchronized(this) {
+ for (FeatureUser x : mFeatureUsers) {
+ if (x.isSameUser(pid, uid, networkType, feature)) {
+ u = x;
+ found = true;
+ break;
+ }
+ }
+ }
+ if (found && u != null) {
+ if (VDBG) log("stopUsingNetworkFeature: X");
+ // stop regardless of how many other time this proc had called start
+ return stopUsingNetworkFeature(u, true);
+ } else {
+ // none found!
+ if (VDBG) log("stopUsingNetworkFeature: X not a live request, ignoring");
+ return 1;
+ }
+ }
+
+ private int stopUsingNetworkFeature(FeatureUser u, boolean ignoreDups) {
+ int networkType = u.mNetworkType;
+ String feature = u.mFeature;
+ int pid = u.mPid;
+ int uid = u.mUid;
+
+ NetworkStateTracker tracker = null;
+ boolean callTeardown = false; // used to carry our decision outside of sync block
+
+ if (VDBG) {
+ log("stopUsingNetworkFeature: net " + networkType + ": " + feature);
+ }
+
+ if (!ConnectivityManager.isNetworkTypeValid(networkType)) {
+ if (DBG) {
+ log("stopUsingNetworkFeature: net " + networkType + ": " + feature +
+ ", net is invalid");
+ }
+ return -1;
+ }
+
+ // need to link the mFeatureUsers list with the mNetRequestersPids state in this
+ // sync block
+ synchronized(this) {
+ // check if this process still has an outstanding start request
+ if (!mFeatureUsers.contains(u)) {
+ if (VDBG) {
+ log("stopUsingNetworkFeature: this process has no outstanding requests" +
+ ", ignoring");
+ }
+ return 1;
+ }
+ u.unlinkDeathRecipient();
+ mFeatureUsers.remove(mFeatureUsers.indexOf(u));
+ // If we care about duplicate requests, check for that here.
+ //
+ // This is done to support the extension of a request - the app
+ // can request we start the network feature again and renew the
+ // auto-shutoff delay. Normal "stop" calls from the app though
+ // do not pay attention to duplicate requests - in effect the
+ // API does not refcount and a single stop will counter multiple starts.
+ if (ignoreDups == false) {
+ for (FeatureUser x : mFeatureUsers) {
+ if (x.isSameUser(u)) {
+ if (VDBG) log("stopUsingNetworkFeature: dup is found, ignoring");
+ return 1;
+ }
+ }
+ }
+
+ // TODO - move to individual network trackers
+ int usedNetworkType = convertFeatureToNetworkType(networkType, feature);
+
+ tracker = mNetTrackers[usedNetworkType];
+ if (tracker == null) {
+ if (DBG) {
+ log("stopUsingNetworkFeature: net " + networkType + ": " + feature +
+ " no known tracker for used net type " + usedNetworkType);
+ }
+ return -1;
+ }
+ if (usedNetworkType != networkType) {
+ Integer currentPid = new Integer(pid);
+ mNetRequestersPids[usedNetworkType].remove(currentPid);
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ reassessPidDns(pid, true);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ flushVmDnsCache();
+ if (mNetRequestersPids[usedNetworkType].size() != 0) {
+ if (VDBG) {
+ log("stopUsingNetworkFeature: net " + networkType + ": " + feature +
+ " others still using it");
+ }
+ return 1;
+ }
+ callTeardown = true;
+ } else {
+ if (DBG) {
+ log("stopUsingNetworkFeature: net " + networkType + ": " + feature +
+ " not a known feature - dropping");
+ }
+ }
+ }
+
+ if (callTeardown) {
+ if (DBG) {
+ log("stopUsingNetworkFeature: teardown net " + networkType + ": " + feature);
+ }
+ tracker.teardown();
+ return 1;
+ } else {
+ return -1;
+ }
+ }
+
+ /**
+ * Check if the address falls into any of currently running VPN's route's.
+ */
+ private boolean isAddressUnderVpn(InetAddress address) {
+ synchronized (mVpns) {
+ synchronized (mRoutesLock) {
+ int uid = UserHandle.getCallingUserId();
+ Vpn vpn = mVpns.get(uid);
+ if (vpn == null) {
+ return false;
+ }
+
+ // Check if an exemption exists for this address.
+ for (LinkAddress destination : mExemptAddresses) {
+ if (!NetworkUtils.addressTypeMatches(address, destination.getAddress())) {
+ continue;
+ }
+
+ int prefix = destination.getPrefixLength();
+ InetAddress addrMasked = NetworkUtils.getNetworkPart(address, prefix);
+ InetAddress destMasked = NetworkUtils.getNetworkPart(destination.getAddress(),
+ prefix);
+
+ if (addrMasked.equals(destMasked)) {
+ return false;
+ }
+ }
+
+ // Finally check if the address is covered by the VPN.
+ return vpn.isAddressCovered(address);
+ }
+ }
+ }
+
+ /**
+ * @deprecated use requestRouteToHostAddress instead
+ *
+ * Ensure that a network route exists to deliver traffic to the specified
+ * host via the specified network interface.
+ * @param networkType the type of the network over which traffic to the
+ * specified host is to be routed
+ * @param hostAddress the IP address of the host to which the route is
+ * desired
+ * @return {@code true} on success, {@code false} on failure
+ */
+ public boolean requestRouteToHost(int networkType, int hostAddress, String packageName) {
+ InetAddress inetAddress = NetworkUtils.intToInetAddress(hostAddress);
+
+ if (inetAddress == null) {
+ return false;
+ }
+
+ return requestRouteToHostAddress(networkType, inetAddress.getAddress(), packageName);
+ }
+
+ /**
+ * Ensure that a network route exists to deliver traffic to the specified
+ * host via the specified network interface.
+ * @param networkType the type of the network over which traffic to the
+ * specified host is to be routed
+ * @param hostAddress the IP address of the host to which the route is
+ * desired
+ * @return {@code true} on success, {@code false} on failure
+ */
+ public boolean requestRouteToHostAddress(int networkType, byte[] hostAddress,
+ String packageName) {
+ enforceChangePermission();
+ if (mProtectedNetworks.contains(networkType)) {
+ enforceConnectivityInternalPermission();
+ }
+ boolean exempt;
+ InetAddress addr;
+ try {
+ addr = InetAddress.getByAddress(hostAddress);
+ } catch (UnknownHostException e) {
+ if (DBG) log("requestRouteToHostAddress got " + e.toString());
+ return false;
+ }
+ // System apps may request routes bypassing the VPN to keep other networks working.
+ if (Binder.getCallingUid() == Process.SYSTEM_UID) {
+ exempt = true;
+ } else {
+ mAppOpsManager.checkPackage(Binder.getCallingUid(), packageName);
+ try {
+ ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(packageName,
+ 0);
+ exempt = (info.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+ } catch (NameNotFoundException e) {
+ throw new IllegalArgumentException("Failed to find calling package details", e);
+ }
+ }
+
+ // Non-exempt routeToHost's can only be added if the host is not covered by the VPN.
+ // This can be either because the VPN's routes do not cover the destination or a
+ // system application added an exemption that covers this destination.
+ if (!exempt && isAddressUnderVpn(addr)) {
+ return false;
+ }
+
+ if (!ConnectivityManager.isNetworkTypeValid(networkType)) {
+ if (DBG) log("requestRouteToHostAddress on invalid network: " + networkType);
+ return false;
+ }
+
+ NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType);
+ if (nai == null) {
+ if (mLegacyTypeTracker.isTypeSupported(networkType) == false) {
+ if (DBG) log("requestRouteToHostAddress on unsupported network: " + networkType);
+ } else {
+ if (DBG) log("requestRouteToHostAddress on down network: " + networkType);
+ }
+ return false;
+ }
+
+ DetailedState netState = nai.networkInfo.getDetailedState();
+
+ if ((netState != DetailedState.CONNECTED &&
+ netState != DetailedState.CAPTIVE_PORTAL_CHECK)) {
+ if (VDBG) {
+ log("requestRouteToHostAddress on down network "
+ + "(" + networkType + ") - dropped"
+ + " netState=" + netState);
+ }
+ return false;
+ }
+ final int uid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ LinkProperties lp = nai.linkProperties;
+ boolean ok = modifyRouteToAddress(lp, addr, ADD, TO_DEFAULT_TABLE, exempt,
+ nai.network.netId, uid);
+ if (DBG) log("requestRouteToHostAddress ok=" + ok);
+ return ok;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ private boolean addRoute(LinkProperties p, RouteInfo r, boolean toDefaultTable,
+ boolean exempt, int netId) {
+ return modifyRoute(p, r, 0, ADD, toDefaultTable, exempt, netId, false, UID_UNUSED);
+ }
+
+ private boolean removeRoute(LinkProperties p, RouteInfo r, boolean toDefaultTable, int netId) {
+ return modifyRoute(p, r, 0, REMOVE, toDefaultTable, UNEXEMPT, netId, false, UID_UNUSED);
+ }
+
+ private boolean modifyRouteToAddress(LinkProperties lp, InetAddress addr, boolean doAdd,
+ boolean toDefaultTable, boolean exempt, int netId, int uid) {
+ RouteInfo bestRoute = RouteInfo.selectBestRoute(lp.getAllRoutes(), addr);
+ if (bestRoute == null) {
+ bestRoute = RouteInfo.makeHostRoute(addr, lp.getInterfaceName());
+ } else {
+ String iface = bestRoute.getInterface();
+ if (bestRoute.getGateway().equals(addr)) {
+ // if there is no better route, add the implied hostroute for our gateway
+ bestRoute = RouteInfo.makeHostRoute(addr, iface);
+ } else {
+ // if we will connect to this through another route, add a direct route
+ // to it's gateway
+ bestRoute = RouteInfo.makeHostRoute(addr, bestRoute.getGateway(), iface);
+ }
+ }
+ return modifyRoute(lp, bestRoute, 0, doAdd, toDefaultTable, exempt, netId, true, uid);
+ }
+
+ /*
+ * TODO: Clean all this stuff up. Once we have UID-based routing, stuff will break due to
+ * incorrect tracking of mAddedRoutes, so a cleanup becomes necessary and urgent. But at
+ * the same time, there'll be no more need to track mAddedRoutes or mExemptAddresses,
+ * or even have the concept of an exempt address, or do things like "selectBestRoute", or
+ * determine "default" vs "secondary" table, etc., so the cleanup becomes possible.
+ */
+ private boolean modifyRoute(LinkProperties lp, RouteInfo r, int cycleCount, boolean doAdd,
+ boolean toDefaultTable, boolean exempt, int netId, boolean legacy, int uid) {
+ if ((lp == null) || (r == null)) {
+ if (DBG) log("modifyRoute got unexpected null: " + lp + ", " + r);
+ return false;
+ }
+
+ if (cycleCount > MAX_HOSTROUTE_CYCLE_COUNT) {
+ loge("Error modifying route - too much recursion");
+ return false;
+ }
+
+ String ifaceName = r.getInterface();
+ if(ifaceName == null) {
+ loge("Error modifying route - no interface name");
+ return false;
+ }
+ if (r.hasGateway()) {
+ RouteInfo bestRoute = RouteInfo.selectBestRoute(lp.getAllRoutes(), r.getGateway());
+ if (bestRoute != null) {
+ if (bestRoute.getGateway().equals(r.getGateway())) {
+ // if there is no better route, add the implied hostroute for our gateway
+ bestRoute = RouteInfo.makeHostRoute(r.getGateway(), ifaceName);
+ } else {
+ // if we will connect to our gateway through another route, add a direct
+ // route to it's gateway
+ bestRoute = RouteInfo.makeHostRoute(r.getGateway(),
+ bestRoute.getGateway(),
+ ifaceName);
+ }
+ modifyRoute(lp, bestRoute, cycleCount+1, doAdd, toDefaultTable, exempt, netId,
+ legacy, uid);
+ }
+ }
+ if (doAdd) {
+ if (VDBG) log("Adding " + r + " for interface " + ifaceName);
+ try {
+ if (toDefaultTable) {
+ synchronized (mRoutesLock) {
+ // only track default table - only one apps can effect
+ mAddedRoutes.add(r);
+ if (legacy) {
+ mNetd.addLegacyRouteForNetId(netId, r, uid);
+ } else {
+ mNetd.addRoute(netId, r);
+ }
+ if (exempt) {
+ LinkAddress dest = r.getDestinationLinkAddress();
+ if (!mExemptAddresses.contains(dest)) {
+ mNetd.setHostExemption(dest);
+ mExemptAddresses.add(dest);
+ }
+ }
+ }
+ } else {
+ if (legacy) {
+ mNetd.addLegacyRouteForNetId(netId, r, uid);
+ } else {
+ mNetd.addRoute(netId, r);
+ }
+ }
+ } catch (Exception e) {
+ // never crash - catch them all
+ if (DBG) loge("Exception trying to add a route: " + e);
+ return false;
+ }
+ } else {
+ // if we remove this one and there are no more like it, then refcount==0 and
+ // we can remove it from the table
+ if (toDefaultTable) {
+ synchronized (mRoutesLock) {
+ mAddedRoutes.remove(r);
+ if (mAddedRoutes.contains(r) == false) {
+ if (VDBG) log("Removing " + r + " for interface " + ifaceName);
+ try {
+ if (legacy) {
+ mNetd.removeLegacyRouteForNetId(netId, r, uid);
+ } else {
+ mNetd.removeRoute(netId, r);
+ }
+ LinkAddress dest = r.getDestinationLinkAddress();
+ if (mExemptAddresses.contains(dest)) {
+ mNetd.clearHostExemption(dest);
+ mExemptAddresses.remove(dest);
+ }
+ } catch (Exception e) {
+ // never crash - catch them all
+ if (VDBG) loge("Exception trying to remove a route: " + e);
+ return false;
+ }
+ } else {
+ if (VDBG) log("not removing " + r + " as it's still in use");
+ }
+ }
+ } else {
+ if (VDBG) log("Removing " + r + " for interface " + ifaceName);
+ try {
+ if (legacy) {
+ mNetd.removeLegacyRouteForNetId(netId, r, uid);
+ } else {
+ mNetd.removeRoute(netId, r);
+ }
+ } catch (Exception e) {
+ // never crash - catch them all
+ if (VDBG) loge("Exception trying to remove a route: " + e);
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ public void setDataDependency(int networkType, boolean met) {
+ enforceConnectivityInternalPermission();
+
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_DEPENDENCY_MET,
+ (met ? ENABLED : DISABLED), networkType));
+ }
+
+ private void handleSetDependencyMet(int networkType, boolean met) {
+ if (mNetTrackers[networkType] != null) {
+ if (DBG) {
+ log("handleSetDependencyMet(" + networkType + ", " + met + ")");
+ }
+ mNetTrackers[networkType].setDependencyMet(met);
+ }
+ }
+
+ private INetworkPolicyListener mPolicyListener = new INetworkPolicyListener.Stub() {
+ @Override
+ public void onUidRulesChanged(int uid, int uidRules) {
+ // caller is NPMS, since we only register with them
+ if (LOGD_RULES) {
+ log("onUidRulesChanged(uid=" + uid + ", uidRules=" + uidRules + ")");
+ }
+
+ synchronized (mRulesLock) {
+ // skip update when we've already applied rules
+ final int oldRules = mUidRules.get(uid, RULE_ALLOW_ALL);
+ if (oldRules == uidRules) return;
+
+ mUidRules.put(uid, uidRules);
+ }
+
+ // TODO: notify UID when it has requested targeted updates
+ }
+
+ @Override
+ public void onMeteredIfacesChanged(String[] meteredIfaces) {
+ // caller is NPMS, since we only register with them
+ if (LOGD_RULES) {
+ log("onMeteredIfacesChanged(ifaces=" + Arrays.toString(meteredIfaces) + ")");
+ }
+
+ synchronized (mRulesLock) {
+ mMeteredIfaces.clear();
+ for (String iface : meteredIfaces) {
+ mMeteredIfaces.add(iface);
+ }
+ }
+ }
+
+ @Override
+ public void onRestrictBackgroundChanged(boolean restrictBackground) {
+ // caller is NPMS, since we only register with them
+ if (LOGD_RULES) {
+ log("onRestrictBackgroundChanged(restrictBackground=" + restrictBackground + ")");
+ }
+
+ // kick off connectivity change broadcast for active network, since
+ // global background policy change is radical.
+ final int networkType = mActiveDefaultNetwork;
+ if (isNetworkTypeValid(networkType)) {
+ final NetworkStateTracker tracker = mNetTrackers[networkType];
+ if (tracker != null) {
+ final NetworkInfo info = tracker.getNetworkInfo();
+ if (info != null && info.isConnected()) {
+ sendConnectedBroadcast(info);
+ }
+ }
+ }
+ }
+ };
+
+ @Override
+ public void setPolicyDataEnable(int networkType, boolean enabled) {
+ // only someone like NPMS should only be calling us
+ mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+
+ mHandler.sendMessage(mHandler.obtainMessage(
+ EVENT_SET_POLICY_DATA_ENABLE, networkType, (enabled ? ENABLED : DISABLED)));
+ }
+
+ private void handleSetPolicyDataEnable(int networkType, boolean enabled) {
+ // TODO - handle this passing to factories
+// if (isNetworkTypeValid(networkType)) {
+// final NetworkStateTracker tracker = mNetTrackers[networkType];
+// if (tracker != null) {
+// tracker.setPolicyDataEnable(enabled);
+// }
+// }
+ }
+
+ private void enforceAccessPermission() {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.ACCESS_NETWORK_STATE,
+ "ConnectivityService");
+ }
+
+ private void enforceChangePermission() {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CHANGE_NETWORK_STATE,
+ "ConnectivityService");
+ }
+
+ // TODO Make this a special check when it goes public
+ private void enforceTetherChangePermission() {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CHANGE_NETWORK_STATE,
+ "ConnectivityService");
+ }
+
+ private void enforceTetherAccessPermission() {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.ACCESS_NETWORK_STATE,
+ "ConnectivityService");
+ }
+
+ private void enforceConnectivityInternalPermission() {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CONNECTIVITY_INTERNAL,
+ "ConnectivityService");
+ }
+
+ private void enforceMarkNetworkSocketPermission() {
+ //Media server special case
+ if (Binder.getCallingUid() == Process.MEDIA_UID) {
+ return;
+ }
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.MARK_NETWORK_SOCKET,
+ "ConnectivityService");
+ }
+
+ /**
+ * Handle a {@code DISCONNECTED} event. If this pertains to the non-active
+ * network, we ignore it. If it is for the active network, we send out a
+ * broadcast. But first, we check whether it might be possible to connect
+ * to a different network.
+ * @param info the {@code NetworkInfo} for the network
+ */
+ private void handleDisconnect(NetworkInfo info) {
+
+ int prevNetType = info.getType();
+
+ mNetTrackers[prevNetType].setTeardownRequested(false);
+ int thisNetId = mNetTrackers[prevNetType].getNetwork().netId;
+
+ // Remove idletimer previously setup in {@code handleConnect}
+// Already in place in new function. This is dead code.
+// if (mNetConfigs[prevNetType].isDefault()) {
+// removeDataActivityTracking(prevNetType);
+// }
+
+ /*
+ * If the disconnected network is not the active one, then don't report
+ * this as a loss of connectivity. What probably happened is that we're
+ * getting the disconnect for a network that we explicitly disabled
+ * in accordance with network preference policies.
+ */
+ if (!mNetConfigs[prevNetType].isDefault()) {
+ List<Integer> pids = mNetRequestersPids[prevNetType];
+ for (Integer pid : pids) {
+ // will remove them because the net's no longer connected
+ // need to do this now as only now do we know the pids and
+ // can properly null things that are no longer referenced.
+ reassessPidDns(pid.intValue(), false);
+ }
+ }
+
+ Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION);
+ intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, new NetworkInfo(info));
+ intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType());
+ if (info.isFailover()) {
+ intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true);
+ info.setFailover(false);
+ }
+ if (info.getReason() != null) {
+ intent.putExtra(ConnectivityManager.EXTRA_REASON, info.getReason());
+ }
+ if (info.getExtraInfo() != null) {
+ intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO,
+ info.getExtraInfo());
+ }
+
+ if (mNetConfigs[prevNetType].isDefault()) {
+ tryFailover(prevNetType);
+ if (mActiveDefaultNetwork != -1) {
+ NetworkInfo switchTo = mNetTrackers[mActiveDefaultNetwork].getNetworkInfo();
+ intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO, switchTo);
+ } else {
+ mDefaultInetConditionPublished = 0; // we're not connected anymore
+ intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true);
+ }
+ }
+ intent.putExtra(ConnectivityManager.EXTRA_INET_CONDITION, mDefaultInetConditionPublished);
+
+ // Reset interface if no other connections are using the same interface
+ boolean doReset = true;
+ LinkProperties linkProperties = mNetTrackers[prevNetType].getLinkProperties();
+ if (linkProperties != null) {
+ String oldIface = linkProperties.getInterfaceName();
+ if (TextUtils.isEmpty(oldIface) == false) {
+ for (NetworkStateTracker networkStateTracker : mNetTrackers) {
+ if (networkStateTracker == null) continue;
+ NetworkInfo networkInfo = networkStateTracker.getNetworkInfo();
+ if (networkInfo.isConnected() && networkInfo.getType() != prevNetType) {
+ LinkProperties l = networkStateTracker.getLinkProperties();
+ if (l == null) continue;
+ if (oldIface.equals(l.getInterfaceName())) {
+ doReset = false;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // do this before we broadcast the change
+// Already done in new function. This is dead code.
+// handleConnectivityChange(prevNetType, doReset);
+
+ final Intent immediateIntent = new Intent(intent);
+ immediateIntent.setAction(CONNECTIVITY_ACTION_IMMEDIATE);
+ sendStickyBroadcast(immediateIntent);
+ sendStickyBroadcastDelayed(intent, getConnectivityChangeDelay());
+ /*
+ * If the failover network is already connected, then immediately send
+ * out a followup broadcast indicating successful failover
+ */
+ if (mActiveDefaultNetwork != -1) {
+ sendConnectedBroadcastDelayed(mNetTrackers[mActiveDefaultNetwork].getNetworkInfo(),
+ getConnectivityChangeDelay());
+ }
+ try {
+// mNetd.removeNetwork(thisNetId);
+ } catch (Exception e) {
+ loge("Exception removing network: " + e);
+ } finally {
+ mNetTrackers[prevNetType].setNetId(INVALID_NET_ID);
+ }
+ }
+
+ private void tryFailover(int prevNetType) {
+ /*
+ * If this is a default network, check if other defaults are available.
+ * Try to reconnect on all available and let them hash it out when
+ * more than one connects.
+ */
+ if (mNetConfigs[prevNetType].isDefault()) {
+ if (mActiveDefaultNetwork == prevNetType) {
+ if (DBG) {
+ log("tryFailover: set mActiveDefaultNetwork=-1, prevNetType=" + prevNetType);
+ }
+ mActiveDefaultNetwork = -1;
+ try {
+ mNetd.clearDefaultNetId();
+ } catch (Exception e) {
+ loge("Exception clearing default network :" + e);
+ }
+ }
+
+ // don't signal a reconnect for anything lower or equal priority than our
+ // current connected default
+ // TODO - don't filter by priority now - nice optimization but risky
+// int currentPriority = -1;
+// if (mActiveDefaultNetwork != -1) {
+// currentPriority = mNetConfigs[mActiveDefaultNetwork].mPriority;
+// }
+
+ for (int checkType=0; checkType <= ConnectivityManager.MAX_NETWORK_TYPE; checkType++) {
+ if (checkType == prevNetType) continue;
+ if (mNetConfigs[checkType] == null) continue;
+ if (!mNetConfigs[checkType].isDefault()) continue;
+ if (mNetTrackers[checkType] == null) continue;
+
+// Enabling the isAvailable() optimization caused mobile to not get
+// selected if it was in the middle of error handling. Specifically
+// a moble connection that took 30 seconds to complete the DEACTIVATE_DATA_CALL
+// would not be available and we wouldn't get connected to anything.
+// So removing the isAvailable() optimization below for now. TODO: This
+// optimization should work and we need to investigate why it doesn't work.
+// This could be related to how DEACTIVATE_DATA_CALL is reporting its
+// complete before it is really complete.
+
+// if (!mNetTrackers[checkType].isAvailable()) continue;
+
+// if (currentPriority >= mNetConfigs[checkType].mPriority) continue;
+
+ NetworkStateTracker checkTracker = mNetTrackers[checkType];
+ NetworkInfo checkInfo = checkTracker.getNetworkInfo();
+ if (!checkInfo.isConnectedOrConnecting() || checkTracker.isTeardownRequested()) {
+ checkInfo.setFailover(true);
+ checkTracker.reconnect();
+ }
+ if (DBG) log("Attempting to switch to " + checkInfo.getTypeName());
+ }
+ }
+ }
+
+ public void sendConnectedBroadcast(NetworkInfo info) {
+ enforceConnectivityInternalPermission();
+ sendGeneralBroadcast(info, CONNECTIVITY_ACTION_IMMEDIATE);
+ sendGeneralBroadcast(info, CONNECTIVITY_ACTION);
+ }
+
+ private void sendConnectedBroadcastDelayed(NetworkInfo info, int delayMs) {
+ sendGeneralBroadcast(info, CONNECTIVITY_ACTION_IMMEDIATE);
+ sendGeneralBroadcastDelayed(info, CONNECTIVITY_ACTION, delayMs);
+ }
+
+ private void sendInetConditionBroadcast(NetworkInfo info) {
+ sendGeneralBroadcast(info, ConnectivityManager.INET_CONDITION_ACTION);
+ }
+
+ private Intent makeGeneralIntent(NetworkInfo info, String bcastType) {
+ if (mLockdownTracker != null) {
+ info = mLockdownTracker.augmentNetworkInfo(info);
+ }
+
+ Intent intent = new Intent(bcastType);
+ intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, new NetworkInfo(info));
+ intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType());
+ if (info.isFailover()) {
+ intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true);
+ info.setFailover(false);
+ }
+ if (info.getReason() != null) {
+ intent.putExtra(ConnectivityManager.EXTRA_REASON, info.getReason());
+ }
+ if (info.getExtraInfo() != null) {
+ intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO,
+ info.getExtraInfo());
+ }
+ intent.putExtra(ConnectivityManager.EXTRA_INET_CONDITION, mDefaultInetConditionPublished);
+ return intent;
+ }
+
+ private void sendGeneralBroadcast(NetworkInfo info, String bcastType) {
+ sendStickyBroadcast(makeGeneralIntent(info, bcastType));
+ }
+
+ private void sendGeneralBroadcastDelayed(NetworkInfo info, String bcastType, int delayMs) {
+ sendStickyBroadcastDelayed(makeGeneralIntent(info, bcastType), delayMs);
+ }
+
+ private void sendDataActivityBroadcast(int deviceType, boolean active, long tsNanos) {
+ Intent intent = new Intent(ConnectivityManager.ACTION_DATA_ACTIVITY_CHANGE);
+ intent.putExtra(ConnectivityManager.EXTRA_DEVICE_TYPE, deviceType);
+ intent.putExtra(ConnectivityManager.EXTRA_IS_ACTIVE, active);
+ intent.putExtra(ConnectivityManager.EXTRA_REALTIME_NS, tsNanos);
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mContext.sendOrderedBroadcastAsUser(intent, UserHandle.ALL,
+ RECEIVE_DATA_ACTIVITY_CHANGE, null, null, 0, null, null);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ private void sendStickyBroadcast(Intent intent) {
+ synchronized(this) {
+ if (!mSystemReady) {
+ mInitialBroadcast = new Intent(intent);
+ }
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ if (VDBG) {
+ log("sendStickyBroadcast: action=" + intent.getAction());
+ }
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
+
+ private void sendStickyBroadcastDelayed(Intent intent, int delayMs) {
+ if (delayMs <= 0) {
+ sendStickyBroadcast(intent);
+ } else {
+ if (VDBG) {
+ log("sendStickyBroadcastDelayed: delayMs=" + delayMs + ", action="
+ + intent.getAction());
+ }
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(
+ EVENT_SEND_STICKY_BROADCAST_INTENT, intent), delayMs);
+ }
+ }
+
+ void systemReady() {
+ mCaptivePortalTracker = CaptivePortalTracker.makeCaptivePortalTracker(mContext, this);
+ loadGlobalProxy();
+
+ synchronized(this) {
+ mSystemReady = true;
+ if (mInitialBroadcast != null) {
+ mContext.sendStickyBroadcastAsUser(mInitialBroadcast, UserHandle.ALL);
+ mInitialBroadcast = null;
+ }
+ }
+ // load the global proxy at startup
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_APPLY_GLOBAL_HTTP_PROXY));
+
+ // Try bringing up tracker, but if KeyStore isn't ready yet, wait
+ // for user to unlock device.
+ if (!updateLockdownVpn()) {
+ final IntentFilter filter = new IntentFilter(Intent.ACTION_USER_PRESENT);
+ mContext.registerReceiver(mUserPresentReceiver, filter);
+ }
+ }
+
+ private BroadcastReceiver mUserPresentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // Try creating lockdown tracker, since user present usually means
+ // unlocked keystore.
+ if (updateLockdownVpn()) {
+ mContext.unregisterReceiver(this);
+ }
+ }
+ };
+
+ private boolean isNewNetTypePreferredOverCurrentNetType(int type) {
+ if (((type != mNetworkPreference)
+ && (mNetConfigs[mActiveDefaultNetwork].priority > mNetConfigs[type].priority))
+ || (mNetworkPreference == mActiveDefaultNetwork)) {
+ return false;
+ }
+ return true;
+ }
+
+ private void handleConnect(NetworkInfo info) {
+ final int newNetType = info.getType();
+
+ // snapshot isFailover, because sendConnectedBroadcast() resets it
+ boolean isFailover = info.isFailover();
+ final NetworkStateTracker thisNet = mNetTrackers[newNetType];
+ final String thisIface = thisNet.getLinkProperties().getInterfaceName();
+
+ if (VDBG) {
+ log("handleConnect: E newNetType=" + newNetType + " thisIface=" + thisIface
+ + " isFailover" + isFailover);
+ }
+
+ // if this is a default net and other default is running
+ // kill the one not preferred
+ if (mNetConfigs[newNetType].isDefault()) {
+ if (mActiveDefaultNetwork != -1 && mActiveDefaultNetwork != newNetType) {
+ if (isNewNetTypePreferredOverCurrentNetType(newNetType)) {
+ String teardownPolicy = SystemProperties.get("net.teardownPolicy");
+ if (TextUtils.equals(teardownPolicy, "keep") == false) {
+ // tear down the other
+ NetworkStateTracker otherNet =
+ mNetTrackers[mActiveDefaultNetwork];
+ if (DBG) {
+ log("Policy requires " + otherNet.getNetworkInfo().getTypeName() +
+ " teardown");
+ }
+ if (!teardown(otherNet)) {
+ loge("Network declined teardown request");
+ teardown(thisNet);
+ return;
+ }
+ } else {
+ //TODO - remove
+ loge("network teardown skipped due to net.teardownPolicy setting");
+ }
+ } else {
+ // don't accept this one
+ if (VDBG) {
+ log("Not broadcasting CONNECT_ACTION " +
+ "to torn down network " + info.getTypeName());
+ }
+ teardown(thisNet);
+ return;
+ }
+ }
+ int thisNetId = nextNetId();
+ thisNet.setNetId(thisNetId);
+ try {
+// mNetd.createNetwork(thisNetId, thisIface);
+ } catch (Exception e) {
+ loge("Exception creating network :" + e);
+ teardown(thisNet);
+ return;
+ }
+// Already in place in new function. This is dead code.
+// setupDataActivityTracking(newNetType);
+ synchronized (ConnectivityService.this) {
+ // have a new default network, release the transition wakelock in a second
+ // if it's held. The second pause is to allow apps to reconnect over the
+ // new network
+ if (mNetTransitionWakeLock.isHeld()) {
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(
+ EVENT_CLEAR_NET_TRANSITION_WAKELOCK,
+ mNetTransitionWakeLockSerialNumber, 0),
+ 1000);
+ }
+ }
+ mActiveDefaultNetwork = newNetType;
+ try {
+ mNetd.setDefaultNetId(thisNetId);
+ } catch (Exception e) {
+ loge("Exception setting default network :" + e);
+ }
+ // this will cause us to come up initially as unconnected and switching
+ // to connected after our normal pause unless somebody reports us as reall
+ // disconnected
+ mDefaultInetConditionPublished = 0;
+ mDefaultConnectionSequence++;
+ mInetConditionChangeInFlight = false;
+ // Don't do this - if we never sign in stay, grey
+ //reportNetworkCondition(mActiveDefaultNetwork, 100);
+ updateNetworkSettings(thisNet);
+ } else {
+ int thisNetId = nextNetId();
+ thisNet.setNetId(thisNetId);
+ try {
+// mNetd.createNetwork(thisNetId, thisIface);
+ } catch (Exception e) {
+ loge("Exception creating network :" + e);
+ teardown(thisNet);
+ return;
+ }
+ }
+ thisNet.setTeardownRequested(false);
+// Already in place in new function. This is dead code.
+// updateMtuSizeSettings(thisNet);
+// handleConnectivityChange(newNetType, false);
+ sendConnectedBroadcastDelayed(info, getConnectivityChangeDelay());
+
+ // notify battery stats service about this network
+ if (thisIface != null) {
+ try {
+ BatteryStatsService.getService().noteNetworkInterfaceType(thisIface, newNetType);
+ } catch (RemoteException e) {
+ // ignored; service lives in system_server
+ }
+ }
+ }
+
+ /** @hide */
+ @Override
+ public void captivePortalCheckCompleted(NetworkInfo info, boolean isCaptivePortal) {
+ enforceConnectivityInternalPermission();
+ if (DBG) log("captivePortalCheckCompleted: ni=" + info + " captive=" + isCaptivePortal);
+// mNetTrackers[info.getType()].captivePortalCheckCompleted(isCaptivePortal);
+ }
+
+ /**
+ * Setup data activity tracking for the given network.
+ *
+ * Every {@code setupDataActivityTracking} should be paired with a
+ * {@link #removeDataActivityTracking} for cleanup.
+ */
+ private void setupDataActivityTracking(NetworkAgentInfo networkAgent) {
+ final String iface = networkAgent.linkProperties.getInterfaceName();
+
+ final int timeout;
+ int type = ConnectivityManager.TYPE_NONE;
+
+ if (networkAgent.networkCapabilities.hasTransport(
+ NetworkCapabilities.TRANSPORT_CELLULAR)) {
+ timeout = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.DATA_ACTIVITY_TIMEOUT_MOBILE,
+ 5);
+ type = ConnectivityManager.TYPE_MOBILE;
+ } else if (networkAgent.networkCapabilities.hasTransport(
+ NetworkCapabilities.TRANSPORT_WIFI)) {
+ timeout = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.DATA_ACTIVITY_TIMEOUT_WIFI,
+ 0);
+ type = ConnectivityManager.TYPE_WIFI;
+ } else {
+ // do not track any other networks
+ timeout = 0;
+ }
+
+ if (timeout > 0 && iface != null && type != ConnectivityManager.TYPE_NONE) {
+ try {
+ mNetd.addIdleTimer(iface, timeout, type);
+ } catch (Exception e) {
+ // You shall not crash!
+ loge("Exception in setupDataActivityTracking " + e);
+ }
+ }
+ }
+
+ /**
+ * Remove data activity tracking when network disconnects.
+ */
+ private void removeDataActivityTracking(NetworkAgentInfo networkAgent) {
+ final String iface = networkAgent.linkProperties.getInterfaceName();
+ final NetworkCapabilities caps = networkAgent.networkCapabilities;
+
+ if (iface != null && (caps.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) ||
+ caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))) {
+ try {
+ // the call fails silently if no idletimer setup for this interface
+ mNetd.removeIdleTimer(iface);
+ } catch (Exception e) {
+ loge("Exception in removeDataActivityTracking " + e);
+ }
+ }
+ }
+
+ /**
+ * After a change in the connectivity state of a network. We're mainly
+ * concerned with making sure that the list of DNS servers is set up
+ * according to which networks are connected, and ensuring that the
+ * right routing table entries exist.
+ *
+ * TODO - delete when we're sure all this functionallity is captured.
+ */
+ private void handleConnectivityChange(int netType, LinkProperties curLp, boolean doReset) {
+ int resetMask = doReset ? NetworkUtils.RESET_ALL_ADDRESSES : 0;
+ boolean exempt = ConnectivityManager.isNetworkTypeExempt(netType);
+ if (VDBG) {
+ log("handleConnectivityChange: netType=" + netType + " doReset=" + doReset
+ + " resetMask=" + resetMask);
+ }
+
+ /*
+ * If a non-default network is enabled, add the host routes that
+ * will allow it's DNS servers to be accessed.
+ */
+ handleDnsConfigurationChange(netType);
+
+ LinkProperties newLp = null;
+
+ if (mNetTrackers[netType].getNetworkInfo().isConnected()) {
+ newLp = mNetTrackers[netType].getLinkProperties();
+ if (VDBG) {
+ log("handleConnectivityChange: changed linkProperty[" + netType + "]:" +
+ " doReset=" + doReset + " resetMask=" + resetMask +
+ "\n curLp=" + curLp +
+ "\n newLp=" + newLp);
+ }
+
+ if (curLp != null) {
+ if (curLp.isIdenticalInterfaceName(newLp)) {
+ CompareResult<LinkAddress> car = curLp.compareAddresses(newLp);
+ if ((car.removed.size() != 0) || (car.added.size() != 0)) {
+ for (LinkAddress linkAddr : car.removed) {
+ if (linkAddr.getAddress() instanceof Inet4Address) {
+ resetMask |= NetworkUtils.RESET_IPV4_ADDRESSES;
+ }
+ if (linkAddr.getAddress() instanceof Inet6Address) {
+ resetMask |= NetworkUtils.RESET_IPV6_ADDRESSES;
+ }
+ }
+ if (DBG) {
+ log("handleConnectivityChange: addresses changed" +
+ " linkProperty[" + netType + "]:" + " resetMask=" + resetMask +
+ "\n car=" + car);
+ }
+ } else {
+ if (VDBG) {
+ log("handleConnectivityChange: addresses are the same reset per" +
+ " doReset linkProperty[" + netType + "]:" +
+ " resetMask=" + resetMask);
+ }
+ }
+ } else {
+ resetMask = NetworkUtils.RESET_ALL_ADDRESSES;
+ if (DBG) {
+ log("handleConnectivityChange: interface not not equivalent reset both" +
+ " linkProperty[" + netType + "]:" +
+ " resetMask=" + resetMask);
+ }
+ }
+ }
+ if (mNetConfigs[netType].isDefault()) {
+ handleApplyDefaultProxy(newLp.getHttpProxy());
+ }
+ } else {
+ if (VDBG) {
+ log("handleConnectivityChange: changed linkProperty[" + netType + "]:" +
+ " doReset=" + doReset + " resetMask=" + resetMask +
+ "\n curLp=" + curLp +
+ "\n newLp= null");
+ }
+ }
+ mCurrentLinkProperties[netType] = newLp;
+ boolean resetDns = updateRoutes(newLp, curLp, mNetConfigs[netType].isDefault(), exempt,
+ mNetTrackers[netType].getNetwork().netId);
+
+ if (resetMask != 0 || resetDns) {
+ if (VDBG) log("handleConnectivityChange: resetting");
+ if (curLp != null) {
+ if (VDBG) log("handleConnectivityChange: resetting curLp=" + curLp);
+ for (String iface : curLp.getAllInterfaceNames()) {
+ if (TextUtils.isEmpty(iface) == false) {
+ if (resetMask != 0) {
+ if (DBG) log("resetConnections(" + iface + ", " + resetMask + ")");
+ NetworkUtils.resetConnections(iface, resetMask);
+
+ // Tell VPN the interface is down. It is a temporary
+ // but effective fix to make VPN aware of the change.
+ if ((resetMask & NetworkUtils.RESET_IPV4_ADDRESSES) != 0) {
+ synchronized(mVpns) {
+ for (int i = 0; i < mVpns.size(); i++) {
+ mVpns.valueAt(i).interfaceStatusChanged(iface, false);
+ }
+ }
+ }
+ }
+ } else {
+ loge("Can't reset connection for type "+netType);
+ }
+ }
+ if (resetDns) {
+ flushVmDnsCache();
+ if (VDBG) log("resetting DNS cache for type " + netType);
+ try {
+ mNetd.flushNetworkDnsCache(mNetTrackers[netType].getNetwork().netId);
+ } catch (Exception e) {
+ // never crash - catch them all
+ if (DBG) loge("Exception resetting dns cache: " + e);
+ }
+ }
+ }
+ }
+
+ // TODO: Temporary notifying upstread change to Tethering.
+ // @see bug/4455071
+ /** Notify TetheringService if interface name has been changed. */
+ if (TextUtils.equals(mNetTrackers[netType].getNetworkInfo().getReason(),
+ PhoneConstants.REASON_LINK_PROPERTIES_CHANGED)) {
+ if (isTetheringSupported()) {
+ mTethering.handleTetherIfaceChange();
+ }
+ }
+ }
+
+ /**
+ * Add and remove routes using the old properties (null if not previously connected),
+ * new properties (null if becoming disconnected). May even be double null, which
+ * is a noop.
+ * Uses isLinkDefault to determine if default routes should be set or conversely if
+ * host routes should be set to the dns servers
+ * returns a boolean indicating the routes changed
+ */
+ private boolean updateRoutes(LinkProperties newLp, LinkProperties curLp,
+ boolean isLinkDefault, boolean exempt, int netId) {
+ Collection<RouteInfo> routesToAdd = null;
+ CompareResult<InetAddress> dnsDiff = new CompareResult<InetAddress>();
+ CompareResult<RouteInfo> routeDiff = new CompareResult<RouteInfo>();
+ if (curLp != null) {
+ // check for the delta between the current set and the new
+ routeDiff = curLp.compareAllRoutes(newLp);
+ dnsDiff = curLp.compareDnses(newLp);
+ } else if (newLp != null) {
+ routeDiff.added = newLp.getAllRoutes();
+ dnsDiff.added = newLp.getDnsServers();
+ }
+
+ boolean routesChanged = (routeDiff.removed.size() != 0 || routeDiff.added.size() != 0);
+
+ for (RouteInfo r : routeDiff.removed) {
+ if (isLinkDefault || ! r.isDefaultRoute()) {
+ if (VDBG) log("updateRoutes: default remove route r=" + r);
+ removeRoute(curLp, r, TO_DEFAULT_TABLE, netId);
+ }
+ if (isLinkDefault == false) {
+ // remove from a secondary route table
+ removeRoute(curLp, r, TO_SECONDARY_TABLE, netId);
+ }
+ }
+
+ for (RouteInfo r : routeDiff.added) {
+ if (isLinkDefault || ! r.isDefaultRoute()) {
+ addRoute(newLp, r, TO_DEFAULT_TABLE, exempt, netId);
+ } else {
+ // add to a secondary route table
+ addRoute(newLp, r, TO_SECONDARY_TABLE, UNEXEMPT, netId);
+
+ // many radios add a default route even when we don't want one.
+ // remove the default route unless somebody else has asked for it
+ String ifaceName = newLp.getInterfaceName();
+ synchronized (mRoutesLock) {
+ if (!TextUtils.isEmpty(ifaceName) && !mAddedRoutes.contains(r)) {
+ if (VDBG) log("Removing " + r + " for interface " + ifaceName);
+ try {
+ mNetd.removeRoute(netId, r);
+ } catch (Exception e) {
+ // never crash - catch them all
+ if (DBG) loge("Exception trying to remove a route: " + e);
+ }
+ }
+ }
+ }
+ }
+
+ return routesChanged;
+ }
+
+ /**
+ * Reads the network specific MTU size from reources.
+ * and set it on it's iface.
+ */
+ private void updateMtu(LinkProperties newLp, LinkProperties oldLp) {
+ final String iface = newLp.getInterfaceName();
+ final int mtu = newLp.getMtu();
+ if (oldLp != null && newLp.isIdenticalMtu(oldLp)) {
+ if (VDBG) log("identical MTU - not setting");
+ return;
+ }
+
+ if (mtu < 68 || mtu > 10000) {
+ loge("Unexpected mtu value: " + mtu + ", " + iface);
+ return;
+ }
+
+ try {
+ if (VDBG) log("Setting MTU size: " + iface + ", " + mtu);
+ mNetd.setMtu(iface, mtu);
+ } catch (Exception e) {
+ Slog.e(TAG, "exception in setMtu()" + e);
+ }
+ }
+
+ /**
+ * Reads the network specific TCP buffer sizes from SystemProperties
+ * net.tcp.buffersize.[default|wifi|umts|edge|gprs] and set them for system
+ * wide use
+ */
+ private void updateNetworkSettings(NetworkStateTracker nt) {
+ String key = nt.getTcpBufferSizesPropName();
+ String bufferSizes = key == null ? null : SystemProperties.get(key);
+
+ if (TextUtils.isEmpty(bufferSizes)) {
+ if (VDBG) log(key + " not found in system properties. Using defaults");
+
+ // Setting to default values so we won't be stuck to previous values
+ key = "net.tcp.buffersize.default";
+ bufferSizes = SystemProperties.get(key);
+ }
+
+ // Set values in kernel
+ if (bufferSizes.length() != 0) {
+ if (VDBG) {
+ log("Setting TCP values: [" + bufferSizes
+ + "] which comes from [" + key + "]");
+ }
+ setBufferSize(bufferSizes);
+ }
+
+ final String defaultRwndKey = "net.tcp.default_init_rwnd";
+ int defaultRwndValue = SystemProperties.getInt(defaultRwndKey, 0);
+ Integer rwndValue = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.TCP_DEFAULT_INIT_RWND, defaultRwndValue);
+ final String sysctlKey = "sys.sysctl.tcp_def_init_rwnd";
+ if (rwndValue != 0) {
+ SystemProperties.set(sysctlKey, rwndValue.toString());
+ }
+ }
+
+ /**
+ * Writes TCP buffer sizes to /sys/kernel/ipv4/tcp_[r/w]mem_[min/def/max]
+ * which maps to /proc/sys/net/ipv4/tcp_rmem and tcpwmem
+ *
+ * @param bufferSizes in the format of "readMin, readInitial, readMax,
+ * writeMin, writeInitial, writeMax"
+ */
+ private void setBufferSize(String bufferSizes) {
+ try {
+ String[] values = bufferSizes.split(",");
+
+ if (values.length == 6) {
+ final String prefix = "/sys/kernel/ipv4/tcp_";
+ FileUtils.stringToFile(prefix + "rmem_min", values[0]);
+ FileUtils.stringToFile(prefix + "rmem_def", values[1]);
+ FileUtils.stringToFile(prefix + "rmem_max", values[2]);
+ FileUtils.stringToFile(prefix + "wmem_min", values[3]);
+ FileUtils.stringToFile(prefix + "wmem_def", values[4]);
+ FileUtils.stringToFile(prefix + "wmem_max", values[5]);
+ } else {
+ loge("Invalid buffersize string: " + bufferSizes);
+ }
+ } catch (IOException e) {
+ loge("Can't set tcp buffer sizes:" + e);
+ }
+ }
+
+ /**
+ * Adjust the per-process dns entries (net.dns<x>.<pid>) based
+ * on the highest priority active net which this process requested.
+ * If there aren't any, clear it out
+ */
+ private void reassessPidDns(int pid, boolean doBump)
+ {
+ if (VDBG) log("reassessPidDns for pid " + pid);
+ Integer myPid = new Integer(pid);
+ for(int i : mPriorityList) {
+ if (mNetConfigs[i].isDefault()) {
+ continue;
+ }
+ NetworkStateTracker nt = mNetTrackers[i];
+ if (nt.getNetworkInfo().isConnected() &&
+ !nt.isTeardownRequested()) {
+ LinkProperties p = nt.getLinkProperties();
+ if (p == null) continue;
+ if (mNetRequestersPids[i].contains(myPid)) {
+ try {
+ // TODO: Reimplement this via local variable in bionic.
+ // mNetd.setDnsNetworkForPid(nt.getNetwork().netId, pid);
+ } catch (Exception e) {
+ Slog.e(TAG, "exception reasseses pid dns: " + e);
+ }
+ return;
+ }
+ }
+ }
+ // nothing found - delete
+ try {
+ // TODO: Reimplement this via local variable in bionic.
+ // mNetd.clearDnsNetworkForPid(pid);
+ } catch (Exception e) {
+ Slog.e(TAG, "exception clear interface from pid: " + e);
+ }
+ }
+
+ private void flushVmDnsCache() {
+ /*
+ * Tell the VMs to toss their DNS caches
+ */
+ Intent intent = new Intent(Intent.ACTION_CLEAR_DNS_CACHE);
+ intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ /*
+ * Connectivity events can happen before boot has completed ...
+ */
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ // Caller must grab mDnsLock.
+ private void updateDnsLocked(String network, int netId,
+ Collection<InetAddress> dnses, String domains) {
+ int last = 0;
+ if (dnses.size() == 0 && mDefaultDns != null) {
+ dnses = new ArrayList();
+ dnses.add(mDefaultDns);
+ if (DBG) {
+ loge("no dns provided for " + network + " - using " + mDefaultDns.getHostAddress());
+ }
+ }
+
+ try {
+ mNetd.setDnsServersForNetwork(netId, NetworkUtils.makeStrings(dnses), domains);
+
+ for (InetAddress dns : dnses) {
+ ++last;
+ String key = "net.dns" + last;
+ String value = dns.getHostAddress();
+ SystemProperties.set(key, value);
+ }
+ for (int i = last + 1; i <= mNumDnsEntries; ++i) {
+ String key = "net.dns" + i;
+ SystemProperties.set(key, "");
+ }
+ mNumDnsEntries = last;
+ } catch (Exception e) {
+ loge("exception setting default dns interface: " + e);
+ }
+ }
+
+ private void handleDnsConfigurationChange(int netType) {
+ // add default net's dns entries
+ NetworkStateTracker nt = mNetTrackers[netType];
+ if (nt != null && nt.getNetworkInfo().isConnected() && !nt.isTeardownRequested()) {
+ LinkProperties p = nt.getLinkProperties();
+ if (p == null) return;
+ Collection<InetAddress> dnses = p.getDnsServers();
+ int netId = nt.getNetwork().netId;
+ if (mNetConfigs[netType].isDefault()) {
+ String network = nt.getNetworkInfo().getTypeName();
+ synchronized (mDnsLock) {
+ updateDnsLocked(network, netId, dnses, p.getDomains());
+ }
+ } else {
+ try {
+ mNetd.setDnsServersForNetwork(netId,
+ NetworkUtils.makeStrings(dnses), p.getDomains());
+ } catch (Exception e) {
+ if (DBG) loge("exception setting dns servers: " + e);
+ }
+ // set per-pid dns for attached secondary nets
+ List<Integer> pids = mNetRequestersPids[netType];
+ for (Integer pid : pids) {
+ try {
+ // TODO: Reimplement this via local variable in bionic.
+ // mNetd.setDnsNetworkForPid(netId, pid);
+ } catch (Exception e) {
+ Slog.e(TAG, "exception setting interface for pid: " + e);
+ }
+ }
+ }
+ flushVmDnsCache();
+ }
+ }
+
+ @Override
+ public int getRestoreDefaultNetworkDelay(int networkType) {
+ String restoreDefaultNetworkDelayStr = SystemProperties.get(
+ NETWORK_RESTORE_DELAY_PROP_NAME);
+ if(restoreDefaultNetworkDelayStr != null &&
+ restoreDefaultNetworkDelayStr.length() != 0) {
+ try {
+ return Integer.valueOf(restoreDefaultNetworkDelayStr);
+ } catch (NumberFormatException e) {
+ }
+ }
+ // if the system property isn't set, use the value for the apn type
+ int ret = RESTORE_DEFAULT_NETWORK_DELAY;
+
+ if ((networkType <= ConnectivityManager.MAX_NETWORK_TYPE) &&
+ (mNetConfigs[networkType] != null)) {
+ ret = mNetConfigs[networkType].restoreTime;
+ }
+ return ret;
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
+ if (mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+ pw.println("Permission Denial: can't dump ConnectivityService " +
+ "from from pid=" + Binder.getCallingPid() + ", uid=" +
+ Binder.getCallingUid());
+ return;
+ }
+
+ pw.println("NetworkFactories for:");
+ pw.increaseIndent();
+ for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
+ pw.println(nfi.name);
+ }
+ pw.decreaseIndent();
+ pw.println();
+
+ NetworkAgentInfo defaultNai = mNetworkForRequestId.get(mDefaultRequest.requestId);
+ pw.print("Active default network: ");
+ if (defaultNai == null) {
+ pw.println("none");
+ } else {
+ pw.println(defaultNai.network.netId);
+ }
+ pw.println();
+
+ pw.println("Current Networks:");
+ pw.increaseIndent();
+ for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
+ pw.println(nai.toString());
+ pw.increaseIndent();
+ pw.println("Requests:");
+ pw.increaseIndent();
+ for (int i = 0; i < nai.networkRequests.size(); i++) {
+ pw.println(nai.networkRequests.valueAt(i).toString());
+ }
+ pw.decreaseIndent();
+ pw.println("Lingered:");
+ pw.increaseIndent();
+ for (NetworkRequest nr : nai.networkLingered) pw.println(nr.toString());
+ pw.decreaseIndent();
+ pw.decreaseIndent();
+ }
+ pw.decreaseIndent();
+ pw.println();
+
+ pw.println("Network Requests:");
+ pw.increaseIndent();
+ for (NetworkRequestInfo nri : mNetworkRequests.values()) {
+ pw.println(nri.toString());
+ }
+ pw.println();
+ pw.decreaseIndent();
+
+ synchronized (this) {
+ pw.println("NetworkTranstionWakeLock is currently " +
+ (mNetTransitionWakeLock.isHeld() ? "" : "not ") + "held.");
+ pw.println("It was last requested for "+mNetTransitionWakeLockCausedBy);
+ }
+ pw.println();
+
+ mTethering.dump(fd, pw, args);
+
+ if (mInetLog != null) {
+ pw.println();
+ pw.println("Inet condition reports:");
+ pw.increaseIndent();
+ for(int i = 0; i < mInetLog.size(); i++) {
+ pw.println(mInetLog.get(i));
+ }
+ pw.decreaseIndent();
+ }
+ }
+
+ // must be stateless - things change under us.
+ private class NetworkStateTrackerHandler extends Handler {
+ public NetworkStateTrackerHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ NetworkInfo info;
+ switch (msg.what) {
+ case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: {
+ handleAsyncChannelHalfConnect(msg);
+ break;
+ }
+ case AsyncChannel.CMD_CHANNEL_DISCONNECT: {
+ NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
+ if (nai != null) nai.asyncChannel.disconnect();
+ break;
+ }
+ case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
+ handleAsyncChannelDisconnected(msg);
+ break;
+ }
+ case NetworkAgent.EVENT_NETWORK_CAPABILITIES_CHANGED: {
+ NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
+ if (nai == null) {
+ loge("EVENT_NETWORK_CAPABILITIES_CHANGED from unknown NetworkAgent");
+ } else {
+ updateCapabilities(nai, (NetworkCapabilities)msg.obj);
+ }
+ break;
+ }
+ case NetworkAgent.EVENT_NETWORK_PROPERTIES_CHANGED: {
+ NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
+ if (nai == null) {
+ loge("NetworkAgent not found for EVENT_NETWORK_PROPERTIES_CHANGED");
+ } else {
+ if (VDBG) log("Update of Linkproperties for " + nai.name());
+ LinkProperties oldLp = nai.linkProperties;
+ nai.linkProperties = (LinkProperties)msg.obj;
+ updateLinkProperties(nai, oldLp);
+ }
+ break;
+ }
+ case NetworkAgent.EVENT_NETWORK_INFO_CHANGED: {
+ NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
+ if (nai == null) {
+ loge("EVENT_NETWORK_INFO_CHANGED from unknown NetworkAgent");
+ break;
+ }
+ info = (NetworkInfo) msg.obj;
+ updateNetworkInfo(nai, info);
+ break;
+ }
+ case NetworkAgent.EVENT_NETWORK_SCORE_CHANGED: {
+ NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
+ if (nai == null) {
+ loge("EVENT_NETWORK_SCORE_CHANGED from unknown NetworkAgent");
+ break;
+ }
+ Integer score = (Integer) msg.obj;
+ if (score != null) updateNetworkScore(nai, score.intValue());
+ break;
+ }
+ case NetworkMonitor.EVENT_NETWORK_VALIDATED: {
+ NetworkAgentInfo nai = (NetworkAgentInfo)msg.obj;
+ handleConnectionValidated(nai);
+ break;
+ }
+ case NetworkMonitor.EVENT_NETWORK_LINGER_COMPLETE: {
+ NetworkAgentInfo nai = (NetworkAgentInfo)msg.obj;
+ handleLingerComplete(nai);
+ break;
+ }
+ case NetworkStateTracker.EVENT_STATE_CHANGED: {
+ info = (NetworkInfo) msg.obj;
+ NetworkInfo.State state = info.getState();
+
+ if (VDBG || (state == NetworkInfo.State.CONNECTED) ||
+ (state == NetworkInfo.State.DISCONNECTED) ||
+ (state == NetworkInfo.State.SUSPENDED)) {
+ log("ConnectivityChange for " +
+ info.getTypeName() + ": " +
+ state + "/" + info.getDetailedState());
+ }
+
+ // Since mobile has the notion of a network/apn that can be used for
+ // provisioning we need to check every time we're connected as
+ // CaptiveProtalTracker won't detected it because DCT doesn't report it
+ // as connected as ACTION_ANY_DATA_CONNECTION_STATE_CHANGED instead its
+ // reported as ACTION_DATA_CONNECTION_CONNECTED_TO_PROVISIONING_APN. Which
+ // is received by MDST and sent here as EVENT_STATE_CHANGED.
+ if (ConnectivityManager.isNetworkTypeMobile(info.getType())
+ && (0 != Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, 0))
+ && (((state == NetworkInfo.State.CONNECTED)
+ && (info.getType() == ConnectivityManager.TYPE_MOBILE))
+ || info.isConnectedToProvisioningNetwork())) {
+ log("ConnectivityChange checkMobileProvisioning for"
+ + " TYPE_MOBILE or ProvisioningNetwork");
+ checkMobileProvisioning(CheckMp.MAX_TIMEOUT_MS);
+ }
+
+ EventLogTags.writeConnectivityStateChanged(
+ info.getType(), info.getSubtype(), info.getDetailedState().ordinal());
+
+ if (info.isConnectedToProvisioningNetwork()) {
+ /**
+ * TODO: Create ConnectivityManager.TYPE_MOBILE_PROVISIONING
+ * for now its an in between network, its a network that
+ * is actually a default network but we don't want it to be
+ * announced as such to keep background applications from
+ * trying to use it. It turns out that some still try so we
+ * take the additional step of clearing any default routes
+ * to the link that may have incorrectly setup by the lower
+ * levels.
+ */
+ LinkProperties lp = getLinkPropertiesForTypeInternal(info.getType());
+ if (DBG) {
+ log("EVENT_STATE_CHANGED: connected to provisioning network, lp=" + lp);
+ }
+
+ // Clear any default routes setup by the radio so
+ // any activity by applications trying to use this
+ // connection will fail until the provisioning network
+ // is enabled.
+ for (RouteInfo r : lp.getRoutes()) {
+ removeRoute(lp, r, TO_DEFAULT_TABLE,
+ mNetTrackers[info.getType()].getNetwork().netId);
+ }
+ } else if (state == NetworkInfo.State.DISCONNECTED) {
+ } else if (state == NetworkInfo.State.SUSPENDED) {
+ } else if (state == NetworkInfo.State.CONNECTED) {
+ // handleConnect(info);
+ }
+ if (mLockdownTracker != null) {
+ mLockdownTracker.onNetworkInfoChanged(info);
+ }
+ break;
+ }
+ case NetworkStateTracker.EVENT_CONFIGURATION_CHANGED: {
+ info = (NetworkInfo) msg.obj;
+ // TODO: Temporary allowing network configuration
+ // change not resetting sockets.
+ // @see bug/4455071
+ handleConnectivityChange(info.getType(), mCurrentLinkProperties[info.getType()],
+ false);
+ break;
+ }
+ case NetworkStateTracker.EVENT_NETWORK_SUBTYPE_CHANGED: {
+ info = (NetworkInfo) msg.obj;
+ int type = info.getType();
+ if (mNetConfigs[type].isDefault()) updateNetworkSettings(mNetTrackers[type]);
+ break;
+ }
+ }
+ }
+ }
+
+ private void handleAsyncChannelHalfConnect(Message msg) {
+ AsyncChannel ac = (AsyncChannel) msg.obj;
+ if (mNetworkFactoryInfos.containsKey(msg.replyTo)) {
+ if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
+ if (VDBG) log("NetworkFactory connected");
+ // A network factory has connected. Send it all current NetworkRequests.
+ for (NetworkRequestInfo nri : mNetworkRequests.values()) {
+ if (nri.isRequest == false) continue;
+ NetworkAgentInfo nai = mNetworkForRequestId.get(nri.request.requestId);
+ ac.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK,
+ (nai != null ? nai.currentScore : 0), 0, nri.request);
+ }
+ } else {
+ loge("Error connecting NetworkFactory");
+ mNetworkFactoryInfos.remove(msg.obj);
+ }
+ } else if (mNetworkAgentInfos.containsKey(msg.replyTo)) {
+ if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
+ if (VDBG) log("NetworkAgent connected");
+ // A network agent has requested a connection. Establish the connection.
+ mNetworkAgentInfos.get(msg.replyTo).asyncChannel.
+ sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
+ } else {
+ loge("Error connecting NetworkAgent");
+ NetworkAgentInfo nai = mNetworkAgentInfos.remove(msg.replyTo);
+ if (nai != null) {
+ mNetworkForNetId.remove(nai.network.netId);
+ mLegacyTypeTracker.remove(nai);
+ }
+ }
+ }
+ }
+ private void handleAsyncChannelDisconnected(Message msg) {
+ NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
+ if (nai != null) {
+ if (DBG) {
+ log(nai.name() + " got DISCONNECTED, was satisfying " + nai.networkRequests.size());
+ }
+ // A network agent has disconnected.
+ // Tell netd to clean up the configuration for this network
+ // (routing rules, DNS, etc).
+ try {
+ mNetd.removeNetwork(nai.network.netId);
+ } catch (Exception e) {
+ loge("Exception removing network: " + e);
+ }
+ // TODO - if we move the logic to the network agent (have them disconnect
+ // because they lost all their requests or because their score isn't good)
+ // then they would disconnect organically, report their new state and then
+ // disconnect the channel.
+ if (nai.networkInfo.isConnected()) {
+ nai.networkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED,
+ null, null);
+ }
+ notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOST);
+ nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_DISCONNECTED);
+ mNetworkAgentInfos.remove(msg.replyTo);
+ updateClat(null, nai.linkProperties, nai);
+ mLegacyTypeTracker.remove(nai);
+ mNetworkForNetId.remove(nai.network.netId);
+ // Since we've lost the network, go through all the requests that
+ // it was satisfying and see if any other factory can satisfy them.
+ final ArrayList<NetworkAgentInfo> toActivate = new ArrayList<NetworkAgentInfo>();
+ for (int i = 0; i < nai.networkRequests.size(); i++) {
+ NetworkRequest request = nai.networkRequests.valueAt(i);
+ NetworkAgentInfo currentNetwork = mNetworkForRequestId.get(request.requestId);
+ if (VDBG) {
+ log(" checking request " + request + ", currentNetwork = " +
+ (currentNetwork != null ? currentNetwork.name() : "null"));
+ }
+ if (currentNetwork != null && currentNetwork.network.netId == nai.network.netId) {
+ mNetworkForRequestId.remove(request.requestId);
+ sendUpdatedScoreToFactories(request, 0);
+ NetworkAgentInfo alternative = null;
+ for (Map.Entry entry : mNetworkAgentInfos.entrySet()) {
+ NetworkAgentInfo existing = (NetworkAgentInfo)entry.getValue();
+ if (existing.networkInfo.isConnected() &&
+ request.networkCapabilities.satisfiedByNetworkCapabilities(
+ existing.networkCapabilities) &&
+ (alternative == null ||
+ alternative.currentScore < existing.currentScore)) {
+ alternative = existing;
+ }
+ }
+ if (alternative != null && !toActivate.contains(alternative)) {
+ toActivate.add(alternative);
+ }
+ }
+ }
+ if (nai.networkRequests.get(mDefaultRequest.requestId) != null) {
+ removeDataActivityTracking(nai);
+ mActiveDefaultNetwork = ConnectivityManager.TYPE_NONE;
+ }
+ for (NetworkAgentInfo networkToActivate : toActivate) {
+ networkToActivate.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED);
+ }
+ }
+ }
+
+ private void handleRegisterNetworkRequest(Message msg) {
+ final NetworkRequestInfo nri = (NetworkRequestInfo) (msg.obj);
+ final NetworkCapabilities newCap = nri.request.networkCapabilities;
+ int score = 0;
+
+ // Check for the best currently alive network that satisfies this request
+ NetworkAgentInfo bestNetwork = null;
+ for (NetworkAgentInfo network : mNetworkAgentInfos.values()) {
+ if (VDBG) log("handleRegisterNetworkRequest checking " + network.name());
+ if (newCap.satisfiedByNetworkCapabilities(network.networkCapabilities)) {
+ if (VDBG) log("apparently satisfied. currentScore=" + network.currentScore);
+ if ((bestNetwork == null) || bestNetwork.currentScore < network.currentScore) {
+ bestNetwork = network;
+ }
+ }
+ }
+ if (bestNetwork != null) {
+ if (VDBG) log("using " + bestNetwork.name());
+ bestNetwork.addRequest(nri.request);
+ mNetworkForRequestId.put(nri.request.requestId, bestNetwork);
+ int legacyType = nri.request.legacyType;
+ if (legacyType != TYPE_NONE) {
+ mLegacyTypeTracker.add(legacyType, bestNetwork);
+ }
+ notifyNetworkCallback(bestNetwork, nri);
+ score = bestNetwork.currentScore;
+ }
+ mNetworkRequests.put(nri.request, nri);
+ if (msg.what == EVENT_REGISTER_NETWORK_REQUEST) {
+ if (DBG) log("sending new NetworkRequest to factories");
+ for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
+ nfi.asyncChannel.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK, score,
+ 0, nri.request);
+ }
+ }
+ }
+
+ private void handleReleaseNetworkRequest(NetworkRequest request) {
+ if (DBG) log("releasing NetworkRequest " + request);
+ NetworkRequestInfo nri = mNetworkRequests.remove(request);
+ if (nri != null) {
+ // tell the network currently servicing this that it's no longer interested
+ NetworkAgentInfo affectedNetwork = mNetworkForRequestId.get(nri.request.requestId);
+ if (affectedNetwork != null) {
+ mNetworkForRequestId.remove(nri.request.requestId);
+ affectedNetwork.networkRequests.remove(nri.request.requestId);
+ if (VDBG) {
+ log(" Removing from current network " + affectedNetwork.name() + ", leaving " +
+ affectedNetwork.networkRequests.size() + " requests.");
+ }
+ }
+
+ if (nri.isRequest) {
+ for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
+ nfi.asyncChannel.sendMessage(android.net.NetworkFactory.CMD_CANCEL_REQUEST,
+ nri.request);
+ }
+
+ if (affectedNetwork != null) {
+ // check if this network still has live requests - otherwise, tear down
+ // TODO - probably push this to the NF/NA
+ boolean keep = false;
+ for (int i = 0; i < affectedNetwork.networkRequests.size(); i++) {
+ NetworkRequest r = affectedNetwork.networkRequests.valueAt(i);
+ if (mNetworkRequests.get(r).isRequest) {
+ keep = true;
+ break;
+ }
+ }
+ if (keep == false) {
+ if (DBG) log("no live requests for " + affectedNetwork.name() +
+ "; disconnecting");
+ affectedNetwork.asyncChannel.disconnect();
+ }
+ }
+ }
+ callCallbackForRequest(nri, null, ConnectivityManager.CALLBACK_RELEASED);
+ }
+ }
+
+ private class InternalHandler extends Handler {
+ public InternalHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ NetworkInfo info;
+ switch (msg.what) {
+ case EVENT_CLEAR_NET_TRANSITION_WAKELOCK: {
+ String causedBy = null;
+ synchronized (ConnectivityService.this) {
+ if (msg.arg1 == mNetTransitionWakeLockSerialNumber &&
+ mNetTransitionWakeLock.isHeld()) {
+ mNetTransitionWakeLock.release();
+ causedBy = mNetTransitionWakeLockCausedBy;
+ }
+ }
+ if (causedBy != null) {
+ log("NetTransition Wakelock for " + causedBy + " released by timeout");
+ }
+ break;
+ }
+ case EVENT_RESTORE_DEFAULT_NETWORK: {
+ FeatureUser u = (FeatureUser)msg.obj;
+ u.expire();
+ break;
+ }
+ case EVENT_INET_CONDITION_CHANGE: {
+ int netType = msg.arg1;
+ int condition = msg.arg2;
+ handleInetConditionChange(netType, condition);
+ break;
+ }
+ case EVENT_INET_CONDITION_HOLD_END: {
+ int netType = msg.arg1;
+ int sequence = msg.arg2;
+ handleInetConditionHoldEnd(netType, sequence);
+ break;
+ }
+ case EVENT_APPLY_GLOBAL_HTTP_PROXY: {
+ handleDeprecatedGlobalHttpProxy();
+ break;
+ }
+ case EVENT_SET_DEPENDENCY_MET: {
+ boolean met = (msg.arg1 == ENABLED);
+ handleSetDependencyMet(msg.arg2, met);
+ break;
+ }
+ case EVENT_SEND_STICKY_BROADCAST_INTENT: {
+ Intent intent = (Intent)msg.obj;
+ sendStickyBroadcast(intent);
+ break;
+ }
+ case EVENT_SET_POLICY_DATA_ENABLE: {
+ final int networkType = msg.arg1;
+ final boolean enabled = msg.arg2 == ENABLED;
+ handleSetPolicyDataEnable(networkType, enabled);
+ break;
+ }
+ case EVENT_VPN_STATE_CHANGED: {
+ if (mLockdownTracker != null) {
+ mLockdownTracker.onVpnStateChanged((NetworkInfo) msg.obj);
+ }
+ break;
+ }
+ case EVENT_ENABLE_FAIL_FAST_MOBILE_DATA: {
+ int tag = mEnableFailFastMobileDataTag.get();
+ if (msg.arg1 == tag) {
+ MobileDataStateTracker mobileDst =
+ (MobileDataStateTracker) mNetTrackers[ConnectivityManager.TYPE_MOBILE];
+ if (mobileDst != null) {
+ mobileDst.setEnableFailFastMobileData(msg.arg2);
+ }
+ } else {
+ log("EVENT_ENABLE_FAIL_FAST_MOBILE_DATA: stale arg1:" + msg.arg1
+ + " != tag:" + tag);
+ }
+ break;
+ }
+ case EVENT_SAMPLE_INTERVAL_ELAPSED: {
+ handleNetworkSamplingTimeout();
+ break;
+ }
+ case EVENT_PROXY_HAS_CHANGED: {
+ handleApplyDefaultProxy((ProxyInfo)msg.obj);
+ break;
+ }
+ case EVENT_REGISTER_NETWORK_FACTORY: {
+ handleRegisterNetworkFactory((NetworkFactoryInfo)msg.obj);
+ break;
+ }
+ case EVENT_UNREGISTER_NETWORK_FACTORY: {
+ handleUnregisterNetworkFactory((Messenger)msg.obj);
+ break;
+ }
+ case EVENT_REGISTER_NETWORK_AGENT: {
+ handleRegisterNetworkAgent((NetworkAgentInfo)msg.obj);
+ break;
+ }
+ case EVENT_REGISTER_NETWORK_REQUEST:
+ case EVENT_REGISTER_NETWORK_LISTENER: {
+ handleRegisterNetworkRequest(msg);
+ break;
+ }
+ case EVENT_RELEASE_NETWORK_REQUEST: {
+ handleReleaseNetworkRequest((NetworkRequest) msg.obj);
+ break;
+ }
+ }
+ }
+ }
+
+ // javadoc from interface
+ public int tether(String iface) {
+ enforceTetherChangePermission();
+
+ if (isTetheringSupported()) {
+ return mTethering.tether(iface);
+ } else {
+ return ConnectivityManager.TETHER_ERROR_UNSUPPORTED;
+ }
+ }
+
+ // javadoc from interface
+ public int untether(String iface) {
+ enforceTetherChangePermission();
+
+ if (isTetheringSupported()) {
+ return mTethering.untether(iface);
+ } else {
+ return ConnectivityManager.TETHER_ERROR_UNSUPPORTED;
+ }
+ }
+
+ // javadoc from interface
+ public int getLastTetherError(String iface) {
+ enforceTetherAccessPermission();
+
+ if (isTetheringSupported()) {
+ return mTethering.getLastTetherError(iface);
+ } else {
+ return ConnectivityManager.TETHER_ERROR_UNSUPPORTED;
+ }
+ }
+
+ // TODO - proper iface API for selection by property, inspection, etc
+ public String[] getTetherableUsbRegexs() {
+ enforceTetherAccessPermission();
+ if (isTetheringSupported()) {
+ return mTethering.getTetherableUsbRegexs();
+ } else {
+ return new String[0];
+ }
+ }
+
+ public String[] getTetherableWifiRegexs() {
+ enforceTetherAccessPermission();
+ if (isTetheringSupported()) {
+ return mTethering.getTetherableWifiRegexs();
+ } else {
+ return new String[0];
+ }
+ }
+
+ public String[] getTetherableBluetoothRegexs() {
+ enforceTetherAccessPermission();
+ if (isTetheringSupported()) {
+ return mTethering.getTetherableBluetoothRegexs();
+ } else {
+ return new String[0];
+ }
+ }
+
+ public int setUsbTethering(boolean enable) {
+ enforceTetherChangePermission();
+ if (isTetheringSupported()) {
+ return mTethering.setUsbTethering(enable);
+ } else {
+ return ConnectivityManager.TETHER_ERROR_UNSUPPORTED;
+ }
+ }
+
+ // TODO - move iface listing, queries, etc to new module
+ // javadoc from interface
+ public String[] getTetherableIfaces() {
+ enforceTetherAccessPermission();
+ return mTethering.getTetherableIfaces();
+ }
+
+ public String[] getTetheredIfaces() {
+ enforceTetherAccessPermission();
+ return mTethering.getTetheredIfaces();
+ }
+
+ public String[] getTetheringErroredIfaces() {
+ enforceTetherAccessPermission();
+ return mTethering.getErroredIfaces();
+ }
+
+ // if ro.tether.denied = true we default to no tethering
+ // gservices could set the secure setting to 1 though to enable it on a build where it
+ // had previously been turned off.
+ public boolean isTetheringSupported() {
+ enforceTetherAccessPermission();
+ int defaultVal = (SystemProperties.get("ro.tether.denied").equals("true") ? 0 : 1);
+ boolean tetherEnabledInSettings = (Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.TETHER_SUPPORTED, defaultVal) != 0);
+ return tetherEnabledInSettings && ((mTethering.getTetherableUsbRegexs().length != 0 ||
+ mTethering.getTetherableWifiRegexs().length != 0 ||
+ mTethering.getTetherableBluetoothRegexs().length != 0) &&
+ mTethering.getUpstreamIfaceTypes().length != 0);
+ }
+
+ // An API NetworkStateTrackers can call when they lose their network.
+ // This will automatically be cleared after X seconds or a network becomes CONNECTED,
+ // whichever happens first. The timer is started by the first caller and not
+ // restarted by subsequent callers.
+ public void requestNetworkTransitionWakelock(String forWhom) {
+ enforceConnectivityInternalPermission();
+ synchronized (this) {
+ if (mNetTransitionWakeLock.isHeld()) return;
+ mNetTransitionWakeLockSerialNumber++;
+ mNetTransitionWakeLock.acquire();
+ mNetTransitionWakeLockCausedBy = forWhom;
+ }
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(
+ EVENT_CLEAR_NET_TRANSITION_WAKELOCK,
+ mNetTransitionWakeLockSerialNumber, 0),
+ mNetTransitionWakeLockTimeout);
+ return;
+ }
+
+ // 100 percent is full good, 0 is full bad.
+ public void reportInetCondition(int networkType, int percentage) {
+ if (VDBG) log("reportNetworkCondition(" + networkType + ", " + percentage + ")");
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.STATUS_BAR,
+ "ConnectivityService");
+
+ if (DBG) {
+ int pid = getCallingPid();
+ int uid = getCallingUid();
+ String s = pid + "(" + uid + ") reports inet is " +
+ (percentage > 50 ? "connected" : "disconnected") + " (" + percentage + ") on " +
+ "network Type " + networkType + " at " + GregorianCalendar.getInstance().getTime();
+ mInetLog.add(s);
+ while(mInetLog.size() > INET_CONDITION_LOG_MAX_SIZE) {
+ mInetLog.remove(0);
+ }
+ }
+ mHandler.sendMessage(mHandler.obtainMessage(
+ EVENT_INET_CONDITION_CHANGE, networkType, percentage));
+ }
+
+ public void reportBadNetwork(Network network) {
+ //TODO
+ }
+
+ private void handleInetConditionChange(int netType, int condition) {
+ if (mActiveDefaultNetwork == -1) {
+ if (DBG) log("handleInetConditionChange: no active default network - ignore");
+ return;
+ }
+ if (mActiveDefaultNetwork != netType) {
+ if (DBG) log("handleInetConditionChange: net=" + netType +
+ " != default=" + mActiveDefaultNetwork + " - ignore");
+ return;
+ }
+ if (VDBG) {
+ log("handleInetConditionChange: net=" +
+ netType + ", condition=" + condition +
+ ",mActiveDefaultNetwork=" + mActiveDefaultNetwork);
+ }
+ mDefaultInetCondition = condition;
+ int delay;
+ if (mInetConditionChangeInFlight == false) {
+ if (VDBG) log("handleInetConditionChange: starting a change hold");
+ // setup a new hold to debounce this
+ if (mDefaultInetCondition > 50) {
+ delay = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.INET_CONDITION_DEBOUNCE_UP_DELAY, 500);
+ } else {
+ delay = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.INET_CONDITION_DEBOUNCE_DOWN_DELAY, 3000);
+ }
+ mInetConditionChangeInFlight = true;
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_INET_CONDITION_HOLD_END,
+ mActiveDefaultNetwork, mDefaultConnectionSequence), delay);
+ } else {
+ // we've set the new condition, when this hold ends that will get picked up
+ if (VDBG) log("handleInetConditionChange: currently in hold - not setting new end evt");
+ }
+ }
+
+ private void handleInetConditionHoldEnd(int netType, int sequence) {
+ if (DBG) {
+ log("handleInetConditionHoldEnd: net=" + netType +
+ ", condition=" + mDefaultInetCondition +
+ ", published condition=" + mDefaultInetConditionPublished);
+ }
+ mInetConditionChangeInFlight = false;
+
+ if (mActiveDefaultNetwork == -1) {
+ if (DBG) log("handleInetConditionHoldEnd: no active default network - ignoring");
+ return;
+ }
+ if (mDefaultConnectionSequence != sequence) {
+ if (DBG) log("handleInetConditionHoldEnd: event hold for obsolete network - ignoring");
+ return;
+ }
+ // TODO: Figure out why this optimization sometimes causes a
+ // change in mDefaultInetCondition to be missed and the
+ // UI to not be updated.
+ //if (mDefaultInetConditionPublished == mDefaultInetCondition) {
+ // if (DBG) log("no change in condition - aborting");
+ // return;
+ //}
+ NetworkInfo networkInfo = getNetworkInfoForType(mActiveDefaultNetwork);
+ if (networkInfo.isConnected() == false) {
+ if (DBG) log("handleInetConditionHoldEnd: default network not connected - ignoring");
+ return;
+ }
+ mDefaultInetConditionPublished = mDefaultInetCondition;
+ sendInetConditionBroadcast(networkInfo);
+ return;
+ }
+
+ public ProxyInfo getProxy() {
+ // this information is already available as a world read/writable jvm property
+ // so this API change wouldn't have a benifit. It also breaks the passing
+ // of proxy info to all the JVMs.
+ // enforceAccessPermission();
+ synchronized (mProxyLock) {
+ ProxyInfo ret = mGlobalProxy;
+ if ((ret == null) && !mDefaultProxyDisabled) ret = mDefaultProxy;
+ return ret;
+ }
+ }
+
+ public void setGlobalProxy(ProxyInfo proxyProperties) {
+ enforceConnectivityInternalPermission();
+
+ synchronized (mProxyLock) {
+ if (proxyProperties == mGlobalProxy) return;
+ if (proxyProperties != null && proxyProperties.equals(mGlobalProxy)) return;
+ if (mGlobalProxy != null && mGlobalProxy.equals(proxyProperties)) return;
+
+ String host = "";
+ int port = 0;
+ String exclList = "";
+ String pacFileUrl = "";
+ if (proxyProperties != null && (!TextUtils.isEmpty(proxyProperties.getHost()) ||
+ (proxyProperties.getPacFileUrl() != null))) {
+ if (!proxyProperties.isValid()) {
+ if (DBG)
+ log("Invalid proxy properties, ignoring: " + proxyProperties.toString());
+ return;
+ }
+ mGlobalProxy = new ProxyInfo(proxyProperties);
+ host = mGlobalProxy.getHost();
+ port = mGlobalProxy.getPort();
+ exclList = mGlobalProxy.getExclusionListAsString();
+ if (proxyProperties.getPacFileUrl() != null) {
+ pacFileUrl = proxyProperties.getPacFileUrl().toString();
+ }
+ } else {
+ mGlobalProxy = null;
+ }
+ ContentResolver res = mContext.getContentResolver();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ Settings.Global.putString(res, Settings.Global.GLOBAL_HTTP_PROXY_HOST, host);
+ Settings.Global.putInt(res, Settings.Global.GLOBAL_HTTP_PROXY_PORT, port);
+ Settings.Global.putString(res, Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST,
+ exclList);
+ Settings.Global.putString(res, Settings.Global.GLOBAL_HTTP_PROXY_PAC, pacFileUrl);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ if (mGlobalProxy == null) {
+ proxyProperties = mDefaultProxy;
+ }
+ sendProxyBroadcast(proxyProperties);
+ }
+
+ private void loadGlobalProxy() {
+ ContentResolver res = mContext.getContentResolver();
+ String host = Settings.Global.getString(res, Settings.Global.GLOBAL_HTTP_PROXY_HOST);
+ int port = Settings.Global.getInt(res, Settings.Global.GLOBAL_HTTP_PROXY_PORT, 0);
+ String exclList = Settings.Global.getString(res,
+ Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST);
+ String pacFileUrl = Settings.Global.getString(res, Settings.Global.GLOBAL_HTTP_PROXY_PAC);
+ if (!TextUtils.isEmpty(host) || !TextUtils.isEmpty(pacFileUrl)) {
+ ProxyInfo proxyProperties;
+ if (!TextUtils.isEmpty(pacFileUrl)) {
+ proxyProperties = new ProxyInfo(pacFileUrl);
+ } else {
+ proxyProperties = new ProxyInfo(host, port, exclList);
+ }
+ if (!proxyProperties.isValid()) {
+ if (DBG) log("Invalid proxy properties, ignoring: " + proxyProperties.toString());
+ return;
+ }
+
+ synchronized (mProxyLock) {
+ mGlobalProxy = proxyProperties;
+ }
+ }
+ }
+
+ public ProxyInfo getGlobalProxy() {
+ // this information is already available as a world read/writable jvm property
+ // so this API change wouldn't have a benifit. It also breaks the passing
+ // of proxy info to all the JVMs.
+ // enforceAccessPermission();
+ synchronized (mProxyLock) {
+ return mGlobalProxy;
+ }
+ }
+
+ private void handleApplyDefaultProxy(ProxyInfo proxy) {
+ if (proxy != null && TextUtils.isEmpty(proxy.getHost())
+ && (proxy.getPacFileUrl() == null)) {
+ proxy = null;
+ }
+ synchronized (mProxyLock) {
+ if (mDefaultProxy != null && mDefaultProxy.equals(proxy)) return;
+ if (mDefaultProxy == proxy) return; // catches repeated nulls
+ if (proxy != null && !proxy.isValid()) {
+ if (DBG) log("Invalid proxy properties, ignoring: " + proxy.toString());
+ return;
+ }
+
+ // This call could be coming from the PacManager, containing the port of the local
+ // proxy. If this new proxy matches the global proxy then copy this proxy to the
+ // global (to get the correct local port), and send a broadcast.
+ // TODO: Switch PacManager to have its own message to send back rather than
+ // reusing EVENT_HAS_CHANGED_PROXY and this call to handleApplyDefaultProxy.
+ if ((mGlobalProxy != null) && (proxy != null) && (proxy.getPacFileUrl() != null)
+ && proxy.getPacFileUrl().equals(mGlobalProxy.getPacFileUrl())) {
+ mGlobalProxy = proxy;
+ sendProxyBroadcast(mGlobalProxy);
+ return;
+ }
+ mDefaultProxy = proxy;
+
+ if (mGlobalProxy != null) return;
+ if (!mDefaultProxyDisabled) {
+ sendProxyBroadcast(proxy);
+ }
+ }
+ }
+
+ private void handleDeprecatedGlobalHttpProxy() {
+ String proxy = Settings.Global.getString(mContext.getContentResolver(),
+ Settings.Global.HTTP_PROXY);
+ if (!TextUtils.isEmpty(proxy)) {
+ String data[] = proxy.split(":");
+ if (data.length == 0) {
+ return;
+ }
+
+ String proxyHost = data[0];
+ int proxyPort = 8080;
+ if (data.length > 1) {
+ try {
+ proxyPort = Integer.parseInt(data[1]);
+ } catch (NumberFormatException e) {
+ return;
+ }
+ }
+ ProxyInfo p = new ProxyInfo(data[0], proxyPort, "");
+ setGlobalProxy(p);
+ }
+ }
+
+ private void sendProxyBroadcast(ProxyInfo proxy) {
+ if (proxy == null) proxy = new ProxyInfo("", 0, "");
+ if (mPacManager.setCurrentProxyScriptUrl(proxy)) return;
+ if (DBG) log("sending Proxy Broadcast for " + proxy);
+ Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING |
+ Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.putExtra(Proxy.EXTRA_PROXY_INFO, proxy);
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ private static class SettingsObserver extends ContentObserver {
+ private int mWhat;
+ private Handler mHandler;
+ SettingsObserver(Handler handler, int what) {
+ super(handler);
+ mHandler = handler;
+ mWhat = what;
+ }
+
+ void observe(Context context) {
+ ContentResolver resolver = context.getContentResolver();
+ resolver.registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.HTTP_PROXY), false, this);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ mHandler.obtainMessage(mWhat).sendToTarget();
+ }
+ }
+
+ private static void log(String s) {
+ Slog.d(TAG, s);
+ }
+
+ private static void loge(String s) {
+ Slog.e(TAG, s);
+ }
+
+ int convertFeatureToNetworkType(int networkType, String feature) {
+ int usedNetworkType = networkType;
+
+ if(networkType == ConnectivityManager.TYPE_MOBILE) {
+ if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_MMS)) {
+ usedNetworkType = ConnectivityManager.TYPE_MOBILE_MMS;
+ } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_SUPL)) {
+ usedNetworkType = ConnectivityManager.TYPE_MOBILE_SUPL;
+ } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_DUN) ||
+ TextUtils.equals(feature, Phone.FEATURE_ENABLE_DUN_ALWAYS)) {
+ usedNetworkType = ConnectivityManager.TYPE_MOBILE_DUN;
+ } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_HIPRI)) {
+ usedNetworkType = ConnectivityManager.TYPE_MOBILE_HIPRI;
+ } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_FOTA)) {
+ usedNetworkType = ConnectivityManager.TYPE_MOBILE_FOTA;
+ } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_IMS)) {
+ usedNetworkType = ConnectivityManager.TYPE_MOBILE_IMS;
+ } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_CBS)) {
+ usedNetworkType = ConnectivityManager.TYPE_MOBILE_CBS;
+ } else {
+ Slog.e(TAG, "Can't match any mobile netTracker!");
+ }
+ } else if (networkType == ConnectivityManager.TYPE_WIFI) {
+ if (TextUtils.equals(feature, "p2p")) {
+ usedNetworkType = ConnectivityManager.TYPE_WIFI_P2P;
+ } else {
+ Slog.e(TAG, "Can't match any wifi netTracker!");
+ }
+ } else {
+ Slog.e(TAG, "Unexpected network type");
+ }
+ return usedNetworkType;
+ }
+
+ private static <T> T checkNotNull(T value, String message) {
+ if (value == null) {
+ throw new NullPointerException(message);
+ }
+ return value;
+ }
+
+ /**
+ * Protect a socket from VPN routing rules. This method is used by
+ * VpnBuilder and not available in ConnectivityManager. Permissions
+ * are checked in Vpn class.
+ * @hide
+ */
+ @Override
+ public boolean protectVpn(ParcelFileDescriptor socket) {
+ throwIfLockdownEnabled();
+ try {
+ int type = mActiveDefaultNetwork;
+ int user = UserHandle.getUserId(Binder.getCallingUid());
+ if (ConnectivityManager.isNetworkTypeValid(type) && mNetTrackers[type] != null) {
+ synchronized(mVpns) {
+ mVpns.get(user).protect(socket);
+ }
+ return true;
+ }
+ } catch (Exception e) {
+ // ignore
+ } finally {
+ try {
+ socket.close();
+ } catch (Exception e) {
+ // ignore
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Prepare for a VPN application. This method is used by VpnDialogs
+ * and not available in ConnectivityManager. Permissions are checked
+ * in Vpn class.
+ * @hide
+ */
+ @Override
+ public boolean prepareVpn(String oldPackage, String newPackage) {
+ throwIfLockdownEnabled();
+ int user = UserHandle.getUserId(Binder.getCallingUid());
+ synchronized(mVpns) {
+ return mVpns.get(user).prepare(oldPackage, newPackage);
+ }
+ }
+
+ @Override
+ public void markSocketAsUser(ParcelFileDescriptor socket, int uid) {
+ enforceMarkNetworkSocketPermission();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ int mark = mNetd.getMarkForUid(uid);
+ // Clear the mark on the socket if no mark is needed to prevent socket reuse issues
+ if (mark == -1) {
+ mark = 0;
+ }
+ NetworkUtils.markSocket(socket.getFd(), mark);
+ } catch (RemoteException e) {
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Configure a TUN interface and return its file descriptor. Parameters
+ * are encoded and opaque to this class. This method is used by VpnBuilder
+ * and not available in ConnectivityManager. Permissions are checked in
+ * Vpn class.
+ * @hide
+ */
+ @Override
+ public ParcelFileDescriptor establishVpn(VpnConfig config) {
+ throwIfLockdownEnabled();
+ int user = UserHandle.getUserId(Binder.getCallingUid());
+ synchronized(mVpns) {
+ return mVpns.get(user).establish(config);
+ }
+ }
+
+ /**
+ * Start legacy VPN, controlling native daemons as needed. Creates a
+ * secondary thread to perform connection work, returning quickly.
+ */
+ @Override
+ public void startLegacyVpn(VpnProfile profile) {
+ throwIfLockdownEnabled();
+ final LinkProperties egress = getActiveLinkProperties();
+ if (egress == null) {
+ throw new IllegalStateException("Missing active network connection");
+ }
+ int user = UserHandle.getUserId(Binder.getCallingUid());
+ synchronized(mVpns) {
+ mVpns.get(user).startLegacyVpn(profile, mKeyStore, egress);
+ }
+ }
+
+ /**
+ * Return the information of the ongoing legacy VPN. This method is used
+ * by VpnSettings and not available in ConnectivityManager. Permissions
+ * are checked in Vpn class.
+ * @hide
+ */
+ @Override
+ public LegacyVpnInfo getLegacyVpnInfo() {
+ throwIfLockdownEnabled();
+ int user = UserHandle.getUserId(Binder.getCallingUid());
+ synchronized(mVpns) {
+ return mVpns.get(user).getLegacyVpnInfo();
+ }
+ }
+
+ /**
+ * Returns the information of the ongoing VPN. This method is used by VpnDialogs and
+ * not available in ConnectivityManager.
+ * Permissions are checked in Vpn class.
+ * @hide
+ */
+ @Override
+ public VpnConfig getVpnConfig() {
+ int user = UserHandle.getUserId(Binder.getCallingUid());
+ synchronized(mVpns) {
+ return mVpns.get(user).getVpnConfig();
+ }
+ }
+
+ /**
+ * Callback for VPN subsystem. Currently VPN is not adapted to the service
+ * through NetworkStateTracker since it works differently. For example, it
+ * needs to override DNS servers but never takes the default routes. It
+ * relies on another data network, and it could keep existing connections
+ * alive after reconnecting, switching between networks, or even resuming
+ * from deep sleep. Calls from applications should be done synchronously
+ * to avoid race conditions. As these are all hidden APIs, refactoring can
+ * be done whenever a better abstraction is developed.
+ */
+ public class VpnCallback {
+ private VpnCallback() {
+ }
+
+ public void onStateChanged(NetworkInfo info) {
+ mHandler.obtainMessage(EVENT_VPN_STATE_CHANGED, info).sendToTarget();
+ }
+
+ public void override(String iface, List<String> dnsServers, List<String> searchDomains) {
+ if (dnsServers == null) {
+ restore();
+ return;
+ }
+
+ // Convert DNS servers into addresses.
+ List<InetAddress> addresses = new ArrayList<InetAddress>();
+ for (String address : dnsServers) {
+ // Double check the addresses and remove invalid ones.
+ try {
+ addresses.add(InetAddress.parseNumericAddress(address));
+ } catch (Exception e) {
+ // ignore
+ }
+ }
+ if (addresses.isEmpty()) {
+ restore();
+ return;
+ }
+
+ // Concatenate search domains into a string.
+ StringBuilder buffer = new StringBuilder();
+ if (searchDomains != null) {
+ for (String domain : searchDomains) {
+ buffer.append(domain).append(' ');
+ }
+ }
+ String domains = buffer.toString().trim();
+
+ // Apply DNS changes.
+ synchronized (mDnsLock) {
+ // TODO: Re-enable this when the netId of the VPN is known.
+ // updateDnsLocked("VPN", netId, addresses, domains);
+ }
+
+ // Temporarily disable the default proxy (not global).
+ synchronized (mProxyLock) {
+ mDefaultProxyDisabled = true;
+ if (mGlobalProxy == null && mDefaultProxy != null) {
+ sendProxyBroadcast(null);
+ }
+ }
+
+ // TODO: support proxy per network.
+ }
+
+ public void restore() {
+ synchronized (mProxyLock) {
+ mDefaultProxyDisabled = false;
+ if (mGlobalProxy == null && mDefaultProxy != null) {
+ sendProxyBroadcast(mDefaultProxy);
+ }
+ }
+ }
+
+ public void protect(ParcelFileDescriptor socket) {
+ try {
+ final int mark = mNetd.getMarkForProtect();
+ NetworkUtils.markSocket(socket.getFd(), mark);
+ } catch (RemoteException e) {
+ }
+ }
+
+ public void setRoutes(String interfaze, List<RouteInfo> routes) {
+ for (RouteInfo route : routes) {
+ try {
+ mNetd.setMarkedForwardingRoute(interfaze, route);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ public void setMarkedForwarding(String interfaze) {
+ try {
+ mNetd.setMarkedForwarding(interfaze);
+ } catch (RemoteException e) {
+ }
+ }
+
+ public void clearMarkedForwarding(String interfaze) {
+ try {
+ mNetd.clearMarkedForwarding(interfaze);
+ } catch (RemoteException e) {
+ }
+ }
+
+ public void addUserForwarding(String interfaze, int uid, boolean forwardDns) {
+ int uidStart = uid * UserHandle.PER_USER_RANGE;
+ int uidEnd = uidStart + UserHandle.PER_USER_RANGE - 1;
+ addUidForwarding(interfaze, uidStart, uidEnd, forwardDns);
+ }
+
+ public void clearUserForwarding(String interfaze, int uid, boolean forwardDns) {
+ int uidStart = uid * UserHandle.PER_USER_RANGE;
+ int uidEnd = uidStart + UserHandle.PER_USER_RANGE - 1;
+ clearUidForwarding(interfaze, uidStart, uidEnd, forwardDns);
+ }
+
+ public void addUidForwarding(String interfaze, int uidStart, int uidEnd,
+ boolean forwardDns) {
+ // TODO: Re-enable this when the netId of the VPN is known.
+ // try {
+ // mNetd.setUidRangeRoute(netId, uidStart, uidEnd, forwardDns);
+ // } catch (RemoteException e) {
+ // }
+
+ }
+
+ public void clearUidForwarding(String interfaze, int uidStart, int uidEnd,
+ boolean forwardDns) {
+ // TODO: Re-enable this when the netId of the VPN is known.
+ // try {
+ // mNetd.clearUidRangeRoute(interfaze, uidStart, uidEnd);
+ // } catch (RemoteException e) {
+ // }
+
+ }
+ }
+
+ @Override
+ public boolean updateLockdownVpn() {
+ if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+ Slog.w(TAG, "Lockdown VPN only available to AID_SYSTEM");
+ return false;
+ }
+
+ // Tear down existing lockdown if profile was removed
+ mLockdownEnabled = LockdownVpnTracker.isEnabled();
+ if (mLockdownEnabled) {
+ if (!mKeyStore.isUnlocked()) {
+ Slog.w(TAG, "KeyStore locked; unable to create LockdownTracker");
+ return false;
+ }
+
+ final String profileName = new String(mKeyStore.get(Credentials.LOCKDOWN_VPN));
+ final VpnProfile profile = VpnProfile.decode(
+ profileName, mKeyStore.get(Credentials.VPN + profileName));
+ int user = UserHandle.getUserId(Binder.getCallingUid());
+ synchronized(mVpns) {
+ setLockdownTracker(new LockdownVpnTracker(mContext, mNetd, this, mVpns.get(user),
+ profile));
+ }
+ } else {
+ setLockdownTracker(null);
+ }
+
+ return true;
+ }
+
+ /**
+ * Internally set new {@link LockdownVpnTracker}, shutting down any existing
+ * {@link LockdownVpnTracker}. Can be {@code null} to disable lockdown.
+ */
+ private void setLockdownTracker(LockdownVpnTracker tracker) {
+ // Shutdown any existing tracker
+ final LockdownVpnTracker existing = mLockdownTracker;
+ mLockdownTracker = null;
+ if (existing != null) {
+ existing.shutdown();
+ }
+
+ try {
+ if (tracker != null) {
+ mNetd.setFirewallEnabled(true);
+ mNetd.setFirewallInterfaceRule("lo", true);
+ mLockdownTracker = tracker;
+ mLockdownTracker.init();
+ } else {
+ mNetd.setFirewallEnabled(false);
+ }
+ } catch (RemoteException e) {
+ // ignored; NMS lives inside system_server
+ }
+ }
+
+ private void throwIfLockdownEnabled() {
+ if (mLockdownEnabled) {
+ throw new IllegalStateException("Unavailable in lockdown mode");
+ }
+ }
+
+ public void supplyMessenger(int networkType, Messenger messenger) {
+ enforceConnectivityInternalPermission();
+
+ if (isNetworkTypeValid(networkType) && mNetTrackers[networkType] != null) {
+ mNetTrackers[networkType].supplyMessenger(messenger);
+ }
+ }
+
+ public int findConnectionTypeForIface(String iface) {
+ enforceConnectivityInternalPermission();
+
+ if (TextUtils.isEmpty(iface)) return ConnectivityManager.TYPE_NONE;
+ for (NetworkStateTracker tracker : mNetTrackers) {
+ if (tracker != null) {
+ LinkProperties lp = tracker.getLinkProperties();
+ if (lp != null && iface.equals(lp.getInterfaceName())) {
+ return tracker.getNetworkInfo().getType();
+ }
+ }
+ }
+ return ConnectivityManager.TYPE_NONE;
+ }
+
+ /**
+ * Have mobile data fail fast if enabled.
+ *
+ * @param enabled DctConstants.ENABLED/DISABLED
+ */
+ private void setEnableFailFastMobileData(int enabled) {
+ int tag;
+
+ if (enabled == DctConstants.ENABLED) {
+ tag = mEnableFailFastMobileDataTag.incrementAndGet();
+ } else {
+ tag = mEnableFailFastMobileDataTag.get();
+ }
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_ENABLE_FAIL_FAST_MOBILE_DATA, tag,
+ enabled));
+ }
+
+ private boolean isMobileDataStateTrackerReady() {
+ MobileDataStateTracker mdst =
+ (MobileDataStateTracker) mNetTrackers[ConnectivityManager.TYPE_MOBILE_HIPRI];
+ return (mdst != null) && (mdst.isReady());
+ }
+
+ /**
+ * The ResultReceiver resultCode for checkMobileProvisioning (CMP_RESULT_CODE)
+ */
+
+ /**
+ * No connection was possible to the network.
+ * This is NOT a warm sim.
+ */
+ private static final int CMP_RESULT_CODE_NO_CONNECTION = 0;
+
+ /**
+ * A connection was made to the internet, all is well.
+ * This is NOT a warm sim.
+ */
+ private static final int CMP_RESULT_CODE_CONNECTABLE = 1;
+
+ /**
+ * A connection was made but no dns server was available to resolve a name to address.
+ * This is NOT a warm sim since provisioning network is supported.
+ */
+ private static final int CMP_RESULT_CODE_NO_DNS = 2;
+
+ /**
+ * A connection was made but could not open a TCP connection.
+ * This is NOT a warm sim since provisioning network is supported.
+ */
+ private static final int CMP_RESULT_CODE_NO_TCP_CONNECTION = 3;
+
+ /**
+ * A connection was made but there was a redirection, we appear to be in walled garden.
+ * This is an indication of a warm sim on a mobile network such as T-Mobile.
+ */
+ private static final int CMP_RESULT_CODE_REDIRECTED = 4;
+
+ /**
+ * The mobile network is a provisioning network.
+ * This is an indication of a warm sim on a mobile network such as AT&T.
+ */
+ private static final int CMP_RESULT_CODE_PROVISIONING_NETWORK = 5;
+
+ /**
+ * The mobile network is provisioning
+ */
+ private static final int CMP_RESULT_CODE_IS_PROVISIONING = 6;
+
+ private AtomicBoolean mIsProvisioningNetwork = new AtomicBoolean(false);
+ private AtomicBoolean mIsStartingProvisioning = new AtomicBoolean(false);
+
+ private AtomicBoolean mIsCheckingMobileProvisioning = new AtomicBoolean(false);
+
+ @Override
+ public int checkMobileProvisioning(int suggestedTimeOutMs) {
+ int timeOutMs = -1;
+ if (DBG) log("checkMobileProvisioning: E suggestedTimeOutMs=" + suggestedTimeOutMs);
+ enforceConnectivityInternalPermission();
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ timeOutMs = suggestedTimeOutMs;
+ if (suggestedTimeOutMs > CheckMp.MAX_TIMEOUT_MS) {
+ timeOutMs = CheckMp.MAX_TIMEOUT_MS;
+ }
+
+ // Check that mobile networks are supported
+ if (!isNetworkSupported(ConnectivityManager.TYPE_MOBILE)
+ || !isNetworkSupported(ConnectivityManager.TYPE_MOBILE_HIPRI)) {
+ if (DBG) log("checkMobileProvisioning: X no mobile network");
+ return timeOutMs;
+ }
+
+ // If we're already checking don't do it again
+ // TODO: Add a queue of results...
+ if (mIsCheckingMobileProvisioning.getAndSet(true)) {
+ if (DBG) log("checkMobileProvisioning: X already checking ignore for the moment");
+ return timeOutMs;
+ }
+
+ // Start off with mobile notification off
+ setProvNotificationVisible(false, ConnectivityManager.TYPE_MOBILE_HIPRI, null, null);
+
+ CheckMp checkMp = new CheckMp(mContext, this);
+ CheckMp.CallBack cb = new CheckMp.CallBack() {
+ @Override
+ void onComplete(Integer result) {
+ if (DBG) log("CheckMp.onComplete: result=" + result);
+ NetworkInfo ni =
+ mNetTrackers[ConnectivityManager.TYPE_MOBILE_HIPRI].getNetworkInfo();
+ switch(result) {
+ case CMP_RESULT_CODE_CONNECTABLE:
+ case CMP_RESULT_CODE_NO_CONNECTION:
+ case CMP_RESULT_CODE_NO_DNS:
+ case CMP_RESULT_CODE_NO_TCP_CONNECTION: {
+ if (DBG) log("CheckMp.onComplete: ignore, connected or no connection");
+ break;
+ }
+ case CMP_RESULT_CODE_REDIRECTED: {
+ if (DBG) log("CheckMp.onComplete: warm sim");
+ String url = getMobileProvisioningUrl();
+ if (TextUtils.isEmpty(url)) {
+ url = getMobileRedirectedProvisioningUrl();
+ }
+ if (TextUtils.isEmpty(url) == false) {
+ if (DBG) log("CheckMp.onComplete: warm (redirected), url=" + url);
+ setProvNotificationVisible(true,
+ ConnectivityManager.TYPE_MOBILE_HIPRI, ni.getExtraInfo(),
+ url);
+ } else {
+ if (DBG) log("CheckMp.onComplete: warm (redirected), no url");
+ }
+ break;
+ }
+ case CMP_RESULT_CODE_PROVISIONING_NETWORK: {
+ String url = getMobileProvisioningUrl();
+ if (TextUtils.isEmpty(url) == false) {
+ if (DBG) log("CheckMp.onComplete: warm (no dns/tcp), url=" + url);
+ setProvNotificationVisible(true,
+ ConnectivityManager.TYPE_MOBILE_HIPRI, ni.getExtraInfo(),
+ url);
+ // Mark that we've got a provisioning network and
+ // Disable Mobile Data until user actually starts provisioning.
+ mIsProvisioningNetwork.set(true);
+ MobileDataStateTracker mdst = (MobileDataStateTracker)
+ mNetTrackers[ConnectivityManager.TYPE_MOBILE];
+
+ // Disable radio until user starts provisioning
+ mdst.setRadio(false);
+ } else {
+ if (DBG) log("CheckMp.onComplete: warm (no dns/tcp), no url");
+ }
+ break;
+ }
+ case CMP_RESULT_CODE_IS_PROVISIONING: {
+ // FIXME: Need to know when provisioning is done. Probably we can
+ // check the completion status if successful we're done if we
+ // "timedout" or still connected to provisioning APN turn off data?
+ if (DBG) log("CheckMp.onComplete: provisioning started");
+ mIsStartingProvisioning.set(false);
+ break;
+ }
+ default: {
+ loge("CheckMp.onComplete: ignore unexpected result=" + result);
+ break;
+ }
+ }
+ mIsCheckingMobileProvisioning.set(false);
+ }
+ };
+ CheckMp.Params params =
+ new CheckMp.Params(checkMp.getDefaultUrl(), timeOutMs, cb);
+ if (DBG) log("checkMobileProvisioning: params=" + params);
+ // TODO: Reenable when calls to the now defunct
+ // MobileDataStateTracker.isProvisioningNetwork() are removed.
+ // This code should be moved to the Telephony code.
+ // checkMp.execute(params);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ if (DBG) log("checkMobileProvisioning: X");
+ }
+ return timeOutMs;
+ }
+
+ static class CheckMp extends
+ AsyncTask<CheckMp.Params, Void, Integer> {
+ private static final String CHECKMP_TAG = "CheckMp";
+
+ // adb shell setprop persist.checkmp.testfailures 1 to enable testing failures
+ private static boolean mTestingFailures;
+
+ // Choosing 4 loops as half of them will use HTTPS and the other half HTTP
+ private static final int MAX_LOOPS = 4;
+
+ // Number of milli-seconds to complete all of the retires
+ public static final int MAX_TIMEOUT_MS = 60000;
+
+ // The socket should retry only 5 seconds, the default is longer
+ private static final int SOCKET_TIMEOUT_MS = 5000;
+
+ // Sleep time for network errors
+ private static final int NET_ERROR_SLEEP_SEC = 3;
+
+ // Sleep time for network route establishment
+ private static final int NET_ROUTE_ESTABLISHMENT_SLEEP_SEC = 3;
+
+ // Short sleep time for polling :(
+ private static final int POLLING_SLEEP_SEC = 1;
+
+ private Context mContext;
+ private ConnectivityService mCs;
+ private TelephonyManager mTm;
+ private Params mParams;
+
+ /**
+ * Parameters for AsyncTask.execute
+ */
+ static class Params {
+ private String mUrl;
+ private long mTimeOutMs;
+ private CallBack mCb;
+
+ Params(String url, long timeOutMs, CallBack cb) {
+ mUrl = url;
+ mTimeOutMs = timeOutMs;
+ mCb = cb;
+ }
+
+ @Override
+ public String toString() {
+ return "{" + " url=" + mUrl + " mTimeOutMs=" + mTimeOutMs + " mCb=" + mCb + "}";
+ }
+ }
+
+ // As explained to me by Brian Carlstrom and Kenny Root, Certificates can be
+ // issued by name or ip address, for Google its by name so when we construct
+ // this HostnameVerifier we'll pass the original Uri and use it to verify
+ // the host. If the host name in the original uril fails we'll test the
+ // hostname parameter just incase things change.
+ static class CheckMpHostnameVerifier implements HostnameVerifier {
+ Uri mOrgUri;
+
+ CheckMpHostnameVerifier(Uri orgUri) {
+ mOrgUri = orgUri;
+ }
+
+ @Override
+ public boolean verify(String hostname, SSLSession session) {
+ HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier();
+ String orgUriHost = mOrgUri.getHost();
+ boolean retVal = hv.verify(orgUriHost, session) || hv.verify(hostname, session);
+ if (DBG) {
+ log("isMobileOk: hostnameVerify retVal=" + retVal + " hostname=" + hostname
+ + " orgUriHost=" + orgUriHost);
+ }
+ return retVal;
+ }
+ }
+
+ /**
+ * The call back object passed in Params. onComplete will be called
+ * on the main thread.
+ */
+ abstract static class CallBack {
+ // Called on the main thread.
+ abstract void onComplete(Integer result);
+ }
+
+ public CheckMp(Context context, ConnectivityService cs) {
+ if (Build.IS_DEBUGGABLE) {
+ mTestingFailures =
+ SystemProperties.getInt("persist.checkmp.testfailures", 0) == 1;
+ } else {
+ mTestingFailures = false;
+ }
+
+ mContext = context;
+ mCs = cs;
+
+ // Setup access to TelephonyService we'll be using.
+ mTm = (TelephonyManager) mContext.getSystemService(
+ Context.TELEPHONY_SERVICE);
+ }
+
+ /**
+ * Get the default url to use for the test.
+ */
+ public String getDefaultUrl() {
+ // See http://go/clientsdns for usage approval
+ String server = Settings.Global.getString(mContext.getContentResolver(),
+ Settings.Global.CAPTIVE_PORTAL_SERVER);
+ if (server == null) {
+ server = "clients3.google.com";
+ }
+ return "http://" + server + "/generate_204";
+ }
+
+ /**
+ * Detect if its possible to connect to the http url. DNS based detection techniques
+ * do not work at all hotspots. The best way to check is to perform a request to
+ * a known address that fetches the data we expect.
+ */
+ private synchronized Integer isMobileOk(Params params) {
+ Integer result = CMP_RESULT_CODE_NO_CONNECTION;
+ Uri orgUri = Uri.parse(params.mUrl);
+ Random rand = new Random();
+ mParams = params;
+
+ if (mCs.isNetworkSupported(ConnectivityManager.TYPE_MOBILE) == false) {
+ result = CMP_RESULT_CODE_NO_CONNECTION;
+ log("isMobileOk: X not mobile capable result=" + result);
+ return result;
+ }
+
+ if (mCs.mIsStartingProvisioning.get()) {
+ result = CMP_RESULT_CODE_IS_PROVISIONING;
+ log("isMobileOk: X is provisioning result=" + result);
+ return result;
+ }
+
+ // See if we've already determined we've got a provisioning connection,
+ // if so we don't need to do anything active.
+ MobileDataStateTracker mdstDefault = (MobileDataStateTracker)
+ mCs.mNetTrackers[ConnectivityManager.TYPE_MOBILE];
+ boolean isDefaultProvisioning = mdstDefault.isProvisioningNetwork();
+ log("isMobileOk: isDefaultProvisioning=" + isDefaultProvisioning);
+
+ MobileDataStateTracker mdstHipri = (MobileDataStateTracker)
+ mCs.mNetTrackers[ConnectivityManager.TYPE_MOBILE_HIPRI];
+ boolean isHipriProvisioning = mdstHipri.isProvisioningNetwork();
+ log("isMobileOk: isHipriProvisioning=" + isHipriProvisioning);
+
+ if (isDefaultProvisioning || isHipriProvisioning) {
+ result = CMP_RESULT_CODE_PROVISIONING_NETWORK;
+ log("isMobileOk: X default || hipri is provisioning result=" + result);
+ return result;
+ }
+
+ try {
+ // Continue trying to connect until time has run out
+ long endTime = SystemClock.elapsedRealtime() + params.mTimeOutMs;
+
+ if (!mCs.isMobileDataStateTrackerReady()) {
+ // Wait for MobileDataStateTracker to be ready.
+ if (DBG) log("isMobileOk: mdst is not ready");
+ while(SystemClock.elapsedRealtime() < endTime) {
+ if (mCs.isMobileDataStateTrackerReady()) {
+ // Enable fail fast as we'll do retries here and use a
+ // hipri connection so the default connection stays active.
+ if (DBG) log("isMobileOk: mdst ready, enable fail fast of mobile data");
+ mCs.setEnableFailFastMobileData(DctConstants.ENABLED);
+ break;
+ }
+ sleep(POLLING_SLEEP_SEC);
+ }
+ }
+
+ log("isMobileOk: start hipri url=" + params.mUrl);
+
+ // First wait until we can start using hipri
+ Binder binder = new Binder();
+ while(SystemClock.elapsedRealtime() < endTime) {
+ int ret = mCs.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE,
+ Phone.FEATURE_ENABLE_HIPRI, binder);
+ if ((ret == PhoneConstants.APN_ALREADY_ACTIVE)
+ || (ret == PhoneConstants.APN_REQUEST_STARTED)) {
+ log("isMobileOk: hipri started");
+ break;
+ }
+ if (VDBG) log("isMobileOk: hipri not started yet");
+ result = CMP_RESULT_CODE_NO_CONNECTION;
+ sleep(POLLING_SLEEP_SEC);
+ }
+
+ // Continue trying to connect until time has run out
+ while(SystemClock.elapsedRealtime() < endTime) {
+ try {
+ // Wait for hipri to connect.
+ // TODO: Don't poll and handle situation where hipri fails
+ // because default is retrying. See b/9569540
+ NetworkInfo.State state = mCs
+ .getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI).getState();
+ if (state != NetworkInfo.State.CONNECTED) {
+ if (true/*VDBG*/) {
+ log("isMobileOk: not connected ni=" +
+ mCs.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI));
+ }
+ sleep(POLLING_SLEEP_SEC);
+ result = CMP_RESULT_CODE_NO_CONNECTION;
+ continue;
+ }
+
+ // Hipri has started check if this is a provisioning url
+ MobileDataStateTracker mdst = (MobileDataStateTracker)
+ mCs.mNetTrackers[ConnectivityManager.TYPE_MOBILE_HIPRI];
+ if (mdst.isProvisioningNetwork()) {
+ result = CMP_RESULT_CODE_PROVISIONING_NETWORK;
+ if (DBG) log("isMobileOk: X isProvisioningNetwork result=" + result);
+ return result;
+ } else {
+ if (DBG) log("isMobileOk: isProvisioningNetwork is false, continue");
+ }
+
+ // Get of the addresses associated with the url host. We need to use the
+ // address otherwise HttpURLConnection object will use the name to get
+ // the addresses and will try every address but that will bypass the
+ // route to host we setup and the connection could succeed as the default
+ // interface might be connected to the internet via wifi or other interface.
+ InetAddress[] addresses;
+ try {
+ addresses = InetAddress.getAllByName(orgUri.getHost());
+ } catch (UnknownHostException e) {
+ result = CMP_RESULT_CODE_NO_DNS;
+ log("isMobileOk: X UnknownHostException result=" + result);
+ return result;
+ }
+ log("isMobileOk: addresses=" + inetAddressesToString(addresses));
+
+ // Get the type of addresses supported by this link
+ LinkProperties lp = mCs.getLinkPropertiesForTypeInternal(
+ ConnectivityManager.TYPE_MOBILE_HIPRI);
+ boolean linkHasIpv4 = lp.hasIPv4Address();
+ boolean linkHasIpv6 = lp.hasIPv6Address();
+ log("isMobileOk: linkHasIpv4=" + linkHasIpv4
+ + " linkHasIpv6=" + linkHasIpv6);
+
+ final ArrayList<InetAddress> validAddresses =
+ new ArrayList<InetAddress>(addresses.length);
+
+ for (InetAddress addr : addresses) {
+ if (((addr instanceof Inet4Address) && linkHasIpv4) ||
+ ((addr instanceof Inet6Address) && linkHasIpv6)) {
+ validAddresses.add(addr);
+ }
+ }
+
+ if (validAddresses.size() == 0) {
+ return CMP_RESULT_CODE_NO_CONNECTION;
+ }
+
+ int addrTried = 0;
+ while (true) {
+ // Loop through at most MAX_LOOPS valid addresses or until
+ // we run out of time
+ if (addrTried++ >= MAX_LOOPS) {
+ log("isMobileOk: too many loops tried - giving up");
+ break;
+ }
+ if (SystemClock.elapsedRealtime() >= endTime) {
+ log("isMobileOk: spend too much time - giving up");
+ break;
+ }
+
+ InetAddress hostAddr = validAddresses.get(rand.nextInt(
+ validAddresses.size()));
+
+ // Make a route to host so we check the specific interface.
+ if (mCs.requestRouteToHostAddress(ConnectivityManager.TYPE_MOBILE_HIPRI,
+ hostAddr.getAddress(), null)) {
+ // Wait a short time to be sure the route is established ??
+ log("isMobileOk:"
+ + " wait to establish route to hostAddr=" + hostAddr);
+ sleep(NET_ROUTE_ESTABLISHMENT_SLEEP_SEC);
+ } else {
+ log("isMobileOk:"
+ + " could not establish route to hostAddr=" + hostAddr);
+ // Wait a short time before the next attempt
+ sleep(NET_ERROR_SLEEP_SEC);
+ continue;
+ }
+
+ // Rewrite the url to have numeric address to use the specific route
+ // using http for half the attempts and https for the other half.
+ // Doing https first and http second as on a redirected walled garden
+ // such as t-mobile uses we get a SocketTimeoutException: "SSL
+ // handshake timed out" which we declare as
+ // CMP_RESULT_CODE_NO_TCP_CONNECTION. We could change this, but by
+ // having http second we will be using logic used for some time.
+ URL newUrl;
+ String scheme = (addrTried <= (MAX_LOOPS/2)) ? "https" : "http";
+ newUrl = new URL(scheme, hostAddr.getHostAddress(),
+ orgUri.getPath());
+ log("isMobileOk: newUrl=" + newUrl);
+
+ HttpURLConnection urlConn = null;
+ try {
+ // Open the connection set the request headers and get the response
+ urlConn = (HttpURLConnection)newUrl.openConnection(
+ java.net.Proxy.NO_PROXY);
+ if (scheme.equals("https")) {
+ ((HttpsURLConnection)urlConn).setHostnameVerifier(
+ new CheckMpHostnameVerifier(orgUri));
+ }
+ urlConn.setInstanceFollowRedirects(false);
+ urlConn.setConnectTimeout(SOCKET_TIMEOUT_MS);
+ urlConn.setReadTimeout(SOCKET_TIMEOUT_MS);
+ urlConn.setUseCaches(false);
+ urlConn.setAllowUserInteraction(false);
+ // Set the "Connection" to "Close" as by default "Keep-Alive"
+ // is used which is useless in this case.
+ urlConn.setRequestProperty("Connection", "close");
+ int responseCode = urlConn.getResponseCode();
+
+ // For debug display the headers
+ Map<String, List<String>> headers = urlConn.getHeaderFields();
+ log("isMobileOk: headers=" + headers);
+
+ // Close the connection
+ urlConn.disconnect();
+ urlConn = null;
+
+ if (mTestingFailures) {
+ // Pretend no connection, this tests using http and https
+ result = CMP_RESULT_CODE_NO_CONNECTION;
+ log("isMobileOk: TESTING_FAILURES, pretend no connction");
+ continue;
+ }
+
+ if (responseCode == 204) {
+ // Return
+ result = CMP_RESULT_CODE_CONNECTABLE;
+ log("isMobileOk: X got expected responseCode=" + responseCode
+ + " result=" + result);
+ return result;
+ } else {
+ // Retry to be sure this was redirected, we've gotten
+ // occasions where a server returned 200 even though
+ // the device didn't have a "warm" sim.
+ log("isMobileOk: not expected responseCode=" + responseCode);
+ // TODO - it would be nice in the single-address case to do
+ // another DNS resolve here, but flushing the cache is a bit
+ // heavy-handed.
+ result = CMP_RESULT_CODE_REDIRECTED;
+ }
+ } catch (Exception e) {
+ log("isMobileOk: HttpURLConnection Exception" + e);
+ result = CMP_RESULT_CODE_NO_TCP_CONNECTION;
+ if (urlConn != null) {
+ urlConn.disconnect();
+ urlConn = null;
+ }
+ sleep(NET_ERROR_SLEEP_SEC);
+ continue;
+ }
+ }
+ log("isMobileOk: X loops|timed out result=" + result);
+ return result;
+ } catch (Exception e) {
+ log("isMobileOk: Exception e=" + e);
+ continue;
+ }
+ }
+ log("isMobileOk: timed out");
+ } finally {
+ log("isMobileOk: F stop hipri");
+ mCs.setEnableFailFastMobileData(DctConstants.DISABLED);
+ mCs.stopUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE,
+ Phone.FEATURE_ENABLE_HIPRI);
+
+ // Wait for hipri to disconnect.
+ long endTime = SystemClock.elapsedRealtime() + 5000;
+
+ while(SystemClock.elapsedRealtime() < endTime) {
+ NetworkInfo.State state = mCs
+ .getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI).getState();
+ if (state != NetworkInfo.State.DISCONNECTED) {
+ if (VDBG) {
+ log("isMobileOk: connected ni=" +
+ mCs.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI));
+ }
+ sleep(POLLING_SLEEP_SEC);
+ continue;
+ }
+ }
+
+ log("isMobileOk: X result=" + result);
+ }
+ return result;
+ }
+
+ @Override
+ protected Integer doInBackground(Params... params) {
+ return isMobileOk(params[0]);
+ }
+
+ @Override
+ protected void onPostExecute(Integer result) {
+ log("onPostExecute: result=" + result);
+ if ((mParams != null) && (mParams.mCb != null)) {
+ mParams.mCb.onComplete(result);
+ }
+ }
+
+ private String inetAddressesToString(InetAddress[] addresses) {
+ StringBuffer sb = new StringBuffer();
+ boolean firstTime = true;
+ for(InetAddress addr : addresses) {
+ if (firstTime) {
+ firstTime = false;
+ } else {
+ sb.append(",");
+ }
+ sb.append(addr);
+ }
+ return sb.toString();
+ }
+
+ private void printNetworkInfo() {
+ boolean hasIccCard = mTm.hasIccCard();
+ int simState = mTm.getSimState();
+ log("hasIccCard=" + hasIccCard
+ + " simState=" + simState);
+ NetworkInfo[] ni = mCs.getAllNetworkInfo();
+ if (ni != null) {
+ log("ni.length=" + ni.length);
+ for (NetworkInfo netInfo: ni) {
+ log("netInfo=" + netInfo.toString());
+ }
+ } else {
+ log("no network info ni=null");
+ }
+ }
+
+ /**
+ * Sleep for a few seconds then return.
+ * @param seconds
+ */
+ private static void sleep(int seconds) {
+ long stopTime = System.nanoTime() + (seconds * 1000000000);
+ long sleepTime;
+ while ((sleepTime = stopTime - System.nanoTime()) > 0) {
+ try {
+ Thread.sleep(sleepTime / 1000000);
+ } catch (InterruptedException ignored) {
+ }
+ }
+ }
+
+ private static void log(String s) {
+ Slog.d(ConnectivityService.TAG, "[" + CHECKMP_TAG + "] " + s);
+ }
+ }
+
+ // TODO: Move to ConnectivityManager and make public?
+ private static final String CONNECTED_TO_PROVISIONING_NETWORK_ACTION =
+ "com.android.server.connectivityservice.CONNECTED_TO_PROVISIONING_NETWORK_ACTION";
+
+ private BroadcastReceiver mProvisioningReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(CONNECTED_TO_PROVISIONING_NETWORK_ACTION)) {
+ handleMobileProvisioningAction(intent.getStringExtra("EXTRA_URL"));
+ }
+ }
+ };
+
+ private void handleMobileProvisioningAction(String url) {
+ // Mark notification as not visible
+ setProvNotificationVisible(false, ConnectivityManager.TYPE_MOBILE_HIPRI, null, null);
+
+ // Check airplane mode
+ boolean isAirplaneModeOn = Settings.System.getInt(mContext.getContentResolver(),
+ Settings.Global.AIRPLANE_MODE_ON, 0) == 1;
+ // If provisioning network and not in airplane mode handle as a special case,
+ // otherwise launch browser with the intent directly.
+ if (mIsProvisioningNetwork.get() && !isAirplaneModeOn) {
+ if (DBG) log("handleMobileProvisioningAction: on prov network enable then launch");
+ mIsProvisioningNetwork.set(false);
+// mIsStartingProvisioning.set(true);
+// MobileDataStateTracker mdst = (MobileDataStateTracker)
+// mNetTrackers[ConnectivityManager.TYPE_MOBILE];
+ // Radio was disabled on CMP_RESULT_CODE_PROVISIONING_NETWORK, enable it here
+// mdst.setRadio(true);
+// mdst.setEnableFailFastMobileData(DctConstants.ENABLED);
+// mdst.enableMobileProvisioning(url);
+ } else {
+ if (DBG) log("handleMobileProvisioningAction: not prov network");
+ mIsProvisioningNetwork.set(false);
+ // Check for apps that can handle provisioning first
+ Intent provisioningIntent = new Intent(TelephonyIntents.ACTION_CARRIER_SETUP);
+ provisioningIntent.addCategory(TelephonyIntents.CATEGORY_MCCMNC_PREFIX
+ + mTelephonyManager.getSimOperator());
+ if (mContext.getPackageManager().resolveActivity(provisioningIntent, 0 /* flags */)
+ != null) {
+ provisioningIntent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
+ Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(provisioningIntent);
+ } else {
+ // If no apps exist, use standard URL ACTION_VIEW method
+ Intent newIntent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN,
+ Intent.CATEGORY_APP_BROWSER);
+ newIntent.setData(Uri.parse(url));
+ newIntent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
+ Intent.FLAG_ACTIVITY_NEW_TASK);
+ try {
+ mContext.startActivity(newIntent);
+ } catch (ActivityNotFoundException e) {
+ loge("handleMobileProvisioningAction: startActivity failed" + e);
+ }
+ }
+ }
+ }
+
+ private static final String NOTIFICATION_ID = "CaptivePortal.Notification";
+ private volatile boolean mIsNotificationVisible = false;
+
+ private void setProvNotificationVisible(boolean visible, int networkType, String extraInfo,
+ String url) {
+ if (DBG) {
+ log("setProvNotificationVisible: E visible=" + visible + " networkType=" + networkType
+ + " extraInfo=" + extraInfo + " url=" + url);
+ }
+
+ Resources r = Resources.getSystem();
+ NotificationManager notificationManager = (NotificationManager) mContext
+ .getSystemService(Context.NOTIFICATION_SERVICE);
+
+ if (visible) {
+ CharSequence title;
+ CharSequence details;
+ int icon;
+ Intent intent;
+ Notification notification = new Notification();
+ switch (networkType) {
+ case ConnectivityManager.TYPE_WIFI:
+ title = r.getString(R.string.wifi_available_sign_in, 0);
+ details = r.getString(R.string.network_available_sign_in_detailed,
+ extraInfo);
+ icon = R.drawable.stat_notify_wifi_in_range;
+ intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
+ intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
+ Intent.FLAG_ACTIVITY_NEW_TASK);
+ notification.contentIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+ break;
+ case ConnectivityManager.TYPE_MOBILE:
+ case ConnectivityManager.TYPE_MOBILE_HIPRI:
+ title = r.getString(R.string.network_available_sign_in, 0);
+ // TODO: Change this to pull from NetworkInfo once a printable
+ // name has been added to it
+ details = mTelephonyManager.getNetworkOperatorName();
+ icon = R.drawable.stat_notify_rssi_in_range;
+ intent = new Intent(CONNECTED_TO_PROVISIONING_NETWORK_ACTION);
+ intent.putExtra("EXTRA_URL", url);
+ intent.setFlags(0);
+ notification.contentIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+ break;
+ default:
+ title = r.getString(R.string.network_available_sign_in, 0);
+ details = r.getString(R.string.network_available_sign_in_detailed,
+ extraInfo);
+ icon = R.drawable.stat_notify_rssi_in_range;
+ intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
+ intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
+ Intent.FLAG_ACTIVITY_NEW_TASK);
+ notification.contentIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+ break;
+ }
+
+ notification.when = 0;
+ notification.icon = icon;
+ notification.flags = Notification.FLAG_AUTO_CANCEL;
+ notification.tickerText = title;
+ notification.setLatestEventInfo(mContext, title, details, notification.contentIntent);
+
+ try {
+ notificationManager.notify(NOTIFICATION_ID, networkType, notification);
+ } catch (NullPointerException npe) {
+ loge("setNotificaitionVisible: visible notificationManager npe=" + npe);
+ npe.printStackTrace();
+ }
+ } else {
+ try {
+ notificationManager.cancel(NOTIFICATION_ID, networkType);
+ } catch (NullPointerException npe) {
+ loge("setNotificaitionVisible: cancel notificationManager npe=" + npe);
+ npe.printStackTrace();
+ }
+ }
+ mIsNotificationVisible = visible;
+ }
+
+ /** Location to an updatable file listing carrier provisioning urls.
+ * An example:
+ *
+ * <?xml version="1.0" encoding="utf-8"?>
+ * <provisioningUrls>
+ * <provisioningUrl mcc="310" mnc="4">http://myserver.com/foo?mdn=%3$s&iccid=%1$s&imei=%2$s</provisioningUrl>
+ * <redirectedUrl mcc="310" mnc="4">http://www.google.com</redirectedUrl>
+ * </provisioningUrls>
+ */
+ private static final String PROVISIONING_URL_PATH =
+ "/data/misc/radio/provisioning_urls.xml";
+ private final File mProvisioningUrlFile = new File(PROVISIONING_URL_PATH);
+
+ /** XML tag for root element. */
+ private static final String TAG_PROVISIONING_URLS = "provisioningUrls";
+ /** XML tag for individual url */
+ private static final String TAG_PROVISIONING_URL = "provisioningUrl";
+ /** XML tag for redirected url */
+ private static final String TAG_REDIRECTED_URL = "redirectedUrl";
+ /** XML attribute for mcc */
+ private static final String ATTR_MCC = "mcc";
+ /** XML attribute for mnc */
+ private static final String ATTR_MNC = "mnc";
+
+ private static final int REDIRECTED_PROVISIONING = 1;
+ private static final int PROVISIONING = 2;
+
+ private String getProvisioningUrlBaseFromFile(int type) {
+ FileReader fileReader = null;
+ XmlPullParser parser = null;
+ Configuration config = mContext.getResources().getConfiguration();
+ String tagType;
+
+ switch (type) {
+ case PROVISIONING:
+ tagType = TAG_PROVISIONING_URL;
+ break;
+ case REDIRECTED_PROVISIONING:
+ tagType = TAG_REDIRECTED_URL;
+ break;
+ default:
+ throw new RuntimeException("getProvisioningUrlBaseFromFile: Unexpected parameter " +
+ type);
+ }
+
+ try {
+ fileReader = new FileReader(mProvisioningUrlFile);
+ parser = Xml.newPullParser();
+ parser.setInput(fileReader);
+ XmlUtils.beginDocument(parser, TAG_PROVISIONING_URLS);
+
+ while (true) {
+ XmlUtils.nextElement(parser);
+
+ String element = parser.getName();
+ if (element == null) break;
+
+ if (element.equals(tagType)) {
+ String mcc = parser.getAttributeValue(null, ATTR_MCC);
+ try {
+ if (mcc != null && Integer.parseInt(mcc) == config.mcc) {
+ String mnc = parser.getAttributeValue(null, ATTR_MNC);
+ if (mnc != null && Integer.parseInt(mnc) == config.mnc) {
+ parser.next();
+ if (parser.getEventType() == XmlPullParser.TEXT) {
+ return parser.getText();
+ }
+ }
+ }
+ } catch (NumberFormatException e) {
+ loge("NumberFormatException in getProvisioningUrlBaseFromFile: " + e);
+ }
+ }
+ }
+ return null;
+ } catch (FileNotFoundException e) {
+ loge("Carrier Provisioning Urls file not found");
+ } catch (XmlPullParserException e) {
+ loge("Xml parser exception reading Carrier Provisioning Urls file: " + e);
+ } catch (IOException e) {
+ loge("I/O exception reading Carrier Provisioning Urls file: " + e);
+ } finally {
+ if (fileReader != null) {
+ try {
+ fileReader.close();
+ } catch (IOException e) {}
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String getMobileRedirectedProvisioningUrl() {
+ enforceConnectivityInternalPermission();
+ String url = getProvisioningUrlBaseFromFile(REDIRECTED_PROVISIONING);
+ if (TextUtils.isEmpty(url)) {
+ url = mContext.getResources().getString(R.string.mobile_redirected_provisioning_url);
+ }
+ return url;
+ }
+
+ @Override
+ public String getMobileProvisioningUrl() {
+ enforceConnectivityInternalPermission();
+ String url = getProvisioningUrlBaseFromFile(PROVISIONING);
+ if (TextUtils.isEmpty(url)) {
+ url = mContext.getResources().getString(R.string.mobile_provisioning_url);
+ log("getMobileProvisioningUrl: mobile_provisioining_url from resource =" + url);
+ } else {
+ log("getMobileProvisioningUrl: mobile_provisioning_url from File =" + url);
+ }
+ // populate the iccid, imei and phone number in the provisioning url.
+ if (!TextUtils.isEmpty(url)) {
+ String phoneNumber = mTelephonyManager.getLine1Number();
+ if (TextUtils.isEmpty(phoneNumber)) {
+ phoneNumber = "0000000000";
+ }
+ url = String.format(url,
+ mTelephonyManager.getSimSerialNumber() /* ICCID */,
+ mTelephonyManager.getDeviceId() /* IMEI */,
+ phoneNumber /* Phone numer */);
+ }
+
+ return url;
+ }
+
+ @Override
+ public void setProvisioningNotificationVisible(boolean visible, int networkType,
+ String extraInfo, String url) {
+ enforceConnectivityInternalPermission();
+ setProvNotificationVisible(visible, networkType, extraInfo, url);
+ }
+
+ @Override
+ public void setAirplaneMode(boolean enable) {
+ enforceConnectivityInternalPermission();
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ final ContentResolver cr = mContext.getContentResolver();
+ Settings.Global.putInt(cr, Settings.Global.AIRPLANE_MODE_ON, enable ? 1 : 0);
+ Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ intent.putExtra("state", enable);
+ mContext.sendBroadcast(intent);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ private void onUserStart(int userId) {
+ synchronized(mVpns) {
+ Vpn userVpn = mVpns.get(userId);
+ if (userVpn != null) {
+ loge("Starting user already has a VPN");
+ return;
+ }
+ userVpn = new Vpn(mContext, mVpnCallback, mNetd, this, userId);
+ mVpns.put(userId, userVpn);
+ userVpn.startMonitoring(mContext, mTrackerHandler);
+ }
+ }
+
+ private void onUserStop(int userId) {
+ synchronized(mVpns) {
+ Vpn userVpn = mVpns.get(userId);
+ if (userVpn == null) {
+ loge("Stopping user has no VPN");
+ return;
+ }
+ mVpns.delete(userId);
+ }
+ }
+
+ private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+ if (userId == UserHandle.USER_NULL) return;
+
+ if (Intent.ACTION_USER_STARTING.equals(action)) {
+ onUserStart(userId);
+ } else if (Intent.ACTION_USER_STOPPING.equals(action)) {
+ onUserStop(userId);
+ }
+ }
+ };
+
+ @Override
+ public LinkQualityInfo getLinkQualityInfo(int networkType) {
+ enforceAccessPermission();
+ if (isNetworkTypeValid(networkType) && mNetTrackers[networkType] != null) {
+ return mNetTrackers[networkType].getLinkQualityInfo();
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public LinkQualityInfo getActiveLinkQualityInfo() {
+ enforceAccessPermission();
+ if (isNetworkTypeValid(mActiveDefaultNetwork) &&
+ mNetTrackers[mActiveDefaultNetwork] != null) {
+ return mNetTrackers[mActiveDefaultNetwork].getLinkQualityInfo();
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public LinkQualityInfo[] getAllLinkQualityInfo() {
+ enforceAccessPermission();
+ final ArrayList<LinkQualityInfo> result = Lists.newArrayList();
+ for (NetworkStateTracker tracker : mNetTrackers) {
+ if (tracker != null) {
+ LinkQualityInfo li = tracker.getLinkQualityInfo();
+ if (li != null) {
+ result.add(li);
+ }
+ }
+ }
+
+ return result.toArray(new LinkQualityInfo[result.size()]);
+ }
+
+ /* Infrastructure for network sampling */
+
+ private void handleNetworkSamplingTimeout() {
+
+ log("Sampling interval elapsed, updating statistics ..");
+
+ // initialize list of interfaces ..
+ Map<String, SamplingDataTracker.SamplingSnapshot> mapIfaceToSample =
+ new HashMap<String, SamplingDataTracker.SamplingSnapshot>();
+ for (NetworkStateTracker tracker : mNetTrackers) {
+ if (tracker != null) {
+ String ifaceName = tracker.getNetworkInterfaceName();
+ if (ifaceName != null) {
+ mapIfaceToSample.put(ifaceName, null);
+ }
+ }
+ }
+
+ // Read samples for all interfaces
+ SamplingDataTracker.getSamplingSnapshots(mapIfaceToSample);
+
+ // process samples for all networks
+ for (NetworkStateTracker tracker : mNetTrackers) {
+ if (tracker != null) {
+ String ifaceName = tracker.getNetworkInterfaceName();
+ SamplingDataTracker.SamplingSnapshot ss = mapIfaceToSample.get(ifaceName);
+ if (ss != null) {
+ // end the previous sampling cycle
+ tracker.stopSampling(ss);
+ // start a new sampling cycle ..
+ tracker.startSampling(ss);
+ }
+ }
+ }
+
+ log("Done.");
+
+ int samplingIntervalInSeconds = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.CONNECTIVITY_SAMPLING_INTERVAL_IN_SECONDS,
+ DEFAULT_SAMPLING_INTERVAL_IN_SECONDS);
+
+ if (DBG) log("Setting timer for " + String.valueOf(samplingIntervalInSeconds) + "seconds");
+
+ setAlarm(samplingIntervalInSeconds * 1000, mSampleIntervalElapsedIntent);
+ }
+
+ void setAlarm(int timeoutInMilliseconds, PendingIntent intent) {
+ long wakeupTime = SystemClock.elapsedRealtime() + timeoutInMilliseconds;
+ mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, wakeupTime, intent);
+ }
+
+ private final HashMap<Messenger, NetworkFactoryInfo> mNetworkFactoryInfos =
+ new HashMap<Messenger, NetworkFactoryInfo>();
+ private final HashMap<NetworkRequest, NetworkRequestInfo> mNetworkRequests =
+ new HashMap<NetworkRequest, NetworkRequestInfo>();
+
+ private static class NetworkFactoryInfo {
+ public final String name;
+ public final Messenger messenger;
+ public final AsyncChannel asyncChannel;
+
+ public NetworkFactoryInfo(String name, Messenger messenger, AsyncChannel asyncChannel) {
+ this.name = name;
+ this.messenger = messenger;
+ this.asyncChannel = asyncChannel;
+ }
+ }
+
+ private class NetworkRequestInfo implements IBinder.DeathRecipient {
+ static final boolean REQUEST = true;
+ static final boolean LISTEN = false;
+
+ final NetworkRequest request;
+ IBinder mBinder;
+ final int mPid;
+ final int mUid;
+ final Messenger messenger;
+ final boolean isRequest;
+
+ NetworkRequestInfo(Messenger m, NetworkRequest r, IBinder binder, boolean isRequest) {
+ super();
+ messenger = m;
+ request = r;
+ mBinder = binder;
+ mPid = getCallingPid();
+ mUid = getCallingUid();
+ this.isRequest = isRequest;
+
+ try {
+ mBinder.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ binderDied();
+ }
+ }
+
+ void unlinkDeathRecipient() {
+ mBinder.unlinkToDeath(this, 0);
+ }
+
+ public void binderDied() {
+ log("ConnectivityService NetworkRequestInfo binderDied(" +
+ request + ", " + mBinder + ")");
+ releaseNetworkRequest(request);
+ }
+
+ public String toString() {
+ return (isRequest ? "Request" : "Listen") + " from uid/pid:" + mUid + "/" +
+ mPid + " for " + request;
+ }
+ }
+
+ @Override
+ public NetworkRequest requestNetwork(NetworkCapabilities networkCapabilities,
+ Messenger messenger, int timeoutSec, IBinder binder, int legacyType) {
+ if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+ == false) {
+ enforceConnectivityInternalPermission();
+ } else {
+ enforceChangePermission();
+ }
+
+ if (timeoutSec < 0 || timeoutSec > ConnectivityManager.MAX_NETWORK_REQUEST_TIMEOUT_SEC) {
+ throw new IllegalArgumentException("Bad timeout specified");
+ }
+ NetworkRequest networkRequest = new NetworkRequest(new NetworkCapabilities(
+ networkCapabilities), legacyType, nextNetworkRequestId());
+ if (DBG) log("requestNetwork for " + networkRequest);
+ NetworkRequestInfo nri = new NetworkRequestInfo(messenger, networkRequest, binder,
+ NetworkRequestInfo.REQUEST);
+
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_REQUEST, nri));
+ if (timeoutSec > 0) {
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_TIMEOUT_NETWORK_REQUEST,
+ nri), timeoutSec * 1000);
+ }
+ return networkRequest;
+ }
+
+ @Override
+ public NetworkRequest pendingRequestForNetwork(NetworkCapabilities networkCapabilities,
+ PendingIntent operation) {
+ // TODO
+ return null;
+ }
+
+ @Override
+ public NetworkRequest listenForNetwork(NetworkCapabilities networkCapabilities,
+ Messenger messenger, IBinder binder) {
+ enforceAccessPermission();
+
+ NetworkRequest networkRequest = new NetworkRequest(new NetworkCapabilities(
+ networkCapabilities), TYPE_NONE, nextNetworkRequestId());
+ if (DBG) log("listenForNetwork for " + networkRequest);
+ NetworkRequestInfo nri = new NetworkRequestInfo(messenger, networkRequest, binder,
+ NetworkRequestInfo.LISTEN);
+
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_LISTENER, nri));
+ return networkRequest;
+ }
+
+ @Override
+ public void pendingListenForNetwork(NetworkCapabilities networkCapabilities,
+ PendingIntent operation) {
+ }
+
+ @Override
+ public void releaseNetworkRequest(NetworkRequest networkRequest) {
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_RELEASE_NETWORK_REQUEST,
+ networkRequest));
+ }
+
+ @Override
+ public void registerNetworkFactory(Messenger messenger, String name) {
+ enforceConnectivityInternalPermission();
+ NetworkFactoryInfo nfi = new NetworkFactoryInfo(name, messenger, new AsyncChannel());
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_FACTORY, nfi));
+ }
+
+ private void handleRegisterNetworkFactory(NetworkFactoryInfo nfi) {
+ if (VDBG) log("Got NetworkFactory Messenger for " + nfi.name);
+ mNetworkFactoryInfos.put(nfi.messenger, nfi);
+ nfi.asyncChannel.connect(mContext, mTrackerHandler, nfi.messenger);
+ }
+
+ @Override
+ public void unregisterNetworkFactory(Messenger messenger) {
+ enforceConnectivityInternalPermission();
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_NETWORK_FACTORY, messenger));
+ }
+
+ private void handleUnregisterNetworkFactory(Messenger messenger) {
+ NetworkFactoryInfo nfi = mNetworkFactoryInfos.remove(messenger);
+ if (nfi == null) {
+ if (VDBG) log("Failed to find Messenger in unregisterNetworkFactory");
+ return;
+ }
+ if (VDBG) log("unregisterNetworkFactory for " + nfi.name);
+ }
+
+ /**
+ * NetworkAgentInfo supporting a request by requestId.
+ * These have already been vetted (their Capabilities satisfy the request)
+ * and the are the highest scored network available.
+ * the are keyed off the Requests requestId.
+ */
+ private final SparseArray<NetworkAgentInfo> mNetworkForRequestId =
+ new SparseArray<NetworkAgentInfo>();
+
+ private final SparseArray<NetworkAgentInfo> mNetworkForNetId =
+ new SparseArray<NetworkAgentInfo>();
+
+ // NetworkAgentInfo keyed off its connecting messenger
+ // TODO - eval if we can reduce the number of lists/hashmaps/sparsearrays
+ private final HashMap<Messenger, NetworkAgentInfo> mNetworkAgentInfos =
+ new HashMap<Messenger, NetworkAgentInfo>();
+
+ private final NetworkRequest mDefaultRequest;
+
+ public void registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo,
+ LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
+ int currentScore) {
+ enforceConnectivityInternalPermission();
+
+ NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(), nextNetId(),
+ new NetworkInfo(networkInfo), new LinkProperties(linkProperties),
+ new NetworkCapabilities(networkCapabilities), currentScore, mContext, mTrackerHandler);
+ if (VDBG) log("registerNetworkAgent " + nai);
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_AGENT, nai));
+ }
+
+ private void handleRegisterNetworkAgent(NetworkAgentInfo na) {
+ if (VDBG) log("Got NetworkAgent Messenger");
+ mNetworkAgentInfos.put(na.messenger, na);
+ mNetworkForNetId.put(na.network.netId, na);
+ na.asyncChannel.connect(mContext, mTrackerHandler, na.messenger);
+ NetworkInfo networkInfo = na.networkInfo;
+ na.networkInfo = null;
+ updateNetworkInfo(na, networkInfo);
+ }
+
+ private void updateLinkProperties(NetworkAgentInfo networkAgent, LinkProperties oldLp) {
+ LinkProperties newLp = networkAgent.linkProperties;
+ int netId = networkAgent.network.netId;
+
+ updateInterfaces(newLp, oldLp, netId);
+ updateMtu(newLp, oldLp);
+ // TODO - figure out what to do for clat
+// for (LinkProperties lp : newLp.getStackedLinks()) {
+// updateMtu(lp, null);
+// }
+ updateRoutes(newLp, oldLp, netId);
+ updateDnses(newLp, oldLp, netId);
+ updateClat(newLp, oldLp, networkAgent);
+ }
+
+ private void updateClat(LinkProperties newLp, LinkProperties oldLp, NetworkAgentInfo na) {
+ // Update 464xlat state.
+ if (mClat.requiresClat(na)) {
+
+ // If the connection was previously using clat, but is not using it now, stop the clat
+ // daemon. Normally, this happens automatically when the connection disconnects, but if
+ // the disconnect is not reported, or if the connection's LinkProperties changed for
+ // some other reason (e.g., handoff changes the IP addresses on the link), it would
+ // still be running. If it's not running, then stopping it is a no-op.
+ if (Nat464Xlat.isRunningClat(oldLp) && !Nat464Xlat.isRunningClat(newLp)) {
+ mClat.stopClat();
+ }
+ // If the link requires clat to be running, then start the daemon now.
+ if (na.networkInfo.isConnected()) {
+ mClat.startClat(na);
+ } else {
+ mClat.stopClat();
+ }
+ }
+ }
+
+ private void updateInterfaces(LinkProperties newLp, LinkProperties oldLp, int netId) {
+ CompareResult<String> interfaceDiff = new CompareResult<String>();
+ if (oldLp != null) {
+ interfaceDiff = oldLp.compareAllInterfaceNames(newLp);
+ } else if (newLp != null) {
+ interfaceDiff.added = newLp.getAllInterfaceNames();
+ }
+ for (String iface : interfaceDiff.added) {
+ try {
+ mNetd.addInterfaceToNetwork(iface, netId);
+ } catch (Exception e) {
+ loge("Exception adding interface: " + e);
+ }
+ }
+ for (String iface : interfaceDiff.removed) {
+ try {
+ mNetd.removeInterfaceFromNetwork(iface, netId);
+ } catch (Exception e) {
+ loge("Exception removing interface: " + e);
+ }
+ }
+ }
+
+ private void updateRoutes(LinkProperties newLp, LinkProperties oldLp, int netId) {
+ CompareResult<RouteInfo> routeDiff = new CompareResult<RouteInfo>();
+ if (oldLp != null) {
+ routeDiff = oldLp.compareAllRoutes(newLp);
+ } else if (newLp != null) {
+ routeDiff.added = newLp.getAllRoutes();
+ }
+
+ // add routes before removing old in case it helps with continuous connectivity
+
+ // do this twice, adding non-nexthop routes first, then routes they are dependent on
+ for (RouteInfo route : routeDiff.added) {
+ if (route.hasGateway()) continue;
+ try {
+ mNetd.addRoute(netId, route);
+ } catch (Exception e) {
+ loge("Exception in addRoute for non-gateway: " + e);
+ }
+ }
+ for (RouteInfo route : routeDiff.added) {
+ if (route.hasGateway() == false) continue;
+ try {
+ mNetd.addRoute(netId, route);
+ } catch (Exception e) {
+ loge("Exception in addRoute for gateway: " + e);
+ }
+ }
+
+ for (RouteInfo route : routeDiff.removed) {
+ try {
+ mNetd.removeRoute(netId, route);
+ } catch (Exception e) {
+ loge("Exception in removeRoute: " + e);
+ }
+ }
+ }
+ private void updateDnses(LinkProperties newLp, LinkProperties oldLp, int netId) {
+ if (oldLp == null || (newLp.isIdenticalDnses(oldLp) == false)) {
+ Collection<InetAddress> dnses = newLp.getDnsServers();
+ if (dnses.size() == 0 && mDefaultDns != null) {
+ dnses = new ArrayList();
+ dnses.add(mDefaultDns);
+ if (DBG) {
+ loge("no dns provided for netId " + netId + ", so using defaults");
+ }
+ }
+ try {
+ mNetd.setDnsServersForNetwork(netId, NetworkUtils.makeStrings(dnses),
+ newLp.getDomains());
+ } catch (Exception e) {
+ loge("Exception in setDnsServersForNetwork: " + e);
+ }
+ NetworkAgentInfo defaultNai = mNetworkForRequestId.get(mDefaultRequest.requestId);
+ if (defaultNai != null && defaultNai.network.netId == netId) {
+ setDefaultDnsSystemProperties(dnses);
+ }
+ }
+ }
+
+ private void setDefaultDnsSystemProperties(Collection<InetAddress> dnses) {
+ int last = 0;
+ for (InetAddress dns : dnses) {
+ ++last;
+ String key = "net.dns" + last;
+ String value = dns.getHostAddress();
+ SystemProperties.set(key, value);
+ }
+ for (int i = last + 1; i <= mNumDnsEntries; ++i) {
+ String key = "net.dns" + i;
+ SystemProperties.set(key, "");
+ }
+ mNumDnsEntries = last;
+ }
+
+
+ private void updateCapabilities(NetworkAgentInfo networkAgent,
+ NetworkCapabilities networkCapabilities) {
+ // TODO - what else here? Verify still satisfies everybody?
+ // Check if satisfies somebody new? call callbacks?
+ networkAgent.networkCapabilities = networkCapabilities;
+ }
+
+ private void sendUpdatedScoreToFactories(NetworkRequest networkRequest, int score) {
+ if (VDBG) log("sending new Min Network Score(" + score + "): " + networkRequest.toString());
+ for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
+ nfi.asyncChannel.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK, score, 0,
+ networkRequest);
+ }
+ }
+
+ private void callCallbackForRequest(NetworkRequestInfo nri,
+ NetworkAgentInfo networkAgent, int notificationType) {
+ if (nri.messenger == null) return; // Default request has no msgr
+ Object o;
+ int a1 = 0;
+ int a2 = 0;
+ switch (notificationType) {
+ case ConnectivityManager.CALLBACK_LOSING:
+ a1 = 30; // TODO - read this from NetworkMonitor
+ // fall through
+ case ConnectivityManager.CALLBACK_PRECHECK:
+ case ConnectivityManager.CALLBACK_AVAILABLE:
+ case ConnectivityManager.CALLBACK_LOST:
+ case ConnectivityManager.CALLBACK_CAP_CHANGED:
+ case ConnectivityManager.CALLBACK_IP_CHANGED: {
+ o = new NetworkRequest(nri.request);
+ a2 = networkAgent.network.netId;
+ break;
+ }
+ case ConnectivityManager.CALLBACK_UNAVAIL:
+ case ConnectivityManager.CALLBACK_RELEASED: {
+ o = new NetworkRequest(nri.request);
+ break;
+ }
+ default: {
+ loge("Unknown notificationType " + notificationType);
+ return;
+ }
+ }
+ Message msg = Message.obtain();
+ msg.arg1 = a1;
+ msg.arg2 = a2;
+ msg.obj = o;
+ msg.what = notificationType;
+ try {
+ if (VDBG) log("sending notification " + notificationType + " for " + nri.request);
+ nri.messenger.send(msg);
+ } catch (RemoteException e) {
+ // may occur naturally in the race of binder death.
+ loge("RemoteException caught trying to send a callback msg for " + nri.request);
+ }
+ }
+
+ private void handleLingerComplete(NetworkAgentInfo oldNetwork) {
+ if (oldNetwork == null) {
+ loge("Unknown NetworkAgentInfo in handleLingerComplete");
+ return;
+ }
+ if (DBG) log("handleLingerComplete for " + oldNetwork.name());
+ if (DBG) {
+ if (oldNetwork.networkRequests.size() != 0) {
+ loge("Dead network still had " + oldNetwork.networkRequests.size() + " requests");
+ }
+ }
+ oldNetwork.asyncChannel.disconnect();
+ }
+
+ private void handleConnectionValidated(NetworkAgentInfo newNetwork) {
+ if (newNetwork == null) {
+ loge("Unknown NetworkAgentInfo in handleConnectionValidated");
+ return;
+ }
+ boolean keep = false;
+ boolean isNewDefault = false;
+ if (DBG) log("handleConnectionValidated for "+newNetwork.name());
+ // check if any NetworkRequest wants this NetworkAgent
+ ArrayList<NetworkAgentInfo> affectedNetworks = new ArrayList<NetworkAgentInfo>();
+ if (VDBG) log(" new Network has: " + newNetwork.networkCapabilities);
+ for (NetworkRequestInfo nri : mNetworkRequests.values()) {
+ NetworkAgentInfo currentNetwork = mNetworkForRequestId.get(nri.request.requestId);
+ if (newNetwork == currentNetwork) {
+ if (VDBG) log("Network " + newNetwork.name() + " was already satisfying" +
+ " request " + nri.request.requestId + ". No change.");
+ keep = true;
+ continue;
+ }
+
+ // check if it satisfies the NetworkCapabilities
+ if (VDBG) log(" checking if request is satisfied: " + nri.request);
+ if (nri.request.networkCapabilities.satisfiedByNetworkCapabilities(
+ newNetwork.networkCapabilities)) {
+ // next check if it's better than any current network we're using for
+ // this request
+ if (VDBG) {
+ log("currentScore = " +
+ (currentNetwork != null ? currentNetwork.currentScore : 0) +
+ ", newScore = " + newNetwork.currentScore);
+ }
+ if (currentNetwork == null ||
+ currentNetwork.currentScore < newNetwork.currentScore) {
+ if (currentNetwork != null) {
+ if (VDBG) log(" accepting network in place of " + currentNetwork.name());
+ currentNetwork.networkRequests.remove(nri.request.requestId);
+ currentNetwork.networkLingered.add(nri.request);
+ affectedNetworks.add(currentNetwork);
+ } else {
+ if (VDBG) log(" accepting network in place of null");
+ }
+ mNetworkForRequestId.put(nri.request.requestId, newNetwork);
+ newNetwork.addRequest(nri.request);
+ int legacyType = nri.request.legacyType;
+ if (legacyType != TYPE_NONE) {
+ mLegacyTypeTracker.add(legacyType, newNetwork);
+ }
+ keep = true;
+ // TODO - this could get expensive if we have alot of requests for this
+ // network. Think about if there is a way to reduce this. Push
+ // netid->request mapping to each factory?
+ sendUpdatedScoreToFactories(nri.request, newNetwork.currentScore);
+ if (mDefaultRequest.requestId == nri.request.requestId) {
+ isNewDefault = true;
+ updateActiveDefaultNetwork(newNetwork);
+ if (newNetwork.linkProperties != null) {
+ setDefaultDnsSystemProperties(
+ newNetwork.linkProperties.getDnsServers());
+ } else {
+ setDefaultDnsSystemProperties(new ArrayList<InetAddress>());
+ }
+ mLegacyTypeTracker.add(newNetwork.networkInfo.getType(), newNetwork);
+ }
+ }
+ }
+ }
+ for (NetworkAgentInfo nai : affectedNetworks) {
+ boolean teardown = true;
+ for (int i = 0; i < nai.networkRequests.size(); i++) {
+ NetworkRequest nr = nai.networkRequests.valueAt(i);
+ try {
+ if (mNetworkRequests.get(nr).isRequest) {
+ teardown = false;
+ }
+ } catch (Exception e) {
+ loge("Request " + nr + " not found in mNetworkRequests.");
+ loge(" it came from request list of " + nai.name());
+ }
+ }
+ if (teardown) {
+ nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_LINGER);
+ notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOSING);
+ } else {
+ // not going to linger, so kill the list of linger networks.. only
+ // notify them of linger if it happens as the result of gaining another,
+ // but if they transition and old network stays up, don't tell them of linger
+ // or very delayed loss
+ nai.networkLingered.clear();
+ if (VDBG) log("Lingered for " + nai.name() + " cleared");
+ }
+ }
+ if (keep) {
+ if (isNewDefault) {
+ if (VDBG) log("Switching to new default network: " + newNetwork);
+ setupDataActivityTracking(newNetwork);
+ try {
+ mNetd.setDefaultNetId(newNetwork.network.netId);
+ } catch (Exception e) {
+ loge("Exception setting default network :" + e);
+ }
+ if (newNetwork.equals(mNetworkForRequestId.get(mDefaultRequest.requestId))) {
+ handleApplyDefaultProxy(newNetwork.linkProperties.getHttpProxy());
+ }
+ synchronized (ConnectivityService.this) {
+ // have a new default network, release the transition wakelock in
+ // a second if it's held. The second pause is to allow apps
+ // to reconnect over the new network
+ if (mNetTransitionWakeLock.isHeld()) {
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(
+ EVENT_CLEAR_NET_TRANSITION_WAKELOCK,
+ mNetTransitionWakeLockSerialNumber, 0),
+ 1000);
+ }
+ }
+
+ // this will cause us to come up initially as unconnected and switching
+ // to connected after our normal pause unless somebody reports us as
+ // really disconnected
+ mDefaultInetConditionPublished = 0;
+ mDefaultConnectionSequence++;
+ mInetConditionChangeInFlight = false;
+ // TODO - read the tcp buffer size config string from somewhere
+ // updateNetworkSettings();
+ }
+ // notify battery stats service about this network
+ try {
+ BatteryStatsService.getService().noteNetworkInterfaceType(
+ newNetwork.linkProperties.getInterfaceName(),
+ newNetwork.networkInfo.getType());
+ } catch (RemoteException e) { }
+ notifyNetworkCallbacks(newNetwork, ConnectivityManager.CALLBACK_AVAILABLE);
+ } else {
+ if (DBG && newNetwork.networkRequests.size() != 0) {
+ loge("tearing down network with live requests:");
+ for (int i=0; i < newNetwork.networkRequests.size(); i++) {
+ loge(" " + newNetwork.networkRequests.valueAt(i));
+ }
+ }
+ if (VDBG) log("Validated network turns out to be unwanted. Tear it down.");
+ newNetwork.asyncChannel.disconnect();
+ }
+ }
+
+
+ private void updateNetworkInfo(NetworkAgentInfo networkAgent, NetworkInfo newInfo) {
+ NetworkInfo.State state = newInfo.getState();
+ NetworkInfo oldInfo = networkAgent.networkInfo;
+ networkAgent.networkInfo = newInfo;
+
+ if (oldInfo != null && oldInfo.getState() == state) {
+ if (VDBG) log("ignoring duplicate network state non-change");
+ return;
+ }
+ if (DBG) {
+ log(networkAgent.name() + " EVENT_NETWORK_INFO_CHANGED, going from " +
+ (oldInfo == null ? "null" : oldInfo.getState()) +
+ " to " + state);
+ }
+
+ if (state == NetworkInfo.State.CONNECTED) {
+ try {
+ // This is likely caused by the fact that this network already
+ // exists. An example is when a network goes from CONNECTED to
+ // CONNECTING and back (like wifi on DHCP renew).
+ // TODO: keep track of which networks we've created, or ask netd
+ // to tell us whether we've already created this network or not.
+ mNetd.createNetwork(networkAgent.network.netId);
+ } catch (Exception e) {
+ loge("Error creating network " + networkAgent.network.netId + ": "
+ + e.getMessage());
+ return;
+ }
+
+ updateLinkProperties(networkAgent, null);
+ notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_PRECHECK);
+ networkAgent.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED);
+ } else if (state == NetworkInfo.State.DISCONNECTED ||
+ state == NetworkInfo.State.SUSPENDED) {
+ networkAgent.asyncChannel.disconnect();
+ }
+ }
+
+ private void updateNetworkScore(NetworkAgentInfo nai, int score) {
+ if (DBG) log("updateNetworkScore for " + nai.name() + " to " + score);
+
+ nai.currentScore = score;
+
+ // TODO - This will not do the right thing if this network is lowering
+ // its score and has requests that can be served by other
+ // currently-active networks, or if the network is increasing its
+ // score and other networks have requests that can be better served
+ // by this network.
+ //
+ // Really we want to see if any of our requests migrate to other
+ // active/lingered networks and if any other requests migrate to us (depending
+ // on increasing/decreasing currentScore. That's a bit of work and probably our
+ // score checking/network allocation code needs to be modularized so we can understand
+ // (see handleConnectionValided for an example).
+ //
+ // As a first order approx, lets just advertise the new score to factories. If
+ // somebody can beat it they will nominate a network and our normal net replacement
+ // code will fire.
+ for (int i = 0; i < nai.networkRequests.size(); i++) {
+ NetworkRequest nr = nai.networkRequests.valueAt(i);
+ sendUpdatedScoreToFactories(nr, score);
+ }
+ }
+
+ // notify only this one new request of the current state
+ protected void notifyNetworkCallback(NetworkAgentInfo nai, NetworkRequestInfo nri) {
+ int notifyType = ConnectivityManager.CALLBACK_AVAILABLE;
+ // TODO - read state from monitor to decide what to send.
+// if (nai.networkMonitor.isLingering()) {
+// notifyType = NetworkCallbacks.LOSING;
+// } else if (nai.networkMonitor.isEvaluating()) {
+// notifyType = NetworkCallbacks.callCallbackForRequest(request, nai, notifyType);
+// }
+ callCallbackForRequest(nri, nai, notifyType);
+ }
+
+ private void sendLegacyNetworkBroadcast(NetworkAgentInfo nai, boolean connected, int type) {
+ if (connected) {
+ NetworkInfo info = new NetworkInfo(nai.networkInfo);
+ info.setType(type);
+ sendConnectedBroadcastDelayed(info, getConnectivityChangeDelay());
+ } else {
+ NetworkInfo info = new NetworkInfo(nai.networkInfo);
+ info.setType(type);
+ Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION);
+ intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info);
+ intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType());
+ if (info.isFailover()) {
+ intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true);
+ nai.networkInfo.setFailover(false);
+ }
+ if (info.getReason() != null) {
+ intent.putExtra(ConnectivityManager.EXTRA_REASON, info.getReason());
+ }
+ if (info.getExtraInfo() != null) {
+ intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, info.getExtraInfo());
+ }
+ NetworkAgentInfo newDefaultAgent = null;
+ if (nai.networkRequests.get(mDefaultRequest.requestId) != null) {
+ newDefaultAgent = mNetworkForRequestId.get(mDefaultRequest.requestId);
+ if (newDefaultAgent != null) {
+ intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO,
+ newDefaultAgent.networkInfo);
+ } else {
+ intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true);
+ }
+ }
+ intent.putExtra(ConnectivityManager.EXTRA_INET_CONDITION,
+ mDefaultInetConditionPublished);
+ final Intent immediateIntent = new Intent(intent);
+ immediateIntent.setAction(CONNECTIVITY_ACTION_IMMEDIATE);
+ sendStickyBroadcast(immediateIntent);
+ sendStickyBroadcastDelayed(intent, getConnectivityChangeDelay());
+ if (newDefaultAgent != null) {
+ sendConnectedBroadcastDelayed(newDefaultAgent.networkInfo,
+ getConnectivityChangeDelay());
+ }
+ }
+ }
+
+ protected void notifyNetworkCallbacks(NetworkAgentInfo networkAgent, int notifyType) {
+ if (VDBG) log("notifyType " + notifyType + " for " + networkAgent.name());
+ for (int i = 0; i < networkAgent.networkRequests.size(); i++) {
+ NetworkRequest nr = networkAgent.networkRequests.valueAt(i);
+ NetworkRequestInfo nri = mNetworkRequests.get(nr);
+ if (VDBG) log(" sending notification for " + nr);
+ callCallbackForRequest(nri, networkAgent, notifyType);
+ }
+ }
+
+ private LinkProperties getLinkPropertiesForTypeInternal(int networkType) {
+ NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType);
+ return (nai != null) ?
+ new LinkProperties(nai.linkProperties) :
+ new LinkProperties();
+ }
+
+ private NetworkInfo getNetworkInfoForType(int networkType) {
+ if (!mLegacyTypeTracker.isTypeSupported(networkType))
+ return null;
+
+ NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType);
+ if (nai != null) {
+ NetworkInfo result = new NetworkInfo(nai.networkInfo);
+ result.setType(networkType);
+ return result;
+ } else {
+ return new NetworkInfo(networkType, 0, "Unknown", "");
+ }
+ }
+
+ private NetworkCapabilities getNetworkCapabilitiesForType(int networkType) {
+ NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType);
+ return (nai != null) ?
+ new NetworkCapabilities(nai.networkCapabilities) :
+ new NetworkCapabilities();
+ }
+}
diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
new file mode 100644
index 0000000..096ab66
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
@@ -0,0 +1,219 @@
+/*
+ * 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.connectivity;
+
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+
+import java.net.Inet4Address;
+
+import android.content.Context;
+import android.net.IConnectivityManager;
+import android.net.InterfaceConfiguration;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.NetworkAgent;
+import android.net.NetworkUtils;
+import android.net.RouteInfo;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.INetworkManagementService;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.net.BaseNetworkObserver;
+
+/**
+ * @hide
+ *
+ * Class to manage a 464xlat CLAT daemon.
+ */
+public class Nat464Xlat extends BaseNetworkObserver {
+ private Context mContext;
+ private INetworkManagementService mNMService;
+ private IConnectivityManager mConnService;
+ // Whether we started clatd and expect it to be running.
+ private boolean mIsStarted;
+ // Whether the clatd interface exists (i.e., clatd is running).
+ private boolean mIsRunning;
+ // The LinkProperties of the clat interface.
+ private LinkProperties mLP;
+ // Current LinkProperties of the network. Includes mLP as a stacked link when clat is active.
+ private LinkProperties mBaseLP;
+ // ConnectivityService Handler for LinkProperties updates.
+ private Handler mHandler;
+ // Marker to connote which network we're augmenting.
+ private Messenger mNetworkMessenger;
+
+ // This must match the interface name in clatd.conf.
+ private static final String CLAT_INTERFACE_NAME = "clat4";
+
+ private static final String TAG = "Nat464Xlat";
+
+ public Nat464Xlat(Context context, INetworkManagementService nmService,
+ IConnectivityManager connService, Handler handler) {
+ mContext = context;
+ mNMService = nmService;
+ mConnService = connService;
+ mHandler = handler;
+
+ mIsStarted = false;
+ mIsRunning = false;
+ mLP = new LinkProperties();
+
+ // If this is a runtime restart, it's possible that clatd is already
+ // running, but we don't know about it. If so, stop it.
+ try {
+ if (mNMService.isClatdStarted()) {
+ mNMService.stopClatd();
+ }
+ } catch(RemoteException e) {} // Well, we tried.
+ }
+
+ /**
+ * Determines whether a network requires clat.
+ * @param network the NetworkAgentInfo corresponding to the network.
+ * @return true if the network requires clat, false otherwise.
+ */
+ public boolean requiresClat(NetworkAgentInfo network) {
+ int netType = network.networkInfo.getType();
+ LinkProperties lp = network.linkProperties;
+ // Only support clat on mobile for now.
+ Slog.d(TAG, "requiresClat: netType=" + netType + ", hasIPv4Address=" +
+ lp.hasIPv4Address());
+ return netType == TYPE_MOBILE && !lp.hasIPv4Address();
+ }
+
+ public static boolean isRunningClat(LinkProperties lp) {
+ return lp != null && lp.getAllInterfaceNames().contains(CLAT_INTERFACE_NAME);
+ }
+
+ /**
+ * Starts the clat daemon.
+ * @param lp The link properties of the interface to start clatd on.
+ */
+ public void startClat(NetworkAgentInfo network) {
+ if (mNetworkMessenger != null && mNetworkMessenger != network.messenger) {
+ Slog.e(TAG, "startClat: too many networks requesting clat");
+ return;
+ }
+ mNetworkMessenger = network.messenger;
+ LinkProperties lp = network.linkProperties;
+ mBaseLP = new LinkProperties(lp);
+ if (mIsStarted) {
+ Slog.e(TAG, "startClat: already started");
+ return;
+ }
+ String iface = lp.getInterfaceName();
+ Slog.i(TAG, "Starting clatd on " + iface + ", lp=" + lp);
+ try {
+ mNMService.startClatd(iface);
+ } catch(RemoteException e) {
+ Slog.e(TAG, "Error starting clat daemon: " + e);
+ }
+ mIsStarted = true;
+ }
+
+ /**
+ * Stops the clat daemon.
+ */
+ public void stopClat() {
+ if (mIsStarted) {
+ Slog.i(TAG, "Stopping clatd");
+ try {
+ mNMService.stopClatd();
+ } catch(RemoteException e) {
+ Slog.e(TAG, "Error stopping clat daemon: " + e);
+ }
+ mIsStarted = false;
+ mIsRunning = false;
+ mNetworkMessenger = null;
+ mBaseLP = null;
+ mLP.clear();
+ } else {
+ Slog.e(TAG, "stopClat: already stopped");
+ }
+ }
+
+ public boolean isStarted() {
+ return mIsStarted;
+ }
+
+ public boolean isRunning() {
+ return mIsRunning;
+ }
+
+ private void updateConnectivityService() {
+ Message msg = mHandler.obtainMessage(
+ NetworkAgent.EVENT_NETWORK_PROPERTIES_CHANGED, mBaseLP);
+ msg.replyTo = mNetworkMessenger;
+ Slog.i(TAG, "sending message to ConnectivityService: " + msg);
+ msg.sendToTarget();
+ }
+
+ @Override
+ public void interfaceAdded(String iface) {
+ if (iface.equals(CLAT_INTERFACE_NAME)) {
+ Slog.i(TAG, "interface " + CLAT_INTERFACE_NAME +
+ " added, mIsRunning = " + mIsRunning + " -> true");
+ mIsRunning = true;
+
+ // Create the LinkProperties for the clat interface by fetching the
+ // IPv4 address for the interface and adding an IPv4 default route,
+ // then stack the LinkProperties on top of the link it's running on.
+ // Although the clat interface is a point-to-point tunnel, we don't
+ // point the route directly at the interface because some apps don't
+ // understand routes without gateways (see, e.g., http://b/9597256
+ // http://b/9597516). Instead, set the next hop of the route to the
+ // clat IPv4 address itself (for those apps, it doesn't matter what
+ // the IP of the gateway is, only that there is one).
+ try {
+ InterfaceConfiguration config = mNMService.getInterfaceConfig(iface);
+ LinkAddress clatAddress = config.getLinkAddress();
+ mLP.clear();
+ mLP.setInterfaceName(iface);
+ RouteInfo ipv4Default = new RouteInfo(new LinkAddress(Inet4Address.ANY, 0),
+ clatAddress.getAddress(), iface);
+ mLP.addRoute(ipv4Default);
+ mLP.addLinkAddress(clatAddress);
+ mBaseLP.addStackedLink(mLP);
+ Slog.i(TAG, "Adding stacked link. tracker LP: " + mBaseLP);
+ updateConnectivityService();
+ } catch(RemoteException e) {
+ Slog.e(TAG, "Error getting link properties: " + e);
+ }
+ }
+ }
+
+ @Override
+ public void interfaceRemoved(String iface) {
+ if (iface == CLAT_INTERFACE_NAME) {
+ if (mIsRunning) {
+ NetworkUtils.resetConnections(
+ CLAT_INTERFACE_NAME,
+ NetworkUtils.RESET_IPV4_ADDRESSES);
+ mBaseLP.removeStackedLink(mLP);
+ updateConnectivityService();
+ }
+ Slog.i(TAG, "interface " + CLAT_INTERFACE_NAME +
+ " removed, mIsRunning = " + mIsRunning + " -> false");
+ mIsRunning = false;
+ mLP.clear();
+ Slog.i(TAG, "mLP = " + mLP);
+ }
+ }
+};
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
new file mode 100644
index 0000000..b03c247
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2014 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.connectivity;
+
+import android.content.Context;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.net.NetworkRequest;
+import android.os.Handler;
+import android.os.Messenger;
+import android.util.SparseArray;
+
+import com.android.internal.util.AsyncChannel;
+import com.android.server.connectivity.NetworkMonitor;
+
+import java.util.ArrayList;
+
+/**
+ * A bag class used by ConnectivityService for holding a collection of most recent
+ * information published by a particular NetworkAgent as well as the
+ * AsyncChannel/messenger for reaching that NetworkAgent and lists of NetworkRequests
+ * interested in using it.
+ */
+public class NetworkAgentInfo {
+ public NetworkInfo networkInfo;
+ public final Network network;
+ public LinkProperties linkProperties;
+ public NetworkCapabilities networkCapabilities;
+ public int currentScore;
+ public final NetworkMonitor networkMonitor;
+
+ // The list of NetworkRequests being satisfied by this Network.
+ public final SparseArray<NetworkRequest> networkRequests = new SparseArray<NetworkRequest>();
+ public final ArrayList<NetworkRequest> networkLingered = new ArrayList<NetworkRequest>();
+
+ public final Messenger messenger;
+ public final AsyncChannel asyncChannel;
+
+ public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, int netId, NetworkInfo info,
+ LinkProperties lp, NetworkCapabilities nc, int score, Context context,
+ Handler handler) {
+ this.messenger = messenger;
+ asyncChannel = ac;
+ network = new Network(netId);
+ networkInfo = info;
+ linkProperties = lp;
+ networkCapabilities = nc;
+ currentScore = score;
+ networkMonitor = new NetworkMonitor(context, handler, this);
+ }
+
+ public void addRequest(NetworkRequest networkRequest) {
+ networkRequests.put(networkRequest.requestId, networkRequest);
+ }
+
+ public String toString() {
+ return "NetworkAgentInfo{ ni{" + networkInfo + "} network{" +
+ network + "} lp{" +
+ linkProperties + "} nc{" +
+ networkCapabilities + "} Score{" + currentScore + "} }";
+ }
+
+ public String name() {
+ return "NetworkAgentInfo [" + networkInfo.getTypeName() + " (" +
+ networkInfo.getSubtypeName() + ")]";
+ }
+}
diff --git a/services/tests/servicestests/res/raw/netstats_uid_v4 b/services/tests/servicestests/res/raw/netstats_uid_v4
new file mode 100644
index 0000000..e75fc1c
--- /dev/null
+++ b/services/tests/servicestests/res/raw/netstats_uid_v4
Binary files differ
diff --git a/services/tests/servicestests/res/raw/netstats_v1 b/services/tests/servicestests/res/raw/netstats_v1
new file mode 100644
index 0000000..e80860a
--- /dev/null
+++ b/services/tests/servicestests/res/raw/netstats_v1
Binary files differ
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..88aaafc
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
@@ -0,0 +1,231 @@
+/*
+ * 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 org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.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 org.mockito.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"),
+ MOBILE_IFACE);
+ private static final RouteInfo MOBILE_ROUTE_V6 = RouteInfo.makeHostRoute(parse("fd00::33"),
+ MOBILE_IFACE);
+
+ private static final RouteInfo WIFI_ROUTE_V4 = RouteInfo.makeHostRoute(parse("192.168.0.66"),
+ parse("192.168.0.1"),
+ WIFI_IFACE);
+ private static final RouteInfo WIFI_ROUTE_V6 = RouteInfo.makeHostRoute(parse("fd00::66"),
+ parse("fd00::"),
+ WIFI_IFACE);
+
+ 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 = ArgumentCaptor.forClass(Handler.class);
+ 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
+ int mobileNetId = mMobile.tracker.getNetwork().netId;
+ verify(mNetManager).addRoute(eq(mobileNetId), eq(MOBILE_ROUTE_V4));
+ verify(mNetManager).addRoute(eq(mobileNetId), eq(MOBILE_ROUTE_V6));
+ verify(mNetManager).flushNetworkDnsCache(mobileNetId);
+
+ }
+
+ 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
+ int wifiNetId = mWifi.tracker.getNetwork().netId;
+ verify(mNetManager).addRoute(eq(wifiNetId), eq(WIFI_ROUTE_V4));
+ verify(mNetManager).addRoute(eq(wifiNetId), eq(WIFI_ROUTE_V6));
+ verify(mNetManager).flushNetworkDnsCache(wifiNetId);
+ verify(mMobile.tracker).teardown();
+
+ int mobileNetId = mMobile.tracker.getNetwork().netId;
+
+ 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(mobileNetId), eq(MOBILE_ROUTE_V4));
+ verify(mNetManager).removeRoute(eq(mobileNetId), eq(MOBILE_ROUTE_V6));
+
+ }
+
+ private static InetAddress parse(String addr) {
+ return InetAddress.parseNumericAddress(addr);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java
new file mode 100644
index 0000000..0d5daa5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java
@@ -0,0 +1,226 @@
+/*
+ * 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 android.content.Context;
+import android.net.LinkAddress;
+import android.net.LocalSocket;
+import android.net.LocalServerSocket;
+import android.os.Binder;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import com.android.server.net.BaseNetworkObserver;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Tests for {@link NetworkManagementService}.
+ */
+@LargeTest
+public class NetworkManagementServiceTest extends AndroidTestCase {
+
+ private static final String SOCKET_NAME = "__test__NetworkManagementServiceTest";
+ private NetworkManagementService mNMService;
+ private LocalServerSocket mServerSocket;
+ private LocalSocket mSocket;
+ private OutputStream mOutputStream;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ // TODO: make this unnecessary. runtest might already make it unnecessary.
+ System.setProperty("dexmaker.dexcache", getContext().getCacheDir().toString());
+
+ // Set up a sheltered test environment.
+ BroadcastInterceptingContext context = new BroadcastInterceptingContext(getContext());
+ mServerSocket = new LocalServerSocket(SOCKET_NAME);
+
+ // Start the service and wait until it connects to our socket.
+ mNMService = NetworkManagementService.create(context, SOCKET_NAME);
+ mSocket = mServerSocket.accept();
+ mOutputStream = mSocket.getOutputStream();
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ if (mSocket != null) mSocket.close();
+ if (mServerSocket != null) mServerSocket.close();
+ super.tearDown();
+ }
+
+ /**
+ * Sends a message on the netd socket and gives the events some time to make it back.
+ */
+ private void sendMessage(String message) throws IOException {
+ // Strings are null-terminated, so add "\0" at the end.
+ mOutputStream.write((message + "\0").getBytes());
+ }
+
+ private static <T> T expectSoon(T mock) {
+ return verify(mock, timeout(100));
+ }
+
+ /**
+ * Tests that network observers work properly.
+ */
+ public void testNetworkObservers() throws Exception {
+ BaseNetworkObserver observer = mock(BaseNetworkObserver.class);
+ doReturn(new Binder()).when(observer).asBinder(); // Used by registerObserver.
+ mNMService.registerObserver(observer);
+
+ // Forget everything that happened to the mock so far, so we can explicitly verify
+ // everything that happens and does not happen to it from now on.
+ reset(observer);
+
+ // Now send NetworkManagementService messages and ensure that the observer methods are
+ // called. After every valid message we expect a callback soon after; to ensure that
+ // invalid messages don't cause any callbacks, we call verifyNoMoreInteractions at the end.
+
+ /**
+ * Interface changes.
+ */
+ sendMessage("600 Iface added rmnet12");
+ expectSoon(observer).interfaceAdded("rmnet12");
+
+ sendMessage("600 Iface removed eth1");
+ expectSoon(observer).interfaceRemoved("eth1");
+
+ sendMessage("607 Iface removed eth1");
+ // Invalid code.
+
+ sendMessage("600 Iface borked lo down");
+ // Invalid event.
+
+ sendMessage("600 Iface changed clat4 up again");
+ // Extra tokens.
+
+ sendMessage("600 Iface changed clat4 up");
+ expectSoon(observer).interfaceStatusChanged("clat4", true);
+
+ sendMessage("600 Iface linkstate rmnet0 down");
+ expectSoon(observer).interfaceLinkStateChanged("rmnet0", false);
+
+ sendMessage("600 IFACE linkstate clat4 up");
+ // Invalid group.
+
+ /**
+ * Bandwidth control events.
+ */
+ sendMessage("601 limit alert data rmnet_usb0");
+ expectSoon(observer).limitReached("data", "rmnet_usb0");
+
+ sendMessage("601 invalid alert data rmnet0");
+ // Invalid group.
+
+ sendMessage("601 limit increased data rmnet0");
+ // Invalid event.
+
+
+ /**
+ * Interface class activity.
+ */
+
+ sendMessage("613 IfaceClass active rmnet0");
+ expectSoon(observer).interfaceClassDataActivityChanged("rmnet0", true, 0);
+
+ sendMessage("613 IfaceClass active rmnet0 1234");
+ expectSoon(observer).interfaceClassDataActivityChanged("rmnet0", true, 1234);
+
+ sendMessage("613 IfaceClass idle eth0");
+ expectSoon(observer).interfaceClassDataActivityChanged("eth0", false, 0);
+
+ sendMessage("613 IfaceClass idle eth0 1234");
+ expectSoon(observer).interfaceClassDataActivityChanged("eth0", false, 1234);
+
+ sendMessage("613 IfaceClass reallyactive rmnet0 1234");
+ expectSoon(observer).interfaceClassDataActivityChanged("rmnet0", false, 1234);
+
+ sendMessage("613 InterfaceClass reallyactive rmnet0");
+ // Invalid group.
+
+
+ /**
+ * IP address changes.
+ */
+ sendMessage("614 Address updated fe80::1/64 wlan0 128 253");
+ expectSoon(observer).addressUpdated("wlan0", new LinkAddress("fe80::1/64", 128, 253));
+
+ // There is no "added", so we take this as "removed".
+ sendMessage("614 Address added fe80::1/64 wlan0 128 253");
+ expectSoon(observer).addressRemoved("wlan0", new LinkAddress("fe80::1/64", 128, 253));
+
+ sendMessage("614 Address removed 2001:db8::1/64 wlan0 1 0");
+ expectSoon(observer).addressRemoved("wlan0", new LinkAddress("2001:db8::1/64", 1, 0));
+
+ sendMessage("614 Address removed 2001:db8::1/64 wlan0 1");
+ // Not enough arguments.
+
+ sendMessage("666 Address removed 2001:db8::1/64 wlan0 1 0");
+ // Invalid code.
+
+
+ /**
+ * DNS information broadcasts.
+ */
+ sendMessage("615 DnsInfo servers rmnet_usb0 3600 2001:db8::1");
+ expectSoon(observer).interfaceDnsServerInfo("rmnet_usb0", 3600,
+ new String[]{"2001:db8::1"});
+
+ sendMessage("615 DnsInfo servers wlan0 14400 2001:db8::1,2001:db8::2");
+ expectSoon(observer).interfaceDnsServerInfo("wlan0", 14400,
+ new String[]{"2001:db8::1", "2001:db8::2"});
+
+ // We don't check for negative lifetimes, only for parse errors.
+ sendMessage("615 DnsInfo servers wlan0 -3600 ::1");
+ expectSoon(observer).interfaceDnsServerInfo("wlan0", -3600,
+ new String[]{"::1"});
+
+ sendMessage("615 DnsInfo servers wlan0 SIXHUNDRED ::1");
+ // Non-numeric lifetime.
+
+ sendMessage("615 DnsInfo servers wlan0 2001:db8::1");
+ // Missing lifetime.
+
+ sendMessage("615 DnsInfo servers wlan0 3600");
+ // No servers.
+
+ sendMessage("615 DnsInfo servers 3600 wlan0 2001:db8::1,2001:db8::2");
+ // Non-numeric lifetime.
+
+ sendMessage("615 DnsInfo wlan0 7200 2001:db8::1,2001:db8::2");
+ // Invalid tokens.
+
+ sendMessage("666 DnsInfo servers wlan0 5400 2001:db8::1");
+ // Invalid code.
+
+ // No syntax checking on the addresses.
+ sendMessage("615 DnsInfo servers wlan0 600 ,::,,foo,::1,");
+ expectSoon(observer).interfaceDnsServerInfo("wlan0", 600,
+ new String[]{"", "::", "", "foo", "::1"});
+
+ // Make sure nothing else was called.
+ verifyNoMoreInteractions(observer);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
new file mode 100644
index 0000000..a1af8cb
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
@@ -0,0 +1,1057 @@
+/*
+ * Copyright (C) 2011 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.content.Intent.ACTION_UID_REMOVED;
+import static android.content.Intent.EXTRA_UID;
+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.TYPE_WIMAX;
+import static android.net.NetworkStats.IFACE_ALL;
+import static android.net.NetworkStats.SET_ALL;
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.SET_FOREGROUND;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkStats.UID_ALL;
+import static android.net.NetworkStatsHistory.FIELD_ALL;
+import static android.net.NetworkTemplate.buildTemplateMobileAll;
+import static android.net.NetworkTemplate.buildTemplateWifiWildcard;
+import static android.net.TrafficStats.MB_IN_BYTES;
+import static android.net.TrafficStats.UID_REMOVED;
+import static android.net.TrafficStats.UID_TETHERING;
+import static android.text.format.DateUtils.DAY_IN_MILLIS;
+import static android.text.format.DateUtils.HOUR_IN_MILLIS;
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+import static android.text.format.DateUtils.WEEK_IN_MILLIS;
+import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_POLL;
+import static org.easymock.EasyMock.anyLong;
+import static org.easymock.EasyMock.capture;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.eq;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.isA;
+
+import android.app.AlarmManager;
+import android.app.IAlarmManager;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.net.IConnectivityManager;
+import android.net.INetworkManagementEventObserver;
+import android.net.INetworkStatsSession;
+import android.net.LinkProperties;
+import android.net.NetworkInfo;
+import android.net.NetworkInfo.DetailedState;
+import android.net.NetworkState;
+import android.net.NetworkStats;
+import android.net.NetworkStatsHistory;
+import android.net.NetworkTemplate;
+import android.os.INetworkManagementService;
+import android.os.WorkSource;
+import android.telephony.TelephonyManager;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.Suppress;
+import android.util.TrustedTime;
+
+import com.android.server.net.NetworkStatsService;
+import com.android.server.net.NetworkStatsService.NetworkStatsSettings;
+import com.android.server.net.NetworkStatsService.NetworkStatsSettings.Config;
+
+import libcore.io.IoUtils;
+
+import org.easymock.Capture;
+import org.easymock.EasyMock;
+
+import java.io.File;
+
+/**
+ * Tests for {@link NetworkStatsService}.
+ */
+@LargeTest
+public class NetworkStatsServiceTest extends AndroidTestCase {
+ private static final String TAG = "NetworkStatsServiceTest";
+
+ private static final String TEST_IFACE = "test0";
+ private static final String TEST_IFACE2 = "test1";
+ private static final long TEST_START = 1194220800000L;
+
+ private static final String IMSI_1 = "310004";
+ private static final String IMSI_2 = "310260";
+ private static final String TEST_SSID = "AndroidAP";
+
+ private static NetworkTemplate sTemplateWifi = buildTemplateWifiWildcard();
+ private static NetworkTemplate sTemplateImsi1 = buildTemplateMobileAll(IMSI_1);
+ private static NetworkTemplate sTemplateImsi2 = buildTemplateMobileAll(IMSI_2);
+
+ private static final int UID_RED = 1001;
+ private static final int UID_BLUE = 1002;
+ private static final int UID_GREEN = 1003;
+
+ private long mElapsedRealtime;
+
+ private BroadcastInterceptingContext mServiceContext;
+ private File mStatsDir;
+
+ private INetworkManagementService mNetManager;
+ private IAlarmManager mAlarmManager;
+ private TrustedTime mTime;
+ private NetworkStatsSettings mSettings;
+ private IConnectivityManager mConnManager;
+
+ private NetworkStatsService mService;
+ private INetworkStatsSession mSession;
+ private INetworkManagementEventObserver mNetworkObserver;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ mServiceContext = new BroadcastInterceptingContext(getContext());
+ mStatsDir = getContext().getFilesDir();
+ if (mStatsDir.exists()) {
+ IoUtils.deleteContents(mStatsDir);
+ }
+
+ mNetManager = createMock(INetworkManagementService.class);
+ mAlarmManager = createMock(IAlarmManager.class);
+ mTime = createMock(TrustedTime.class);
+ mSettings = createMock(NetworkStatsSettings.class);
+ mConnManager = createMock(IConnectivityManager.class);
+
+ mService = new NetworkStatsService(
+ mServiceContext, mNetManager, mAlarmManager, mTime, mStatsDir, mSettings);
+ mService.bindConnectivityManager(mConnManager);
+
+ mElapsedRealtime = 0L;
+
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectSystemReady();
+
+ // catch INetworkManagementEventObserver during systemReady()
+ final Capture<INetworkManagementEventObserver> networkObserver = new Capture<
+ INetworkManagementEventObserver>();
+ mNetManager.registerObserver(capture(networkObserver));
+ expectLastCall().atLeastOnce();
+
+ replay();
+ mService.systemReady();
+ mSession = mService.openSession();
+ verifyAndReset();
+
+ mNetworkObserver = networkObserver.getValue();
+
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ IoUtils.deleteContents(mStatsDir);
+
+ mServiceContext = null;
+ mStatsDir = null;
+
+ mNetManager = null;
+ mAlarmManager = null;
+ mTime = null;
+ mSettings = null;
+ mConnManager = null;
+
+ mSession.close();
+ mService = null;
+
+ super.tearDown();
+ }
+
+ public void testNetworkStatsWifi() throws Exception {
+ // pretend that wifi network comes online; service should ask about full
+ // network state, and poll any existing interfaces before updating.
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkState(buildWifiState());
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectNetworkStatsPoll();
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+
+ // verify service has empty history for wifi
+ assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
+ verifyAndReset();
+
+ // modify some number on wifi, and trigger poll event
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ .addIfaceValues(TEST_IFACE, 1024L, 1L, 2048L, 2L));
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectNetworkStatsPoll();
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+ // verify service recorded history
+ assertNetworkTotal(sTemplateWifi, 1024L, 1L, 2048L, 2L, 0);
+ verifyAndReset();
+
+ // and bump forward again, with counters going higher. this is
+ // important, since polling should correctly subtract last snapshot.
+ incrementCurrentTime(DAY_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ .addIfaceValues(TEST_IFACE, 4096L, 4L, 8192L, 8L));
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectNetworkStatsPoll();
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+ // verify service recorded history
+ assertNetworkTotal(sTemplateWifi, 4096L, 4L, 8192L, 8L, 0);
+ verifyAndReset();
+
+ }
+
+ public void testStatsRebootPersist() throws Exception {
+ assertStatsFilesExist(false);
+
+ // pretend that wifi network comes online; service should ask about full
+ // network state, and poll any existing interfaces before updating.
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkState(buildWifiState());
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectNetworkStatsPoll();
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+
+ // verify service has empty history for wifi
+ assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
+ verifyAndReset();
+
+ // modify some number on wifi, and trigger poll event
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ .addIfaceValues(TEST_IFACE, 1024L, 8L, 2048L, 16L));
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 2)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 256L, 2L, 128L, 1L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 256L, 2L, 128L, 1L, 0L)
+ .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 128L, 1L, 128L, 1L, 0L));
+ expectNetworkStatsPoll();
+
+ mService.setUidForeground(UID_RED, false);
+ mService.incrementOperationCount(UID_RED, 0xFAAD, 4);
+ mService.setUidForeground(UID_RED, true);
+ mService.incrementOperationCount(UID_RED, 0xFAAD, 6);
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+ // verify service recorded history
+ assertNetworkTotal(sTemplateWifi, 1024L, 8L, 2048L, 16L, 0);
+ assertUidTotal(sTemplateWifi, UID_RED, 1024L, 8L, 512L, 4L, 10);
+ assertUidTotal(sTemplateWifi, UID_RED, SET_DEFAULT, 512L, 4L, 256L, 2L, 4);
+ assertUidTotal(sTemplateWifi, UID_RED, SET_FOREGROUND, 512L, 4L, 256L, 2L, 6);
+ assertUidTotal(sTemplateWifi, UID_BLUE, 128L, 1L, 128L, 1L, 0);
+ verifyAndReset();
+
+ // graceful shutdown system, which should trigger persist of stats, and
+ // clear any values in memory.
+ expectCurrentTime();
+ expectDefaultSettings();
+ replay();
+ mServiceContext.sendBroadcast(new Intent(Intent.ACTION_SHUTDOWN));
+ verifyAndReset();
+
+ assertStatsFilesExist(true);
+
+ // boot through serviceReady() again
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectSystemReady();
+
+ // catch INetworkManagementEventObserver during systemReady()
+ final Capture<INetworkManagementEventObserver> networkObserver = new Capture<
+ INetworkManagementEventObserver>();
+ mNetManager.registerObserver(capture(networkObserver));
+ expectLastCall().atLeastOnce();
+
+ replay();
+ mService.systemReady();
+
+ mNetworkObserver = networkObserver.getValue();
+
+ // after systemReady(), we should have historical stats loaded again
+ assertNetworkTotal(sTemplateWifi, 1024L, 8L, 2048L, 16L, 0);
+ assertUidTotal(sTemplateWifi, UID_RED, 1024L, 8L, 512L, 4L, 10);
+ assertUidTotal(sTemplateWifi, UID_RED, SET_DEFAULT, 512L, 4L, 256L, 2L, 4);
+ assertUidTotal(sTemplateWifi, UID_RED, SET_FOREGROUND, 512L, 4L, 256L, 2L, 6);
+ assertUidTotal(sTemplateWifi, UID_BLUE, 128L, 1L, 128L, 1L, 0);
+ verifyAndReset();
+
+ }
+
+ // TODO: simulate reboot to test bucket resize
+ @Suppress
+ public void testStatsBucketResize() throws Exception {
+ NetworkStatsHistory history = null;
+
+ assertStatsFilesExist(false);
+
+ // pretend that wifi network comes online; service should ask about full
+ // network state, and poll any existing interfaces before updating.
+ expectCurrentTime();
+ expectSettings(0L, HOUR_IN_MILLIS, WEEK_IN_MILLIS);
+ expectNetworkState(buildWifiState());
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectNetworkStatsPoll();
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ verifyAndReset();
+
+ // modify some number on wifi, and trigger poll event
+ incrementCurrentTime(2 * HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectSettings(0L, HOUR_IN_MILLIS, WEEK_IN_MILLIS);
+ expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ .addIfaceValues(TEST_IFACE, 512L, 4L, 512L, 4L));
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectNetworkStatsPoll();
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+ // verify service recorded history
+ history = mSession.getHistoryForNetwork(sTemplateWifi, FIELD_ALL);
+ assertValues(history, Long.MIN_VALUE, Long.MAX_VALUE, 512L, 4L, 512L, 4L, 0);
+ assertEquals(HOUR_IN_MILLIS, history.getBucketDuration());
+ assertEquals(2, history.size());
+ verifyAndReset();
+
+ // now change bucket duration setting and trigger another poll with
+ // exact same values, which should resize existing buckets.
+ expectCurrentTime();
+ expectSettings(0L, 30 * MINUTE_IN_MILLIS, WEEK_IN_MILLIS);
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectNetworkStatsPoll();
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+ // verify identical stats, but spread across 4 buckets now
+ history = mSession.getHistoryForNetwork(sTemplateWifi, FIELD_ALL);
+ assertValues(history, Long.MIN_VALUE, Long.MAX_VALUE, 512L, 4L, 512L, 4L, 0);
+ assertEquals(30 * MINUTE_IN_MILLIS, history.getBucketDuration());
+ assertEquals(4, history.size());
+ verifyAndReset();
+
+ }
+
+ public void testUidStatsAcrossNetworks() throws Exception {
+ // pretend first mobile network comes online
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkState(buildMobile3gState(IMSI_1));
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectNetworkStatsPoll();
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ verifyAndReset();
+
+ // create some traffic on first network
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ .addIfaceValues(TEST_IFACE, 2048L, 16L, 512L, 4L));
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
+ .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 512L, 4L, 0L, 0L, 0L));
+ expectNetworkStatsPoll();
+
+ mService.incrementOperationCount(UID_RED, 0xF00D, 10);
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+ // verify service recorded history
+ assertNetworkTotal(sTemplateImsi1, 2048L, 16L, 512L, 4L, 0);
+ assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
+ assertUidTotal(sTemplateImsi1, UID_RED, 1536L, 12L, 512L, 4L, 10);
+ assertUidTotal(sTemplateImsi1, UID_BLUE, 512L, 4L, 0L, 0L, 0);
+ verifyAndReset();
+
+ // now switch networks; this also tests that we're okay with interfaces
+ // disappearing, to verify we don't count backwards.
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkState(buildMobile3gState(IMSI_2));
+ expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ .addIfaceValues(TEST_IFACE, 2048L, 16L, 512L, 4L));
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
+ .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 512L, 4L, 0L, 0L, 0L));
+ expectNetworkStatsPoll();
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+ verifyAndReset();
+
+ // create traffic on second network
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ .addIfaceValues(TEST_IFACE, 2176L, 17L, 1536L, 12L));
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
+ .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 640L, 5L, 1024L, 8L, 0L)
+ .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, 0xFAAD, 128L, 1L, 1024L, 8L, 0L));
+ expectNetworkStatsPoll();
+
+ mService.incrementOperationCount(UID_BLUE, 0xFAAD, 10);
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+ // verify original history still intact
+ assertNetworkTotal(sTemplateImsi1, 2048L, 16L, 512L, 4L, 0);
+ assertUidTotal(sTemplateImsi1, UID_RED, 1536L, 12L, 512L, 4L, 10);
+ assertUidTotal(sTemplateImsi1, UID_BLUE, 512L, 4L, 0L, 0L, 0);
+
+ // and verify new history also recorded under different template, which
+ // verifies that we didn't cross the streams.
+ assertNetworkTotal(sTemplateImsi2, 128L, 1L, 1024L, 8L, 0);
+ assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
+ assertUidTotal(sTemplateImsi2, UID_BLUE, 128L, 1L, 1024L, 8L, 10);
+ verifyAndReset();
+
+ }
+
+ public void testUidRemovedIsMoved() throws Exception {
+ // pretend that network comes online
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkState(buildWifiState());
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectNetworkStatsPoll();
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ verifyAndReset();
+
+ // create some traffic
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ .addIfaceValues(TEST_IFACE, 4128L, 258L, 544L, 34L));
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 16L, 1L, 16L, 1L, 0L)
+ .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 4096L, 258L, 512L, 32L, 0L)
+ .addValues(TEST_IFACE, UID_GREEN, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L));
+ expectNetworkStatsPoll();
+
+ mService.incrementOperationCount(UID_RED, 0xFAAD, 10);
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+ // verify service recorded history
+ assertNetworkTotal(sTemplateWifi, 4128L, 258L, 544L, 34L, 0);
+ assertUidTotal(sTemplateWifi, UID_RED, 16L, 1L, 16L, 1L, 10);
+ assertUidTotal(sTemplateWifi, UID_BLUE, 4096L, 258L, 512L, 32L, 0);
+ assertUidTotal(sTemplateWifi, UID_GREEN, 16L, 1L, 16L, 1L, 0);
+ verifyAndReset();
+
+ // now pretend two UIDs are uninstalled, which should migrate stats to
+ // special "removed" bucket.
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ .addIfaceValues(TEST_IFACE, 4128L, 258L, 544L, 34L));
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 16L, 1L, 16L, 1L, 0L)
+ .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 4096L, 258L, 512L, 32L, 0L)
+ .addValues(TEST_IFACE, UID_GREEN, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L));
+ expectNetworkStatsPoll();
+
+ replay();
+ final Intent intent = new Intent(ACTION_UID_REMOVED);
+ intent.putExtra(EXTRA_UID, UID_BLUE);
+ mServiceContext.sendBroadcast(intent);
+ intent.putExtra(EXTRA_UID, UID_RED);
+ mServiceContext.sendBroadcast(intent);
+
+ // existing uid and total should remain unchanged; but removed UID
+ // should be gone completely.
+ assertNetworkTotal(sTemplateWifi, 4128L, 258L, 544L, 34L, 0);
+ assertUidTotal(sTemplateWifi, UID_RED, 0L, 0L, 0L, 0L, 0);
+ assertUidTotal(sTemplateWifi, UID_BLUE, 0L, 0L, 0L, 0L, 0);
+ assertUidTotal(sTemplateWifi, UID_GREEN, 16L, 1L, 16L, 1L, 0);
+ assertUidTotal(sTemplateWifi, UID_REMOVED, 4112L, 259L, 528L, 33L, 10);
+ verifyAndReset();
+
+ }
+
+ public void testUid3g4gCombinedByTemplate() throws Exception {
+ // pretend that network comes online
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkState(buildMobile3gState(IMSI_1));
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectNetworkStatsPoll();
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ verifyAndReset();
+
+ // create some traffic
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L));
+ expectNetworkStatsPoll();
+
+ mService.incrementOperationCount(UID_RED, 0xF00D, 5);
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+ // verify service recorded history
+ assertUidTotal(sTemplateImsi1, UID_RED, 1024L, 8L, 1024L, 8L, 5);
+ verifyAndReset();
+
+ // now switch over to 4g network
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkState(buildMobile4gState(TEST_IFACE2));
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L));
+ expectNetworkStatsPoll();
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+ verifyAndReset();
+
+ // create traffic on second network
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
+ .addValues(TEST_IFACE2, UID_RED, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
+ .addValues(TEST_IFACE2, UID_RED, SET_DEFAULT, 0xFAAD, 512L, 4L, 256L, 2L, 0L));
+ expectNetworkStatsPoll();
+
+ mService.incrementOperationCount(UID_RED, 0xFAAD, 5);
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+ // verify that ALL_MOBILE template combines both
+ assertUidTotal(sTemplateImsi1, UID_RED, 1536L, 12L, 1280L, 10L, 10);
+
+ verifyAndReset();
+ }
+
+ public void testSummaryForAllUid() throws Exception {
+ // pretend that network comes online
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkState(buildWifiState());
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectNetworkStatsPoll();
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ verifyAndReset();
+
+ // create some traffic for two apps
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 10L, 1L, 10L, 1L, 0L)
+ .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 1024L, 8L, 512L, 4L, 0L));
+ expectNetworkStatsPoll();
+
+ mService.incrementOperationCount(UID_RED, 0xF00D, 1);
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+ // verify service recorded history
+ assertUidTotal(sTemplateWifi, UID_RED, 50L, 5L, 50L, 5L, 1);
+ assertUidTotal(sTemplateWifi, UID_BLUE, 1024L, 8L, 512L, 4L, 0);
+ verifyAndReset();
+
+ // now create more traffic in next hour, but only for one app
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 10L, 1L, 10L, 1L, 0L)
+ .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 2048L, 16L, 1024L, 8L, 0L));
+ expectNetworkStatsPoll();
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+ // first verify entire history present
+ NetworkStats stats = mSession.getSummaryForAllUid(
+ sTemplateWifi, Long.MIN_VALUE, Long.MAX_VALUE, true);
+ assertEquals(3, stats.size());
+ assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 1);
+ assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, 10L, 1L, 10L, 1L, 1);
+ assertValues(stats, IFACE_ALL, UID_BLUE, SET_DEFAULT, TAG_NONE, 2048L, 16L, 1024L, 8L, 0);
+
+ // now verify that recent history only contains one uid
+ final long currentTime = currentTimeMillis();
+ stats = mSession.getSummaryForAllUid(
+ sTemplateWifi, currentTime - HOUR_IN_MILLIS, currentTime, true);
+ assertEquals(1, stats.size());
+ assertValues(stats, IFACE_ALL, UID_BLUE, SET_DEFAULT, TAG_NONE, 1024L, 8L, 512L, 4L, 0);
+
+ verifyAndReset();
+ }
+
+ public void testForegroundBackground() throws Exception {
+ // pretend that network comes online
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkState(buildWifiState());
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectNetworkStatsPoll();
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ verifyAndReset();
+
+ // create some initial traffic
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 64L, 1L, 64L, 1L, 0L));
+ expectNetworkStatsPoll();
+
+ mService.incrementOperationCount(UID_RED, 0xF00D, 1);
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+ // verify service recorded history
+ assertUidTotal(sTemplateWifi, UID_RED, 128L, 2L, 128L, 2L, 1);
+ verifyAndReset();
+
+ // now switch to foreground
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 64L, 1L, 64L, 1L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 32L, 2L, 32L, 2L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 1L, 1L, 1L, 1L, 0L));
+ expectNetworkStatsPoll();
+
+ mService.setUidForeground(UID_RED, true);
+ mService.incrementOperationCount(UID_RED, 0xFAAD, 1);
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+ // test that we combined correctly
+ assertUidTotal(sTemplateWifi, UID_RED, 160L, 4L, 160L, 4L, 2);
+
+ // verify entire history present
+ final NetworkStats stats = mSession.getSummaryForAllUid(
+ sTemplateWifi, Long.MIN_VALUE, Long.MAX_VALUE, true);
+ assertEquals(4, stats.size());
+ assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 1);
+ assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, 64L, 1L, 64L, 1L, 1);
+ assertValues(stats, IFACE_ALL, UID_RED, SET_FOREGROUND, TAG_NONE, 32L, 2L, 32L, 2L, 1);
+ assertValues(stats, IFACE_ALL, UID_RED, SET_FOREGROUND, 0xFAAD, 1L, 1L, 1L, 1L, 1);
+
+ verifyAndReset();
+ }
+
+ public void testTethering() throws Exception {
+ // pretend first mobile network comes online
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkState(buildMobile3gState(IMSI_1));
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectNetworkStatsPoll();
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ verifyAndReset();
+
+ // create some tethering traffic
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ .addIfaceValues(TEST_IFACE, 2048L, 16L, 512L, 4L));
+
+ final NetworkStats uidStats = new NetworkStats(getElapsedRealtime(), 1)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L);
+ final String[] tetherIfacePairs = new String[] { TEST_IFACE, "wlan0" };
+ final NetworkStats tetherStats = new NetworkStats(getElapsedRealtime(), 1)
+ .addValues(TEST_IFACE, UID_TETHERING, SET_DEFAULT, TAG_NONE, 1920L, 14L, 384L, 2L, 0L);
+
+ expectNetworkStatsUidDetail(uidStats, tetherIfacePairs, tetherStats);
+ expectNetworkStatsPoll();
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+ // verify service recorded history
+ assertNetworkTotal(sTemplateImsi1, 2048L, 16L, 512L, 4L, 0);
+ assertUidTotal(sTemplateImsi1, UID_RED, 128L, 2L, 128L, 2L, 0);
+ assertUidTotal(sTemplateImsi1, UID_TETHERING, 1920L, 14L, 384L, 2L, 0);
+ verifyAndReset();
+
+ }
+
+ public void testReportXtOverDev() throws Exception {
+ // bring mobile network online
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkState(buildMobile3gState(IMSI_1));
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectNetworkStatsPoll();
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ verifyAndReset();
+
+ // create some traffic, but only for DEV, and across 1.5 buckets
+ incrementCurrentTime(90 * MINUTE_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummaryDev(new NetworkStats(getElapsedRealtime(), 1)
+ .addIfaceValues(TEST_IFACE, 6000L, 60L, 3000L, 30L));
+ expectNetworkStatsSummaryXt(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectNetworkStatsPoll();
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+ // verify service recorded history:
+ // 4000(dev) + 2000(dev)
+ assertNetworkTotal(sTemplateImsi1, 6000L, 60L, 3000L, 30L, 0);
+ verifyAndReset();
+
+ // create traffic on both DEV and XT, across two buckets
+ incrementCurrentTime(2 * HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummaryDev(new NetworkStats(getElapsedRealtime(), 1)
+ .addIfaceValues(TEST_IFACE, 6004L, 64L, 3004L, 34L));
+ expectNetworkStatsSummaryXt(new NetworkStats(getElapsedRealtime(), 1)
+ .addIfaceValues(TEST_IFACE, 10240L, 0L, 0L, 0L));
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectNetworkStatsPoll();
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+ // verify that we switching reporting at the first atomic XT bucket,
+ // which should give us:
+ // 4000(dev) + 2000(dev) + 1(dev) + 5120(xt) + 2560(xt)
+ assertNetworkTotal(sTemplateImsi1, 13681L, 61L, 3001L, 31L, 0);
+
+ // also test pure-DEV and pure-XT ranges
+ assertNetworkTotal(sTemplateImsi1, startTimeMillis(),
+ startTimeMillis() + 2 * HOUR_IN_MILLIS, 6001L, 61L, 3001L, 31L, 0);
+ assertNetworkTotal(sTemplateImsi1, startTimeMillis() + 2 * HOUR_IN_MILLIS,
+ startTimeMillis() + 4 * HOUR_IN_MILLIS, 7680L, 0L, 0L, 0L, 0);
+
+ verifyAndReset();
+ }
+
+ private void assertNetworkTotal(NetworkTemplate template, long rxBytes, long rxPackets,
+ long txBytes, long txPackets, int operations) throws Exception {
+ assertNetworkTotal(template, Long.MIN_VALUE, Long.MAX_VALUE, rxBytes, rxPackets, txBytes,
+ txPackets, operations);
+ }
+
+ private void assertNetworkTotal(NetworkTemplate template, long start, long end, long rxBytes,
+ long rxPackets, long txBytes, long txPackets, int operations) throws Exception {
+ // verify history API
+ final NetworkStatsHistory history = mSession.getHistoryForNetwork(template, FIELD_ALL);
+ assertValues(history, start, end, rxBytes, rxPackets, txBytes, txPackets, operations);
+
+ // verify summary API
+ final NetworkStats stats = mSession.getSummaryForNetwork(template, start, end);
+ assertValues(stats, IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, rxPackets, txBytes,
+ txPackets, operations);
+ }
+
+ private void assertUidTotal(NetworkTemplate template, int uid, long rxBytes, long rxPackets,
+ long txBytes, long txPackets, int operations) throws Exception {
+ assertUidTotal(template, uid, SET_ALL, rxBytes, rxPackets, txBytes, txPackets, operations);
+ }
+
+ private void assertUidTotal(NetworkTemplate template, int uid, int set, long rxBytes,
+ long rxPackets, long txBytes, long txPackets, int operations) throws Exception {
+ // verify history API
+ final NetworkStatsHistory history = mSession.getHistoryForUid(
+ template, uid, set, TAG_NONE, FIELD_ALL);
+ assertValues(history, Long.MIN_VALUE, Long.MAX_VALUE, rxBytes, rxPackets, txBytes,
+ txPackets, operations);
+
+ // verify summary API
+ final NetworkStats stats = mSession.getSummaryForAllUid(
+ template, Long.MIN_VALUE, Long.MAX_VALUE, false);
+ assertValues(stats, IFACE_ALL, uid, set, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets,
+ operations);
+ }
+
+ private void expectSystemReady() throws Exception {
+ mAlarmManager.remove(isA(PendingIntent.class));
+ expectLastCall().anyTimes();
+
+ mAlarmManager.set(eq(AlarmManager.ELAPSED_REALTIME), anyLong(), anyLong(), anyLong(),
+ isA(PendingIntent.class), isA(WorkSource.class));
+ expectLastCall().atLeastOnce();
+
+ mNetManager.setGlobalAlert(anyLong());
+ expectLastCall().atLeastOnce();
+
+ expect(mNetManager.isBandwidthControlEnabled()).andReturn(true).atLeastOnce();
+ }
+
+ private void expectNetworkState(NetworkState... state) throws Exception {
+ expect(mConnManager.getAllNetworkState()).andReturn(state).atLeastOnce();
+
+ final LinkProperties linkProp = state.length > 0 ? state[0].linkProperties : null;
+ expect(mConnManager.getActiveLinkProperties()).andReturn(linkProp).atLeastOnce();
+ }
+
+ private void expectNetworkStatsSummary(NetworkStats summary) throws Exception {
+ expectNetworkStatsSummaryDev(summary);
+ expectNetworkStatsSummaryXt(summary);
+ }
+
+ private void expectNetworkStatsSummaryDev(NetworkStats summary) throws Exception {
+ expect(mNetManager.getNetworkStatsSummaryDev()).andReturn(summary).atLeastOnce();
+ }
+
+ private void expectNetworkStatsSummaryXt(NetworkStats summary) throws Exception {
+ expect(mNetManager.getNetworkStatsSummaryXt()).andReturn(summary).atLeastOnce();
+ }
+
+ private void expectNetworkStatsUidDetail(NetworkStats detail) throws Exception {
+ expectNetworkStatsUidDetail(detail, new String[0], new NetworkStats(0L, 0));
+ }
+
+ private void expectNetworkStatsUidDetail(
+ NetworkStats detail, String[] tetherIfacePairs, NetworkStats tetherStats)
+ throws Exception {
+ expect(mNetManager.getNetworkStatsUidDetail(eq(UID_ALL))).andReturn(detail).atLeastOnce();
+
+ // also include tethering details, since they are folded into UID
+ expect(mNetManager.getNetworkStatsTethering())
+ .andReturn(tetherStats).atLeastOnce();
+ }
+
+ private void expectDefaultSettings() throws Exception {
+ expectSettings(0L, HOUR_IN_MILLIS, WEEK_IN_MILLIS);
+ }
+
+ private void expectSettings(long persistBytes, long bucketDuration, long deleteAge)
+ throws Exception {
+ expect(mSettings.getPollInterval()).andReturn(HOUR_IN_MILLIS).anyTimes();
+ expect(mSettings.getTimeCacheMaxAge()).andReturn(DAY_IN_MILLIS).anyTimes();
+ expect(mSettings.getSampleEnabled()).andReturn(true).anyTimes();
+ expect(mSettings.getReportXtOverDev()).andReturn(true).anyTimes();
+
+ final Config config = new Config(bucketDuration, deleteAge, deleteAge);
+ expect(mSettings.getDevConfig()).andReturn(config).anyTimes();
+ expect(mSettings.getXtConfig()).andReturn(config).anyTimes();
+ expect(mSettings.getUidConfig()).andReturn(config).anyTimes();
+ expect(mSettings.getUidTagConfig()).andReturn(config).anyTimes();
+
+ expect(mSettings.getGlobalAlertBytes(anyLong())).andReturn(MB_IN_BYTES).anyTimes();
+ expect(mSettings.getDevPersistBytes(anyLong())).andReturn(MB_IN_BYTES).anyTimes();
+ expect(mSettings.getXtPersistBytes(anyLong())).andReturn(MB_IN_BYTES).anyTimes();
+ expect(mSettings.getUidPersistBytes(anyLong())).andReturn(MB_IN_BYTES).anyTimes();
+ expect(mSettings.getUidTagPersistBytes(anyLong())).andReturn(MB_IN_BYTES).anyTimes();
+ }
+
+ private void expectCurrentTime() throws Exception {
+ expect(mTime.forceRefresh()).andReturn(false).anyTimes();
+ expect(mTime.hasCache()).andReturn(true).anyTimes();
+ expect(mTime.currentTimeMillis()).andReturn(currentTimeMillis()).anyTimes();
+ expect(mTime.getCacheAge()).andReturn(0L).anyTimes();
+ expect(mTime.getCacheCertainty()).andReturn(0L).anyTimes();
+ }
+
+ private void expectNetworkStatsPoll() throws Exception {
+ mNetManager.setGlobalAlert(anyLong());
+ expectLastCall().anyTimes();
+ }
+
+ private void assertStatsFilesExist(boolean exist) {
+ final File basePath = new File(mStatsDir, "netstats");
+ if (exist) {
+ assertTrue(basePath.list().length > 0);
+ } else {
+ assertTrue(basePath.list().length == 0);
+ }
+ }
+
+ private static void assertValues(NetworkStats stats, String iface, int uid, int set,
+ int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, int operations) {
+ final NetworkStats.Entry entry = new NetworkStats.Entry();
+ if (set == SET_DEFAULT || set == SET_ALL) {
+ final int i = stats.findIndex(iface, uid, SET_DEFAULT, tag);
+ if (i != -1) {
+ entry.add(stats.getValues(i, null));
+ }
+ }
+ if (set == SET_FOREGROUND || set == SET_ALL) {
+ final int i = stats.findIndex(iface, uid, SET_FOREGROUND, tag);
+ if (i != -1) {
+ entry.add(stats.getValues(i, null));
+ }
+ }
+
+ assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
+ assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets);
+ assertEquals("unexpected txBytes", txBytes, entry.txBytes);
+ assertEquals("unexpected txPackets", txPackets, entry.txPackets);
+ assertEquals("unexpected operations", operations, entry.operations);
+ }
+
+ private static void assertValues(NetworkStatsHistory stats, long start, long end, long rxBytes,
+ long rxPackets, long txBytes, long txPackets, int operations) {
+ final NetworkStatsHistory.Entry entry = stats.getValues(start, end, null);
+ assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
+ assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets);
+ assertEquals("unexpected txBytes", txBytes, entry.txBytes);
+ assertEquals("unexpected txPackets", txPackets, entry.txPackets);
+ assertEquals("unexpected operations", operations, entry.operations);
+ }
+
+ private static NetworkState buildWifiState() {
+ final NetworkInfo info = new NetworkInfo(TYPE_WIFI, 0, null, null);
+ info.setDetailedState(DetailedState.CONNECTED, null, null);
+ final LinkProperties prop = new LinkProperties();
+ prop.setInterfaceName(TEST_IFACE);
+ return new NetworkState(info, prop, null, null, TEST_SSID);
+ }
+
+ private static NetworkState buildMobile3gState(String subscriberId) {
+ final NetworkInfo info = new NetworkInfo(
+ TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UMTS, null, null);
+ info.setDetailedState(DetailedState.CONNECTED, null, null);
+ final LinkProperties prop = new LinkProperties();
+ prop.setInterfaceName(TEST_IFACE);
+ return new NetworkState(info, prop, null, subscriberId, null);
+ }
+
+ private static NetworkState buildMobile4gState(String iface) {
+ final NetworkInfo info = new NetworkInfo(TYPE_WIMAX, 0, null, null);
+ info.setDetailedState(DetailedState.CONNECTED, null, null);
+ final LinkProperties prop = new LinkProperties();
+ prop.setInterfaceName(iface);
+ return new NetworkState(info, prop, null);
+ }
+
+ private NetworkStats buildEmptyStats() {
+ return new NetworkStats(getElapsedRealtime(), 0);
+ }
+
+ private long getElapsedRealtime() {
+ return mElapsedRealtime;
+ }
+
+ private long startTimeMillis() {
+ return TEST_START;
+ }
+
+ private long currentTimeMillis() {
+ return startTimeMillis() + mElapsedRealtime;
+ }
+
+ private void incrementCurrentTime(long duration) {
+ mElapsedRealtime += duration;
+ }
+
+ private void replay() {
+ EasyMock.replay(mNetManager, mAlarmManager, mTime, mSettings, mConnManager);
+ }
+
+ private void verifyAndReset() {
+ EasyMock.verify(mNetManager, mAlarmManager, mTime, mSettings, mConnManager);
+ EasyMock.reset(mNetManager, mAlarmManager, mTime, mSettings, mConnManager);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkStatsCollectionTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkStatsCollectionTest.java
new file mode 100644
index 0000000..1a6c289
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkStatsCollectionTest.java
@@ -0,0 +1,193 @@
+/*
+ * 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.net;
+
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkStats.UID_ALL;
+import static android.net.NetworkTemplate.buildTemplateMobileAll;
+import static android.text.format.DateUtils.HOUR_IN_MILLIS;
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+
+import android.content.res.Resources;
+import android.net.NetworkStats;
+import android.net.NetworkTemplate;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+
+import com.android.frameworks.servicestests.R;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import libcore.io.IoUtils;
+import libcore.io.Streams;
+
+/**
+ * Tests for {@link NetworkStatsCollection}.
+ */
+@MediumTest
+public class NetworkStatsCollectionTest extends AndroidTestCase {
+
+ private static final String TEST_FILE = "test.bin";
+ private static final String TEST_IMSI = "310260000000000";
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ // ignore any device overlay while testing
+ NetworkTemplate.forceAllNetworkTypes();
+ }
+
+ public void testReadLegacyNetwork() throws Exception {
+ final File testFile = new File(getContext().getFilesDir(), TEST_FILE);
+ stageFile(R.raw.netstats_v1, testFile);
+
+ final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS);
+ collection.readLegacyNetwork(testFile);
+
+ // verify that history read correctly
+ assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
+ 636016770L, 709306L, 88038768L, 518836L);
+
+ // now export into a unified format
+ final ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ collection.write(new DataOutputStream(bos));
+
+ // clear structure completely
+ collection.reset();
+ assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
+ 0L, 0L, 0L, 0L);
+
+ // and read back into structure, verifying that totals are same
+ collection.read(new ByteArrayInputStream(bos.toByteArray()));
+ assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
+ 636016770L, 709306L, 88038768L, 518836L);
+ }
+
+ public void testReadLegacyUid() throws Exception {
+ final File testFile = new File(getContext().getFilesDir(), TEST_FILE);
+ stageFile(R.raw.netstats_uid_v4, testFile);
+
+ final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS);
+ collection.readLegacyUid(testFile, false);
+
+ // verify that history read correctly
+ assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
+ 637076152L, 711413L, 88343717L, 521022L);
+
+ // now export into a unified format
+ final ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ collection.write(new DataOutputStream(bos));
+
+ // clear structure completely
+ collection.reset();
+ assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
+ 0L, 0L, 0L, 0L);
+
+ // and read back into structure, verifying that totals are same
+ collection.read(new ByteArrayInputStream(bos.toByteArray()));
+ assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
+ 637076152L, 711413L, 88343717L, 521022L);
+ }
+
+ public void testReadLegacyUidTags() throws Exception {
+ final File testFile = new File(getContext().getFilesDir(), TEST_FILE);
+ stageFile(R.raw.netstats_uid_v4, testFile);
+
+ final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS);
+ collection.readLegacyUid(testFile, true);
+
+ // verify that history read correctly
+ assertSummaryTotalIncludingTags(collection, buildTemplateMobileAll(TEST_IMSI),
+ 77017831L, 100995L, 35436758L, 92344L);
+
+ // now export into a unified format
+ final ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ collection.write(new DataOutputStream(bos));
+
+ // clear structure completely
+ collection.reset();
+ assertSummaryTotalIncludingTags(collection, buildTemplateMobileAll(TEST_IMSI),
+ 0L, 0L, 0L, 0L);
+
+ // and read back into structure, verifying that totals are same
+ collection.read(new ByteArrayInputStream(bos.toByteArray()));
+ assertSummaryTotalIncludingTags(collection, buildTemplateMobileAll(TEST_IMSI),
+ 77017831L, 100995L, 35436758L, 92344L);
+ }
+
+ public void testStartEndAtomicBuckets() throws Exception {
+ final NetworkStatsCollection collection = new NetworkStatsCollection(HOUR_IN_MILLIS);
+
+ // record empty data straddling between buckets
+ final NetworkStats.Entry entry = new NetworkStats.Entry();
+ entry.rxBytes = 32;
+ collection.recordData(null, UID_ALL, SET_DEFAULT, TAG_NONE, 30 * MINUTE_IN_MILLIS,
+ 90 * MINUTE_IN_MILLIS, entry);
+
+ // assert that we report boundary in atomic buckets
+ assertEquals(0, collection.getStartMillis());
+ assertEquals(2 * HOUR_IN_MILLIS, collection.getEndMillis());
+ }
+
+ /**
+ * Copy a {@link Resources#openRawResource(int)} into {@link File} for
+ * testing purposes.
+ */
+ private void stageFile(int rawId, File file) throws Exception {
+ new File(file.getParent()).mkdirs();
+ InputStream in = null;
+ OutputStream out = null;
+ try {
+ in = getContext().getResources().openRawResource(rawId);
+ out = new FileOutputStream(file);
+ Streams.copy(in, out);
+ } finally {
+ IoUtils.closeQuietly(in);
+ IoUtils.closeQuietly(out);
+ }
+ }
+
+ private static void assertSummaryTotal(NetworkStatsCollection collection,
+ NetworkTemplate template, long rxBytes, long rxPackets, long txBytes, long txPackets) {
+ final NetworkStats.Entry entry = collection.getSummary(
+ template, Long.MIN_VALUE, Long.MAX_VALUE).getTotal(null);
+ assertEntry(entry, rxBytes, rxPackets, txBytes, txPackets);
+ }
+
+ private static void assertSummaryTotalIncludingTags(NetworkStatsCollection collection,
+ NetworkTemplate template, long rxBytes, long rxPackets, long txBytes, long txPackets) {
+ final NetworkStats.Entry entry = collection.getSummary(
+ template, Long.MIN_VALUE, Long.MAX_VALUE).getTotalIncludingTags(null);
+ assertEntry(entry, rxBytes, rxPackets, txBytes, txPackets);
+ }
+
+ private static void assertEntry(
+ NetworkStats.Entry entry, long rxBytes, long rxPackets, long txBytes, long txPackets) {
+ assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
+ assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets);
+ assertEquals("unexpected txBytes", txBytes, entry.txBytes);
+ assertEquals("unexpected txPackets", txPackets, entry.txPackets);
+ }
+}
diff --git a/tests/CoreTests/android/core/NsdServiceInfoTest.java b/tests/CoreTests/android/core/NsdServiceInfoTest.java
new file mode 100644
index 0000000..5bf0167
--- /dev/null
+++ b/tests/CoreTests/android/core/NsdServiceInfoTest.java
@@ -0,0 +1,163 @@
+package android.core;
+
+import android.test.AndroidTestCase;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.StrictMode;
+import android.net.nsd.NsdServiceInfo;
+import android.util.Log;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+
+public class NsdServiceInfoTest extends AndroidTestCase {
+
+ public final static InetAddress LOCALHOST;
+ static {
+ // Because test.
+ StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
+ StrictMode.setThreadPolicy(policy);
+
+ InetAddress _host = null;
+ try {
+ _host = InetAddress.getLocalHost();
+ } catch (UnknownHostException e) { }
+ LOCALHOST = _host;
+ }
+
+ public void testLimits() throws Exception {
+ NsdServiceInfo info = new NsdServiceInfo();
+
+ // Non-ASCII keys.
+ boolean exceptionThrown = false;
+ try {
+ info.setAttribute("猫", "meow");
+ } catch (IllegalArgumentException e) {
+ exceptionThrown = true;
+ }
+ assertTrue(exceptionThrown);
+ assertEmptyServiceInfo(info);
+
+ // ASCII keys with '=' character.
+ exceptionThrown = false;
+ try {
+ info.setAttribute("kitten=", "meow");
+ } catch (IllegalArgumentException e) {
+ exceptionThrown = true;
+ }
+ assertTrue(exceptionThrown);
+ assertEmptyServiceInfo(info);
+
+ // Single key + value length too long.
+ exceptionThrown = false;
+ try {
+ String longValue = "loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo" +
+ "oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo" +
+ "oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo" +
+ "ooooooooooooooooooooooooooooong"; // 248 characters.
+ info.setAttribute("longcat", longValue); // Key + value == 255 characters.
+ } catch (IllegalArgumentException e) {
+ exceptionThrown = true;
+ }
+ assertTrue(exceptionThrown);
+ assertEmptyServiceInfo(info);
+
+ // Total TXT record length too long.
+ exceptionThrown = false;
+ int recordsAdded = 0;
+ try {
+ for (int i = 100; i < 300; ++i) {
+ // 6 char key + 5 char value + 2 bytes overhead = 13 byte record length.
+ String key = String.format("key%d", i);
+ info.setAttribute(key, "12345");
+ recordsAdded++;
+ }
+ } catch (IllegalArgumentException e) {
+ exceptionThrown = true;
+ }
+ assertTrue(exceptionThrown);
+ assertTrue(100 == recordsAdded);
+ assertTrue(info.getTxtRecord().length == 1300);
+ }
+
+ public void testParcel() throws Exception {
+ NsdServiceInfo emptyInfo = new NsdServiceInfo();
+ checkParcelable(emptyInfo);
+
+ NsdServiceInfo fullInfo = new NsdServiceInfo();
+ fullInfo.setServiceName("kitten");
+ fullInfo.setServiceType("_kitten._tcp");
+ fullInfo.setPort(4242);
+ fullInfo.setHost(LOCALHOST);
+ checkParcelable(fullInfo);
+
+ NsdServiceInfo noHostInfo = new NsdServiceInfo();
+ noHostInfo.setServiceName("kitten");
+ noHostInfo.setServiceType("_kitten._tcp");
+ noHostInfo.setPort(4242);
+ checkParcelable(noHostInfo);
+
+ NsdServiceInfo attributedInfo = new NsdServiceInfo();
+ attributedInfo.setServiceName("kitten");
+ attributedInfo.setServiceType("_kitten._tcp");
+ attributedInfo.setPort(4242);
+ attributedInfo.setHost(LOCALHOST);
+ attributedInfo.setAttribute("color", "pink");
+ attributedInfo.setAttribute("sound", (new String("にゃあ")).getBytes("UTF-8"));
+ attributedInfo.setAttribute("adorable", (String) null);
+ attributedInfo.setAttribute("sticky", "yes");
+ attributedInfo.setAttribute("siblings", new byte[] {});
+ attributedInfo.setAttribute("edge cases", new byte[] {0, -1, 127, -128});
+ attributedInfo.removeAttribute("sticky");
+ checkParcelable(attributedInfo);
+
+ // Sanity check that we actually wrote attributes to attributedInfo.
+ assertTrue(attributedInfo.getAttributes().keySet().contains("adorable"));
+ String sound = new String(attributedInfo.getAttributes().get("sound"), "UTF-8");
+ assertTrue(sound.equals("にゃあ"));
+ byte[] edgeCases = attributedInfo.getAttributes().get("edge cases");
+ assertTrue(Arrays.equals(edgeCases, new byte[] {0, -1, 127, -128}));
+ assertFalse(attributedInfo.getAttributes().keySet().contains("sticky"));
+ }
+
+ public void checkParcelable(NsdServiceInfo original) {
+ // Write to parcel.
+ Parcel p = Parcel.obtain();
+ Bundle writer = new Bundle();
+ writer.putParcelable("test_info", original);
+ writer.writeToParcel(p, 0);
+
+ // Extract from parcel.
+ p.setDataPosition(0);
+ Bundle reader = p.readBundle();
+ reader.setClassLoader(NsdServiceInfo.class.getClassLoader());
+ NsdServiceInfo result = reader.getParcelable("test_info");
+
+ // Assert equality of base fields.
+ assertEquality(original.getServiceName(), result.getServiceName());
+ assertEquality(original.getServiceType(), result.getServiceType());
+ assertEquality(original.getHost(), result.getHost());
+ assertTrue(original.getPort() == result.getPort());
+
+ // Assert equality of attribute map.
+ Map<String, byte[]> originalMap = original.getAttributes();
+ Map<String, byte[]> resultMap = result.getAttributes();
+ assertEquality(originalMap.keySet(), resultMap.keySet());
+ for (String key : originalMap.keySet()) {
+ assertTrue(Arrays.equals(originalMap.get(key), resultMap.get(key)));
+ }
+ }
+
+ public void assertEquality(Object expected, Object result) {
+ assertTrue(expected == result || expected.equals(result));
+ }
+
+ public void assertEmptyServiceInfo(NsdServiceInfo shouldBeEmpty) {
+ assertTrue(null == shouldBeEmpty.getTxtRecord());
+ }
+}
diff --git a/tests/common/Android.bp b/tests/common/Android.bp
index e8963b9..28339f1 100644
--- a/tests/common/Android.bp
+++ b/tests/common/Android.bp
@@ -43,6 +43,53 @@
],
}
+android_test {
+ name: "ConnectivityCoverageTests",
+ // Tethering started on SDK 30
+ min_sdk_version: "30",
+ // TODO: change to 31 as soon as it is available
+ target_sdk_version: "30",
+ test_suites: ["device-tests", "mts"],
+ defaults: [
+ "framework-connectivity-test-defaults",
+ "FrameworksNetTests-jni-defaults",
+ "libnetworkstackutilsjni_deps",
+ ],
+ manifest: "AndroidManifest_coverage.xml",
+ test_config: "AndroidTest_Coverage.xml",
+ static_libs: [
+ // Added first so all tests use extended mockito, instead of all tests using regular mockito
+ // (some tests would fail).
+ // TODO: consider removing extended mockito usage in tests that use it, for performance
+ "mockito-target-extended-minus-junit4",
+ "FrameworksNetTestsLib",
+ "modules-utils-native-coverage-listener",
+ "NetdStaticLibTestsLib",
+ "NetworkStaticLibTestsLib",
+ "NetworkStackTestsLib",
+ "TetheringTestsLatestSdkLib",
+ "TetheringIntegrationTestsLatestSdkLib",
+ ],
+ jni_libs: [
+ // For mockito extended
+ "libdexmakerjvmtiagent",
+ "libstaticjvmtiagent",
+ // For NetworkStackUtils included in NetworkStackBase
+ "libnetworkstackutilsjni",
+ "libtetherutilsjni",
+ // For framework tests
+ "libservice-connectivity",
+ ],
+ libs: [
+ // Although not required to compile the static libs together, the "libs" used to build each
+ // of the common static test libs are necessary for R8 to avoid "Missing class" warnings and
+ // incorrect optimizations
+ "framework-tethering.impl",
+ "framework-wifi.stubs.module_lib",
+ ],
+ compile_multilib: "both",
+}
+
// defaults for tests that need to build against framework-connectivity's @hide APIs
// Only usable from targets that have visibility on framework-connectivity.impl.
// Instead of using this, consider avoiding to depend on hidden connectivity APIs in
diff --git a/tests/common/AndroidManifest_coverage.xml b/tests/common/AndroidManifest_coverage.xml
new file mode 100644
index 0000000..8a22792
--- /dev/null
+++ b/tests/common/AndroidManifest_coverage.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.android.connectivity.tests.coverage">
+
+ <application tools:replace="android:label"
+ android:debuggable="true"
+ android:label="Connectivity coverage tests">
+ <uses-library android:name="android.test.runner" />
+ </application>
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.connectivity.tests.coverage"
+ android:label="Connectivity coverage tests">
+ </instrumentation>
+</manifest>
diff --git a/tests/common/AndroidTest_Coverage.xml b/tests/common/AndroidTest_Coverage.xml
new file mode 100644
index 0000000..577f36a
--- /dev/null
+++ b/tests/common/AndroidTest_Coverage.xml
@@ -0,0 +1,27 @@
+<!-- Copyright (C) 2021 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.
+-->
+<configuration description="Runs coverage tests for Connectivity">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="ConnectivityCoverageTests.apk" />
+ </target_preparer>
+
+ <option name="test-tag" value="ConnectivityCoverageTests" />
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.connectivity.tests.coverage" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false"/>
+ <option name="device-listeners" value="com.android.modules.utils.testing.NativeCoverageHackInstrumentationListener" />
+ </test>
+</configuration>
diff --git a/tests/common/java/android/net/NetworkProviderTest.kt b/tests/common/java/android/net/NetworkProviderTest.kt
index 97d3c5a..8cea12e 100644
--- a/tests/common/java/android/net/NetworkProviderTest.kt
+++ b/tests/common/java/android/net/NetworkProviderTest.kt
@@ -18,12 +18,14 @@
import android.app.Instrumentation
import android.content.Context
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED
import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED
import android.net.NetworkCapabilities.TRANSPORT_TEST
import android.net.NetworkProviderTest.TestNetworkCallback.CallbackEntry.OnUnavailable
import android.net.NetworkProviderTest.TestNetworkProvider.CallbackEntry.OnNetworkRequestWithdrawn
import android.net.NetworkProviderTest.TestNetworkProvider.CallbackEntry.OnNetworkRequested
import android.os.Build
+import android.os.Handler
import android.os.HandlerThread
import android.os.Looper
import android.util.Log
@@ -34,6 +36,7 @@
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.TestableNetworkOfferCallback
import com.android.testutils.isDevSdkInRange
import org.junit.After
import org.junit.Before
@@ -44,11 +47,14 @@
import org.mockito.Mockito.mock
import org.mockito.Mockito.verifyNoMoreInteractions
import java.util.UUID
+import java.util.concurrent.Executor
+import java.util.concurrent.RejectedExecutionException
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
import kotlin.test.fail
private const val DEFAULT_TIMEOUT_MS = 5000L
+private const val DEFAULT_NO_CALLBACK_TIMEOUT_MS = 200L
private val instrumentation: Instrumentation
get() = InstrumentationRegistry.getInstrumentation()
private val context: Context get() = InstrumentationRegistry.getContext()
@@ -102,12 +108,24 @@
crossinline predicate: (T) -> Boolean
) = seenEvents.poll(DEFAULT_TIMEOUT_MS) { it is T && predicate(it) }
?: fail("Did not receive callback after ${DEFAULT_TIMEOUT_MS}ms")
+
+ fun assertNoCallback() {
+ val cb = seenEvents.poll(DEFAULT_NO_CALLBACK_TIMEOUT_MS)
+ if (null != cb) fail("Expected no callback but got $cb")
+ }
}
private fun createNetworkProvider(ctx: Context = context): TestNetworkProvider {
return TestNetworkProvider(ctx, mHandlerThread.looper)
}
+ private fun createAndRegisterNetworkProvider(ctx: Context = context) =
+ createNetworkProvider(ctx).also {
+ assertEquals(it.getProviderId(), NetworkProvider.ID_NONE)
+ mCm.registerNetworkProvider(it)
+ assertNotEquals(it.getProviderId(), NetworkProvider.ID_NONE)
+ }
+
// In S+ framework, do not run this test, since the provider will no longer receive
// onNetworkRequested for every request. Instead, provider needs to
// call {@code registerNetworkOffer} with the description of networks they
@@ -115,10 +133,7 @@
@IgnoreAfter(Build.VERSION_CODES.R)
@Test
fun testOnNetworkRequested() {
- val provider = createNetworkProvider()
- assertEquals(provider.getProviderId(), NetworkProvider.ID_NONE)
- mCm.registerNetworkProvider(provider)
- assertNotEquals(provider.getProviderId(), NetworkProvider.ID_NONE)
+ val provider = createAndRegisterNetworkProvider()
val specifier = CompatUtil.makeTestNetworkSpecifier(
UUID.randomUUID().toString())
@@ -179,6 +194,167 @@
mCm.unregisterNetworkProvider(provider)
}
+ // Mainline module can't use internal HandlerExecutor, so add an identical executor here.
+ // TODO: Refactor with the one in MultiNetworkPolicyTracker.
+ private class HandlerExecutor(private val handler: Handler) : Executor {
+ public override fun execute(command: Runnable) {
+ if (!handler.post(command)) {
+ throw RejectedExecutionException(handler.toString() + " is shutting down")
+ }
+ }
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.R)
+ @Test
+ fun testRegisterNetworkOffer() {
+ val provider = createAndRegisterNetworkProvider()
+ val provider2 = createAndRegisterNetworkProvider()
+
+ // Prepare the materials which will be used to create different offers.
+ val specifier1 = CompatUtil.makeTestNetworkSpecifier("TEST-SPECIFIER-1")
+ val specifier2 = CompatUtil.makeTestNetworkSpecifier("TEST-SPECIFIER-2")
+ val scoreWeaker = NetworkScore.Builder().build()
+ val scoreStronger = NetworkScore.Builder().setTransportPrimary(true).build()
+ val ncFilter1 = NetworkCapabilities.Builder().addTransportType(TRANSPORT_TEST)
+ .setNetworkSpecifier(specifier1).build()
+ val ncFilter2 = NetworkCapabilities.Builder().addTransportType(TRANSPORT_TEST)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ .setNetworkSpecifier(specifier1).build()
+ val ncFilter3 = NetworkCapabilities.Builder().addTransportType(TRANSPORT_TEST)
+ .setNetworkSpecifier(specifier2).build()
+ val ncFilter4 = NetworkCapabilities.Builder().addTransportType(TRANSPORT_TEST)
+ .setNetworkSpecifier(specifier2).build()
+
+ // Make 4 offers, where 1 doesn't have NOT_VCN, 2 has NOT_VCN, 3 is similar to 1 but with
+ // different specifier, and 4 is also similar to 1 but with different provider.
+ val offerCallback1 = TestableNetworkOfferCallback(
+ DEFAULT_TIMEOUT_MS, DEFAULT_NO_CALLBACK_TIMEOUT_MS)
+ val offerCallback2 = TestableNetworkOfferCallback(
+ DEFAULT_TIMEOUT_MS, DEFAULT_NO_CALLBACK_TIMEOUT_MS)
+ val offerCallback3 = TestableNetworkOfferCallback(
+ DEFAULT_TIMEOUT_MS, DEFAULT_NO_CALLBACK_TIMEOUT_MS)
+ val offerCallback4 = TestableNetworkOfferCallback(
+ DEFAULT_TIMEOUT_MS, DEFAULT_NO_CALLBACK_TIMEOUT_MS)
+ provider.registerNetworkOffer(scoreWeaker, ncFilter1,
+ HandlerExecutor(mHandlerThread.threadHandler), offerCallback1)
+ provider.registerNetworkOffer(scoreStronger, ncFilter2,
+ HandlerExecutor(mHandlerThread.threadHandler), offerCallback2)
+ provider.registerNetworkOffer(scoreWeaker, ncFilter3,
+ HandlerExecutor(mHandlerThread.threadHandler), offerCallback3)
+ provider2.registerNetworkOffer(scoreWeaker, ncFilter4,
+ HandlerExecutor(mHandlerThread.threadHandler), offerCallback4)
+ // Unlike Android R, Android S+ provider will only receive interested requests via offer
+ // callback. Verify that the callbacks do not see any existing request such as default
+ // requests.
+ offerCallback1.assertNoCallback()
+ offerCallback2.assertNoCallback()
+ offerCallback3.assertNoCallback()
+ offerCallback4.assertNoCallback()
+
+ // File a request with specifier but without NOT_VCN, verify network is needed for callback
+ // with the same specifier.
+ val nrNoNotVcn: NetworkRequest = NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_TEST)
+ // Test network is not allowed to be trusted.
+ .removeCapability(NET_CAPABILITY_TRUSTED)
+ .setNetworkSpecifier(specifier1)
+ .build()
+ val cb1 = ConnectivityManager.NetworkCallback()
+ mCm.requestNetwork(nrNoNotVcn, cb1)
+ offerCallback1.expectOnNetworkNeeded(ncFilter1)
+ offerCallback2.expectOnNetworkNeeded(ncFilter2)
+ offerCallback3.assertNoCallback()
+ offerCallback4.assertNoCallback()
+
+ mCm.unregisterNetworkCallback(cb1)
+ offerCallback1.expectOnNetworkUnneeded(ncFilter1)
+ offerCallback2.expectOnNetworkUnneeded(ncFilter2)
+ offerCallback3.assertNoCallback()
+ offerCallback4.assertNoCallback()
+
+ // File a request without specifier but with NOT_VCN, verify network is needed for offer
+ // with NOT_VCN.
+ val nrNotVcn: NetworkRequest = NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_TEST)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ // Test network is not allowed to be trusted.
+ .removeCapability(NET_CAPABILITY_TRUSTED)
+ .build()
+ val cb2 = ConnectivityManager.NetworkCallback()
+ mCm.requestNetwork(nrNotVcn, cb2)
+ offerCallback1.assertNoCallback()
+ offerCallback2.expectOnNetworkNeeded(ncFilter2)
+ offerCallback3.assertNoCallback()
+ offerCallback4.assertNoCallback()
+
+ // Upgrade offer 3 & 4 to satisfy previous request and then verify they are also needed.
+ ncFilter3.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ provider.registerNetworkOffer(scoreWeaker, ncFilter3,
+ HandlerExecutor(mHandlerThread.threadHandler), offerCallback3)
+ ncFilter4.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ provider2.registerNetworkOffer(scoreWeaker, ncFilter4,
+ HandlerExecutor(mHandlerThread.threadHandler), offerCallback4)
+ offerCallback1.assertNoCallback()
+ offerCallback2.assertNoCallback()
+ offerCallback3.expectOnNetworkNeeded(ncFilter3)
+ offerCallback4.expectOnNetworkNeeded(ncFilter4)
+
+ // Connect an agent to fulfill the request, verify offer 4 is not needed since it is not
+ // from currently serving provider nor can beat the current satisfier.
+ val nc = NetworkCapabilities().apply {
+ addTransportType(NetworkCapabilities.TRANSPORT_TEST)
+ removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+ addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
+ addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
+ addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)
+ addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
+ setNetworkSpecifier(specifier1)
+ }
+ val config = NetworkAgentConfig.Builder().build()
+ val agent = object : NetworkAgent(context, mHandlerThread.looper, "TestAgent", nc,
+ LinkProperties(), scoreWeaker, config, provider) {}
+ agent.register()
+ agent.markConnected()
+ // TODO: The request is satisying by offer 2 instead of offer 1, thus it should not be
+ // considered as needed.
+ offerCallback1.expectOnNetworkNeeded(ncFilter2)
+ offerCallback2.assertNoCallback() // Still needed.
+ offerCallback3.assertNoCallback() // Still needed.
+ offerCallback4.expectOnNetworkUnneeded(ncFilter4)
+
+ // Upgrade the agent, verify no change since the framework will treat the offer as needed
+ // if a request is currently satisfied by the network provided by the same provider.
+ // TODO: Consider offers with weaker score are unneeded.
+ agent.sendNetworkScore(scoreStronger)
+ offerCallback1.assertNoCallback()
+ offerCallback2.assertNoCallback() // Still needed.
+ offerCallback3.assertNoCallback() // Still needed.
+ offerCallback4.assertNoCallback() // Still unneeded.
+
+ // Verify that offer callbacks cannot receive any event if offer is unregistered.
+ provider2.unregisterNetworkOffer(offerCallback4)
+ agent.unregister()
+ offerCallback1.assertNoCallback() // Still needed.
+ offerCallback2.assertNoCallback() // Still needed.
+ offerCallback3.assertNoCallback() // Still needed.
+ // Since the agent is unregistered, and the offer has chance to satisfy the request,
+ // this callback should receive needed if it is not unregistered.
+ offerCallback4.assertNoCallback()
+
+ // Verify that offer callbacks cannot receive any event if provider is unregistered.
+ mCm.unregisterNetworkProvider(provider)
+ mCm.unregisterNetworkCallback(cb2)
+ offerCallback1.assertNoCallback() // Should be unneeded if not unregistered.
+ offerCallback2.assertNoCallback() // Should be unneeded if not unregistered.
+ offerCallback3.assertNoCallback() // Should be unneeded if not unregistered.
+ offerCallback4.assertNoCallback() // Already unregistered.
+
+ // Clean up and Verify providers did not receive any callback during the entire test.
+ mCm.unregisterNetworkProvider(provider2)
+ provider.assertNoCallback()
+ provider2.assertNoCallback()
+ }
+
private class TestNetworkCallback : ConnectivityManager.NetworkCallback() {
private val seenEvents = ArrayTrackRecord<CallbackEntry>().newReadHead()
sealed class CallbackEntry {
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
index 8485263..62aa493 100755
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
@@ -760,7 +760,7 @@
assertEquals(vpnNetwork, mCM.getActiveNetwork());
assertNotEqual(defaultNetwork, vpnNetwork);
maybeExpectVpnTransportInfo(vpnNetwork);
- assertTrue(mCM.getNetworkInfo(vpnNetwork).getType() == TYPE_VPN);
+ assertEquals(TYPE_VPN, mCM.getNetworkInfo(vpnNetwork).getType());
if (SdkLevel.isAtLeastS()) {
// Check that system default network callback has not seen any network changes, even
diff --git a/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt b/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt
index f993aed..9f079c4 100644
--- a/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt
+++ b/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt
@@ -151,8 +151,8 @@
server.addResponse(Request(TEST_PORTAL_URL_PATH), Status.OK,
content = "Test captive portal content")
server.addResponse(Request(TEST_HTTPS_URL_PATH), Status.INTERNAL_ERROR)
- server.addResponse(Request(TEST_HTTP_URL_PATH), Status.REDIRECT,
- locationHeader = makeUrl(TEST_PORTAL_URL_PATH))
+ val headers = mapOf("Location" to makeUrl(TEST_PORTAL_URL_PATH))
+ server.addResponse(Request(TEST_HTTP_URL_PATH), Status.REDIRECT, headers)
setHttpsUrlDeviceConfig(makeUrl(TEST_HTTPS_URL_PATH))
setHttpUrlDeviceConfig(makeUrl(TEST_HTTP_URL_PATH))
// URL expiration needs to be in the next 10 minutes
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityFrameworkInitializerTest.kt b/tests/cts/net/src/android/net/cts/ConnectivityFrameworkInitializerTest.kt
new file mode 100644
index 0000000..d687eaa
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/ConnectivityFrameworkInitializerTest.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2021 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.cts
+
+import android.Manifest.permission.MANAGE_TEST_NETWORKS
+import android.net.ConnectivityDiagnosticsManager
+import android.net.ConnectivityFrameworkInitializer
+import android.net.ConnectivityManager
+import android.net.TestNetworkManager
+import android.os.Build
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.runAsShell
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertNotNull
+
+@RunWith(DevSdkIgnoreRunner::class)
+// ConnectivityFrameworkInitializer was added in S
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+class ConnectivityFrameworkInitializerTest {
+ @Test
+ fun testServicesRegistered() {
+ val ctx = InstrumentationRegistry.getInstrumentation().context as android.content.Context
+ assertNotNull(ctx.getSystemService(ConnectivityManager::class.java),
+ "ConnectivityManager not registered")
+ assertNotNull(ctx.getSystemService(ConnectivityDiagnosticsManager::class.java),
+ "ConnectivityDiagnosticsManager not registered")
+
+ runAsShell(MANAGE_TEST_NETWORKS) {
+ assertNotNull(ctx.getSystemService(TestNetworkManager::class.java),
+ "TestNetworkManager not registered")
+ }
+ // Do not test for DnsResolverServiceManager as it is internal to Connectivity, and
+ // CTS does not have permission to get it anyway.
+ }
+
+ // registerServiceWrappers can only be called during initialization and should throw otherwise
+ @Test(expected = IllegalStateException::class)
+ fun testThrows() {
+ ConnectivityFrameworkInitializer.registerServiceWrappers()
+ }
+}
\ No newline at end of file
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index b249abf..84430a6 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -29,6 +29,7 @@
import static android.content.pm.PackageManager.FEATURE_WIFI_DIRECT;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE;
import static android.net.ConnectivityManager.TYPE_BLUETOOTH;
import static android.net.ConnectivityManager.TYPE_ETHERNET;
import static android.net.ConnectivityManager.TYPE_MOBILE_CBS;
@@ -2136,6 +2137,38 @@
}
}
+ /**
+ * Verify that {@link ConnectivityManager#setProfileNetworkPreference} cannot be called
+ * without required NETWORK_STACK permissions.
+ */
+ @Test
+ public void testSetProfileNetworkPreference_NoPermission() {
+ // Cannot use @IgnoreUpTo(Build.VERSION_CODES.R) because this test also requires API 31
+ // shims, and @IgnoreUpTo does not check that.
+ assumeTrue(TestUtils.shouldTestSApis());
+ assertThrows(SecurityException.class, () -> mCm.setProfileNetworkPreference(
+ UserHandle.of(0), PROFILE_NETWORK_PREFERENCE_ENTERPRISE, null /* executor */,
+ null /* listener */));
+ }
+
+ @Test
+ public void testSystemReady() {
+ assumeTrue(TestUtils.shouldTestSApis());
+ assertThrows(SecurityException.class, () -> mCm.systemReady());
+ }
+
+ @Test
+ public void testGetIpSecNetIdRange() {
+ assumeTrue(TestUtils.shouldTestSApis());
+ // The lower refers to ConnectivityManager.TUN_INTF_NETID_START.
+ final long lower = 64512;
+ // The upper refers to ConnectivityManager.TUN_INTF_NETID_START
+ // + ConnectivityManager.TUN_INTF_NETID_RANGE - 1
+ final long upper = 65535;
+ assertEquals(lower, (long) ConnectivityManager.getIpSecNetIdRange().getLower());
+ assertEquals(upper, (long) ConnectivityManager.getIpSecNetIdRange().getUpper());
+ }
+
private void verifySettings(int expectedAirplaneMode, int expectedPrivateDnsMode,
int expectedAvoidBadWifi) throws Exception {
assertEquals(expectedAirplaneMode, Settings.Global.getInt(
@@ -2197,7 +2230,8 @@
// Validate when setting unmetered to metered, unmetered is lost and replaced by the
// network with the TEST transport.
setWifiMeteredStatusAndWait(ssid, true /* isMetered */);
- defaultCallback.expectCallback(CallbackEntry.LOST, wifiNetwork);
+ defaultCallback.expectCallback(CallbackEntry.LOST, wifiNetwork,
+ NETWORK_CALLBACK_TIMEOUT_MS);
waitForAvailable(defaultCallback, tnt.getNetwork());
// Depending on if this device has cellular connectivity or not, multiple available
// callbacks may be received. Eventually, metered Wi-Fi should be the final available
@@ -2207,7 +2241,8 @@
} finally {
// Validate that removing the test network will fallback to the default network.
runWithShellPermissionIdentity(tnt::teardown);
- defaultCallback.expectCallback(CallbackEntry.LOST, tnt.getNetwork());
+ defaultCallback.expectCallback(CallbackEntry.LOST, tnt.getNetwork(),
+ NETWORK_CALLBACK_TIMEOUT_MS);
waitForAvailable(defaultCallback);
setWifiMeteredStatusAndWait(ssid, oldMeteredValue);
@@ -2243,7 +2278,8 @@
waitForAvailable(systemDefaultCallback, wifiNetwork);
} finally {
runWithShellPermissionIdentity(tnt::teardown);
- defaultCallback.expectCallback(CallbackEntry.LOST, tnt.getNetwork());
+ defaultCallback.expectCallback(CallbackEntry.LOST, tnt.getNetwork(),
+ NETWORK_CALLBACK_TIMEOUT_MS);
// This network preference should only ever use the test network therefore available
// should not trigger when the test network goes down (e.g. switch to cellular).
diff --git a/tests/cts/net/src/android/net/cts/PacProxyManagerTest.java b/tests/cts/net/src/android/net/cts/PacProxyManagerTest.java
new file mode 100644
index 0000000..7d5e9ff
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/PacProxyManagerTest.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2021 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.cts;
+
+import static android.Manifest.permission.NETWORK_SETTINGS;
+
+import static com.android.testutils.TestPermissionUtil.runAsShell;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Instrumentation;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.PacProxyManager;
+import android.net.Proxy;
+import android.net.ProxyInfo;
+import android.net.Uri;
+import android.os.Build;
+import android.util.Log;
+import android.util.Range;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.DevSdkIgnoreRunner;
+import com.android.testutils.TestHttpServer;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.ServerSocket;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+import fi.iki.elonen.NanoHTTPD.Response.Status;
+
+@IgnoreUpTo(Build.VERSION_CODES.R)
+@RunWith(DevSdkIgnoreRunner.class)
+public final class PacProxyManagerTest {
+ private static final String TAG = PacProxyManagerTest.class.getSimpleName();
+ private static final int PROXY_CHANGED_BROADCAST_TIMEOUT_MS = 5000;
+ private static final int CALLBACK_TIMEOUT_MS = 3 * 1000;
+
+ private Context mContext;
+ private TestHttpServer mServer;
+ private ConnectivityManager mCm;
+ private PacProxyManager mPacProxyManager;
+ private ServerSocket mServerSocket;
+ private Instrumentation mInstrumentation;
+
+ private static final String PAC_FILE = "function FindProxyForURL(url, host)"
+ + "{"
+ + " return \"PROXY 192.168.0.1:9091\";"
+ + "}";
+
+ @Before
+ public void setUp() throws Exception {
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mContext = mInstrumentation.getContext();
+
+ mCm = mContext.getSystemService(ConnectivityManager.class);
+ mPacProxyManager = (PacProxyManager) mContext.getSystemService(PacProxyManager.class);
+ mServer = new TestHttpServer();
+ mServer.start();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mServer != null) {
+ mServer.stop();
+ mServer = null;
+ }
+ }
+
+ private class TestPacProxyInstalledListener implements
+ PacProxyManager.PacProxyInstalledListener {
+ private final CountDownLatch mLatch = new CountDownLatch(1);
+
+ public void onPacProxyInstalled(Network network, ProxyInfo proxy) {
+ Log.e(TAG, "onPacProxyInstalled is called.");
+ mLatch.countDown();
+ }
+
+ public boolean waitForCallback() throws Exception {
+ final boolean result = mLatch.await(CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ return result;
+ }
+ }
+
+ private class ProxyBroadcastReceiver extends BroadcastReceiver {
+ private final CountDownLatch mLatch = new CountDownLatch(1);
+ private final ProxyInfo mProxy;
+
+ ProxyBroadcastReceiver(ProxyInfo proxy) {
+ mProxy = proxy;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final ProxyInfo proxy = (ProxyInfo) intent.getExtra(Proxy.EXTRA_PROXY_INFO,
+ ProxyInfo.buildPacProxy(Uri.EMPTY));
+ // ProxyTracker sends sticky broadcast which will receive the last broadcast while
+ // register the intent receiver. That is, if system never receives the intent then
+ // it won't receive an intent when register the receiver. How many intents will be
+ // received in the test is unpredictable so here counts down the latch when the PAC
+ // file in the intent is the same as the one at registration.
+ if (mProxy.getPacFileUrl().equals(proxy.getPacFileUrl())) {
+ // Host/Port represent a local proxy server that redirects to the PAC-configured
+ // server. Host should be "localhost" and the port should be a value which is
+ // between 0 and 65535.
+ assertEquals(proxy.getHost(), "localhost");
+ assertInRange(proxy.getPort(), 0 /* lower */, 65535 /* upper */);
+ mLatch.countDown();
+ }
+ }
+
+ public boolean waitForProxyChanged() throws Exception {
+ final boolean result = mLatch.await(PROXY_CHANGED_BROADCAST_TIMEOUT_MS,
+ TimeUnit.MILLISECONDS);
+ return result;
+ }
+ }
+
+ @Test
+ public void testSetCurrentProxyScriptUrl() throws Exception {
+ // Register a PacProxyInstalledListener
+ final TestPacProxyInstalledListener listener = new TestPacProxyInstalledListener();
+ final Executor executor = (Runnable r) -> r.run();
+
+ runAsShell(NETWORK_SETTINGS, () -> {
+ mPacProxyManager.addPacProxyInstalledListener(executor, listener);
+ });
+
+ final Map<String, String> headers = new HashMap<String, String>();
+ headers.put("Content-Type", "application/x-ns-proxy-autoconfig");
+ final Uri pacProxyUrl = Uri.parse("http://localhost:"
+ + mServer.getListeningPort() + "/proxy.pac");
+ mServer.addResponse(pacProxyUrl, Status.OK, headers, PAC_FILE);
+
+ final ProxyInfo proxy = ProxyInfo.buildPacProxy(pacProxyUrl);
+ final ProxyBroadcastReceiver receiver = new ProxyBroadcastReceiver(proxy);
+ mContext.registerReceiver(receiver, new IntentFilter(Proxy.PROXY_CHANGE_ACTION));
+
+ // Call setCurrentProxyScriptUrl with the URL of the pac file.
+ runAsShell(NETWORK_SETTINGS, () -> {
+ mPacProxyManager.setCurrentProxyScriptUrl(proxy);
+ });
+
+ // Make sure the listener was called and testing the intent is received.
+ try {
+ assertTrue("Didn't receive PROXY_CHANGE_ACTION broadcast.",
+ receiver.waitForProxyChanged());
+ assertTrue("Did not receive onPacProxyInstalled callback.",
+ listener.waitForCallback());
+ } finally {
+ runAsShell(NETWORK_SETTINGS, () -> {
+ mPacProxyManager.removePacProxyInstalledListener(listener);
+ });
+ mContext.unregisterReceiver(receiver);
+ }
+ }
+
+ private void assertInRange(int value, int lower, int upper) {
+ final Range range = new Range(lower, upper);
+ assertTrue(value + "is not within range [" + lower + ", " + upper + "]",
+ range.contains(value));
+ }
+}
diff --git a/tests/cts/net/src/android/net/cts/ProxyTest.java b/tests/cts/net/src/android/net/cts/ProxyTest.java
deleted file mode 100644
index 467d12f..0000000
--- a/tests/cts/net/src/android/net/cts/ProxyTest.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2009 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.cts;
-
-
-import android.net.Proxy;
-import android.test.AndroidTestCase;
-
-public class ProxyTest extends AndroidTestCase {
-
- public void testConstructor() {
- new Proxy();
- }
-
- public void testAccessProperties() {
- final int minValidPort = 0;
- final int maxValidPort = 65535;
- int defaultPort = Proxy.getDefaultPort();
- if(null == Proxy.getDefaultHost()) {
- assertEquals(-1, defaultPort);
- } else {
- assertTrue(defaultPort >= minValidPort && defaultPort <= maxValidPort);
- }
- }
-}
diff --git a/tests/cts/net/src/android/net/cts/ProxyTest.kt b/tests/cts/net/src/android/net/cts/ProxyTest.kt
new file mode 100644
index 0000000..a661b26
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/ProxyTest.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2021 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.cts
+
+import android.net.ConnectivityManager
+import android.net.Proxy
+import android.net.ProxyInfo
+import android.net.Uri
+import android.os.Build
+import android.text.TextUtils
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import org.junit.Assert
+import org.junit.Assert.assertEquals
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ProxyTest {
+ @get:Rule
+ val ignoreRule = DevSdkIgnoreRule()
+
+ @Test
+ fun testConstructor() {
+ Proxy()
+ }
+
+ @Test
+ fun testAccessProperties() {
+ val minValidPort = 0
+ val maxValidPort = 65535
+ val defaultPort = Proxy.getDefaultPort()
+ if (null == Proxy.getDefaultHost()) {
+ assertEquals(-1, defaultPort.toLong())
+ } else {
+ Assert.assertTrue(defaultPort in minValidPort..maxValidPort)
+ }
+ }
+
+ private fun verifyProxySystemProperties(info: ProxyInfo) {
+ assertEquals(info.host, System.getProperty("http.proxyHost"))
+ assertEquals(info.host, System.getProperty("https.proxyHost"))
+
+ assertEquals(info.port.toString(), System.getProperty("http.proxyPort"))
+ assertEquals(info.port.toString(), System.getProperty("https.proxyPort"))
+
+ val strExcludes = if (info.exclusionList.isEmpty()) null
+ else TextUtils.join("|", info.exclusionList)
+ assertEquals(strExcludes, System.getProperty("https.nonProxyHosts"))
+ assertEquals(strExcludes, System.getProperty("http.nonProxyHosts"))
+ }
+
+ private fun getDefaultProxy(): ProxyInfo? {
+ return InstrumentationRegistry.getInstrumentation().context
+ .getSystemService(ConnectivityManager::class.java)
+ .getDefaultProxy()
+ }
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.R) // setHttpProxyConfiguration was added in S
+ fun testSetHttpProxyConfiguration_DirectProxy() {
+ val info = ProxyInfo.buildDirectProxy(
+ "testproxy.android.com",
+ 12345 /* port */,
+ listOf("testexclude1.android.com", "testexclude2.android.com"))
+ val original = getDefaultProxy()
+ try {
+ Proxy.setHttpProxyConfiguration(info)
+ verifyProxySystemProperties(info)
+ } finally {
+ Proxy.setHttpProxyConfiguration(original)
+ }
+ }
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.R) // setHttpProxyConfiguration was added in S
+ fun testSetHttpProxyConfiguration_PacProxy() {
+ val pacInfo = ProxyInfo.buildPacProxy(Uri.parse("http://testpac.android.com/pac.pac"))
+ val original = getDefaultProxy()
+ try {
+ Proxy.setHttpProxyConfiguration(pacInfo)
+ verifyProxySystemProperties(pacInfo)
+ } finally {
+ Proxy.setHttpProxyConfiguration(original)
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/cts/net/util/Android.bp b/tests/cts/net/util/Android.bp
index b5f1208..fffd30f 100644
--- a/tests/cts/net/util/Android.bp
+++ b/tests/cts/net/util/Android.bp
@@ -27,5 +27,6 @@
"junit",
"net-tests-utils",
"modules-utils-build",
+ "net-utils-framework-common",
],
}
diff --git a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
index 4abbecc..bce9880 100644
--- a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
+++ b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
@@ -56,12 +56,13 @@
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
-import android.provider.Settings;
import android.system.Os;
import android.system.OsConstants;
+import android.text.TextUtils;
import android.util.Log;
import com.android.compatibility.common.util.SystemUtil;
+import com.android.net.module.util.ConnectivitySettingsUtils;
import junit.framework.AssertionFailedError;
@@ -80,7 +81,6 @@
public final class CtsNetUtils {
private static final String TAG = CtsNetUtils.class.getSimpleName();
- private static final int DURATION = 10000;
private static final int SOCKET_TIMEOUT_MS = 2000;
private static final int PRIVATE_DNS_PROBE_MS = 1_000;
@@ -104,7 +104,7 @@
private final ContentResolver mCR;
private final WifiManager mWifiManager;
private TestNetworkCallback mCellNetworkCallback;
- private String mOldPrivateDnsMode;
+ private int mOldPrivateDnsMode = 0;
private String mOldPrivateDnsSpecifier;
public CtsNetUtils(Context context) {
@@ -508,64 +508,69 @@
}
public void storePrivateDnsSetting() {
- // Store private DNS setting
- mOldPrivateDnsMode = Settings.Global.getString(mCR, Settings.Global.PRIVATE_DNS_MODE);
- mOldPrivateDnsSpecifier = Settings.Global.getString(mCR,
- Settings.Global.PRIVATE_DNS_SPECIFIER);
- // It's possible that there is no private DNS default value in Settings.
- // Give it a proper default mode which is opportunistic mode.
- if (mOldPrivateDnsMode == null) {
- mOldPrivateDnsSpecifier = "";
- mOldPrivateDnsMode = PRIVATE_DNS_MODE_OPPORTUNISTIC;
- Settings.Global.putString(mCR,
- Settings.Global.PRIVATE_DNS_SPECIFIER, mOldPrivateDnsSpecifier);
- Settings.Global.putString(mCR, Settings.Global.PRIVATE_DNS_MODE, mOldPrivateDnsMode);
- }
+ mOldPrivateDnsMode = ConnectivitySettingsUtils.getPrivateDnsMode(mContext);
+ mOldPrivateDnsSpecifier = ConnectivitySettingsUtils.getPrivateDnsHostname(mContext);
}
public void restorePrivateDnsSetting() throws InterruptedException {
- if (mOldPrivateDnsMode == null) {
+ if (mOldPrivateDnsMode == 0) {
fail("restorePrivateDnsSetting without storing settings first");
}
- // restore private DNS setting
- if (PRIVATE_DNS_MODE_STRICT.equals(mOldPrivateDnsMode)) {
- // In case of invalid setting, set to opportunistic to avoid a bad state and fail
- if (mOldPrivateDnsSpecifier == null) {
- Settings.Global.putString(mCR, Settings.Global.PRIVATE_DNS_MODE,
- PRIVATE_DNS_MODE_OPPORTUNISTIC);
- fail("Invalid private DNS setting: no hostname specified in strict mode");
- }
- setPrivateDnsStrictMode(mOldPrivateDnsSpecifier);
- awaitPrivateDnsSetting("restorePrivateDnsSetting timeout",
- mCm.getActiveNetwork(),
- mOldPrivateDnsSpecifier, true);
- } else {
- Settings.Global.putString(mCR, Settings.Global.PRIVATE_DNS_MODE, mOldPrivateDnsMode);
+ if (mOldPrivateDnsMode != ConnectivitySettingsUtils.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME) {
+ ConnectivitySettingsUtils.setPrivateDnsMode(mContext, mOldPrivateDnsMode);
+ return;
}
+ // restore private DNS setting
+ // In case of invalid setting, set to opportunistic to avoid a bad state and fail
+ if (TextUtils.isEmpty(mOldPrivateDnsSpecifier)) {
+ ConnectivitySettingsUtils.setPrivateDnsMode(mContext,
+ ConnectivitySettingsUtils.PRIVATE_DNS_MODE_OPPORTUNISTIC);
+ fail("Invalid private DNS setting: no hostname specified in strict mode");
+ }
+ setPrivateDnsStrictMode(mOldPrivateDnsSpecifier);
+
+ // There might be a race before private DNS setting is applied and the next test is
+ // running. So waiting private DNS to be validated can reduce the flaky rate of test.
+ awaitPrivateDnsSetting("restorePrivateDnsSetting timeout",
+ mCm.getActiveNetwork(),
+ mOldPrivateDnsSpecifier, true /* requiresValidatedServer */);
}
public void setPrivateDnsStrictMode(String server) {
// To reduce flake rate, set PRIVATE_DNS_SPECIFIER before PRIVATE_DNS_MODE. This ensures
// that if the previous private DNS mode was not strict, the system only sees one
// EVENT_PRIVATE_DNS_SETTINGS_CHANGED event instead of two.
- Settings.Global.putString(mCR, Settings.Global.PRIVATE_DNS_SPECIFIER, server);
- final String mode = Settings.Global.getString(mCR, Settings.Global.PRIVATE_DNS_MODE);
+ ConnectivitySettingsUtils.setPrivateDnsHostname(mContext, server);
+ final int mode = ConnectivitySettingsUtils.getPrivateDnsMode(mContext);
// If current private DNS mode is strict, we only need to set PRIVATE_DNS_SPECIFIER.
- if (!PRIVATE_DNS_MODE_STRICT.equals(mode)) {
- Settings.Global.putString(mCR, Settings.Global.PRIVATE_DNS_MODE,
- PRIVATE_DNS_MODE_STRICT);
+ if (mode != ConnectivitySettingsUtils.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME) {
+ ConnectivitySettingsUtils.setPrivateDnsMode(mContext,
+ ConnectivitySettingsUtils.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME);
}
}
+ /**
+ * Waiting for the new private DNS setting to be validated.
+ * This method is helpful when the new private DNS setting is configured and ensure the new
+ * setting is applied and workable. It can also reduce the flaky rate when the next test is
+ * running.
+ *
+ * @param msg A message that will be printed when the validation of private DNS is timeout.
+ * @param network A network which will apply the new private DNS setting.
+ * @param server The hostname of private DNS.
+ * @param requiresValidatedServer A boolean to decide if it's needed to wait private DNS to be
+ * validated or not.
+ * @throws InterruptedException If the thread is interrupted.
+ */
public void awaitPrivateDnsSetting(@NonNull String msg, @NonNull Network network,
- @NonNull String server, boolean requiresValidatedServers) throws InterruptedException {
- CountDownLatch latch = new CountDownLatch(1);
- NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
+ @NonNull String server, boolean requiresValidatedServer) throws InterruptedException {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
NetworkCallback callback = new NetworkCallback() {
@Override
public void onLinkPropertiesChanged(Network n, LinkProperties lp) {
- if (requiresValidatedServers && lp.getValidatedPrivateDnsServers().isEmpty()) {
+ if (requiresValidatedServer && lp.getValidatedPrivateDnsServers().isEmpty()) {
return;
}
if (network.equals(n) && server.equals(lp.getPrivateDnsServerName())) {
@@ -585,7 +590,7 @@
// private DNS probe. There is no way to know when the probe has completed: because the
// network is likely already validated, there is no callback that we can listen to, so
// just sleep.
- if (requiresValidatedServers) {
+ if (requiresValidatedServer) {
Thread.sleep(PRIVATE_DNS_PROBE_MS);
}
}
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index 5eb43f3..a7f57e8 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -45,21 +45,21 @@
],
}
-android_test {
- name: "FrameworksNetTests",
+android_library {
+ name: "FrameworksNetTestsLib",
+ min_sdk_version: "30",
defaults: [
"framework-connectivity-test-defaults",
- "FrameworksNetTests-jni-defaults",
],
srcs: [
"java/**/*.java",
"java/**/*.kt",
],
- test_suites: ["device-tests"],
jarjar_rules: "jarjar-rules.txt",
static_libs: [
"androidx.test.rules",
"bouncycastle-repackaged-unbundled",
+ "core-tests-support",
"FrameworksNetCommonTests",
"frameworks-base-testutils",
"frameworks-net-integration-testutils",
@@ -78,7 +78,21 @@
"android.test.mock",
"ServiceConnectivityResources",
],
+ visibility: ["//packages/modules/Connectivity/tests:__subpackages__"],
+}
+
+android_test {
+ name: "FrameworksNetTests",
+ min_sdk_version: "30",
+ defaults: [
+ "framework-connectivity-test-defaults",
+ "FrameworksNetTests-jni-defaults",
+ ],
+ test_suites: ["device-tests"],
+ static_libs: [
+ "FrameworksNetTestsLib",
+ ],
jni_libs: [
"libservice-connectivity",
- ],
+ ]
}
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java b/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
index 93599f3..40d4446 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
@@ -43,10 +43,10 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.tests.net.R;
-import com.android.internal.util.test.FsUtil;
import libcore.io.IoUtils;
import libcore.io.Streams;
+import libcore.testing.io.TestIoUtils;
import org.junit.After;
import org.junit.Before;
@@ -70,10 +70,7 @@
@Before
public void setUp() throws Exception {
- mTestProc = new File(InstrumentationRegistry.getContext().getFilesDir(), "proc");
- if (mTestProc.exists()) {
- FsUtil.deleteContents(mTestProc);
- }
+ mTestProc = TestIoUtils.createTemporaryDirectory("proc");
// The libandroid_servers which have the native method is not available to
// applications. So in order to have a test support native library, the native code
@@ -86,10 +83,6 @@
@After
public void tearDown() throws Exception {
mFactory = null;
-
- if (mTestProc.exists()) {
- FsUtil.deleteContents(mTestProc);
- }
}
@Test
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index 3dd6598..ee94ae9 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -118,12 +118,13 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.test.BroadcastInterceptingContext;
-import com.android.internal.util.test.FsUtil;
import com.android.server.net.NetworkStatsService.NetworkStatsSettings;
import com.android.server.net.NetworkStatsService.NetworkStatsSettings.Config;
import com.android.testutils.HandlerUtils;
import com.android.testutils.TestableNetworkStatsProviderBinder;
+import libcore.testing.io.TestIoUtils;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
@@ -238,10 +239,7 @@
MockitoAnnotations.initMocks(this);
final Context context = InstrumentationRegistry.getContext();
mServiceContext = new MockContext(context);
- mStatsDir = context.getFilesDir();
- if (mStatsDir.exists()) {
- FsUtil.deleteContents(mStatsDir);
- }
+ mStatsDir = TestIoUtils.createTemporaryDirectory(getClass().getSimpleName());
PowerManager powerManager = (PowerManager) mServiceContext.getSystemService(
Context.POWER_SERVICE);
@@ -310,8 +308,6 @@
@After
public void tearDown() throws Exception {
- FsUtil.deleteContents(mStatsDir);
-
mServiceContext = null;
mStatsDir = null;