diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 697bde99a2..c78a973cee 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -355,11 +355,17 @@ public class ConnectivityManager { */ public static final int TYPE_WIFI_P2P = 13; - /** {@hide} */ - public static final int MAX_RADIO_TYPE = TYPE_WIFI_P2P; + /** + * The network to use for initially attaching to the network + * {@hide} + */ + public static final int TYPE_MOBILE_IA = 14; /** {@hide} */ - public static final int MAX_NETWORK_TYPE = TYPE_WIFI_P2P; + public static final int MAX_RADIO_TYPE = TYPE_MOBILE_IA; + + /** {@hide} */ + public static final int MAX_NETWORK_TYPE = TYPE_MOBILE_IA; /** * If you want to set the default network preference,you can directly @@ -436,6 +442,8 @@ public class ConnectivityManager { return "MOBILE_CBS"; case TYPE_WIFI_P2P: return "WIFI_P2P"; + case TYPE_MOBILE_IA: + return "MOBILE_IA"; default: return Integer.toString(type); } @@ -458,6 +466,39 @@ public class ConnectivityManager { 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; @@ -582,6 +623,29 @@ public class ConnectivityManager { } } + /** + * 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 + * + *

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. * @@ -1282,6 +1346,25 @@ public class ConnectivityManager { } } + /** + * 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. + * + *

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 * @@ -1297,70 +1380,26 @@ public class ConnectivityManager { } /** - * The ResultReceiver resultCode for checkMobileProvisioning (CMP_RESULT_CODE) - */ - - /** - * No connection was possible to the network. - * {@hide} - */ - public static final int CMP_RESULT_CODE_NO_CONNECTION = 0; - - /** - * A connection was made to the internet, all is well. - * {@hide} - */ - public static final int CMP_RESULT_CODE_CONNECTABLE = 1; - - /** - * 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. - * {@hide} - */ - public static final int CMP_RESULT_CODE_REDIRECTED = 2; - - /** - * A connection was made but no dns server was available to resolve a name to address. - * This is an indication of a warm sim on a mobile network. + * Check mobile provisioning. * - * {@hide} - */ - public static final int CMP_RESULT_CODE_NO_DNS = 3; - - /** - * A connection was made but could not open a TCP connection. - * This is an indication of a warm sim on a mobile network. - * {@hide} - */ - public static final int CMP_RESULT_CODE_NO_TCP_CONNECTION = 4; - - /** - * Check mobile provisioning. The resultCode passed to - * onReceiveResult will be one of the CMP_RESULT_CODE_xxxx values above. - * This may take a minute or more to complete. - * - * @param sendNotificaiton, when true a notification will be sent to user. * @param suggestedTimeOutMs, timeout in milliseconds - * @param resultReceiver needs to be supplied to receive the result * * @return time out that will be used, maybe less that suggestedTimeOutMs * -1 if an error. * * {@hide} */ - public int checkMobileProvisioning(boolean sendNotification, int suggestedTimeOutMs, - ResultReceiver resultReceiver) { + public int checkMobileProvisioning(int suggestedTimeOutMs) { int timeOutMs = -1; try { - timeOutMs = mService.checkMobileProvisioning(sendNotification, suggestedTimeOutMs, - resultReceiver); + timeOutMs = mService.checkMobileProvisioning(suggestedTimeOutMs); } catch (RemoteException e) { } return timeOutMs; } /** - * Get the carrier provisioning url. + * Get the mobile provisioning url. * {@hide} */ public String getMobileProvisioningUrl() { @@ -1370,4 +1409,87 @@ public class ConnectivityManager { } 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 + * + *

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) { + } + } } diff --git a/core/java/android/net/DhcpInfo.java b/core/java/android/net/DhcpInfo.java index ab4cd9b9ef..3bede5da50 100644 --- a/core/java/android/net/DhcpInfo.java +++ b/core/java/android/net/DhcpInfo.java @@ -22,7 +22,6 @@ import java.net.InetAddress; /** * A simple object for retrieving the results of a DHCP request. - * @deprecated - use LinkProperties - To be removed 11/2014 */ public class DhcpInfo implements Parcelable { public int ipAddress; diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index 4600c1a4dc..c1da2e32aa 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -16,6 +16,7 @@ package android.net; +import android.net.LinkQualityInfo; import android.net.LinkProperties; import android.net.NetworkInfo; import android.net.NetworkQuotaInfo; @@ -37,6 +38,9 @@ import com.android.internal.net.VpnProfile; /** {@hide} */ interface IConnectivityManager { + // Keep this in sync with framework/native/services/connectivitymanager/ConnectivityManager.h + void markSocketAsUser(in ParcelFileDescriptor socket, int uid); + void setNetworkPreference(int pref); int getNetworkPreference(); @@ -46,6 +50,8 @@ interface IConnectivityManager NetworkInfo getNetworkInfo(int networkType); NetworkInfo[] getAllNetworkInfo(); + NetworkInfo getProvisioningOrActiveNetworkInfo(); + boolean isNetworkSupported(int networkType); LinkProperties getActiveLinkProperties(); @@ -87,12 +93,6 @@ interface IConnectivityManager String[] getTetheredIfaces(); - /** - * Return list of interface pairs that are actively tethered. Even indexes are - * remote interface, and odd indexes are corresponding local interfaces. - */ - String[] getTetheredIfacePairs(); - String[] getTetheringErroredIfaces(); String[] getTetherableUsbRegexs(); @@ -121,6 +121,8 @@ interface IConnectivityManager ParcelFileDescriptor establishVpn(in VpnConfig config); + VpnConfig getVpnConfig(); + void startLegacyVpn(in VpnProfile profile); LegacyVpnInfo getLegacyVpnInfo(); @@ -129,11 +131,25 @@ interface IConnectivityManager void captivePortalCheckComplete(in NetworkInfo info); + void captivePortalCheckCompleted(in NetworkInfo info, boolean isCaptivePortal); + void supplyMessenger(int networkType, in Messenger messenger); int findConnectionTypeForIface(in String iface); - int checkMobileProvisioning(boolean sendNotification, int suggestedTimeOutMs, in ResultReceiver resultReceiver); + 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); } diff --git a/core/java/android/net/LinkAddress.java b/core/java/android/net/LinkAddress.java index f6a114c834..a390add89e 100644 --- a/core/java/android/net/LinkAddress.java +++ b/core/java/android/net/LinkAddress.java @@ -32,27 +32,56 @@ public class LinkAddress implements Parcelable { /** * IPv4 or IPv6 address. */ - private final InetAddress address; + private InetAddress address; /** * Network prefix length */ - private final int prefixLength; + private int prefixLength; - public LinkAddress(InetAddress address, int prefixLength) { + private void init(InetAddress address, int prefixLength) { if (address == null || prefixLength < 0 || ((address instanceof Inet4Address) && prefixLength > 32) || (prefixLength > 128)) { throw new IllegalArgumentException("Bad LinkAddress params " + address + - prefixLength); + "/" + prefixLength); } this.address = address; this.prefixLength = prefixLength; } + public LinkAddress(InetAddress address, int prefixLength) { + init(address, prefixLength); + } + public LinkAddress(InterfaceAddress interfaceAddress) { - this.address = interfaceAddress.getAddress(); - this.prefixLength = interfaceAddress.getNetworkPrefixLength(); + init(interfaceAddress.getAddress(), + interfaceAddress.getNetworkPrefixLength()); + } + + /** + * Constructs a new {@code LinkAddress} from a string such as "192.0.2.5/24" or + * "2001:db8::1/64". + * @param string The string to parse. + */ + public LinkAddress(String address) { + 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); } @Override diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java index 75f8b5948b..b4d07a1ec2 100644 --- a/core/java/android/net/LinkProperties.java +++ b/core/java/android/net/LinkProperties.java @@ -23,6 +23,7 @@ import android.text.TextUtils; import java.net.InetAddress; import java.net.Inet4Address; +import java.net.Inet6Address; import java.net.UnknownHostException; import java.util.ArrayList; @@ -65,6 +66,7 @@ public class LinkProperties implements Parcelable { private String mDomains; private Collection mRoutes = new ArrayList(); private ProxyProperties 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. @@ -103,6 +105,7 @@ public class LinkProperties implements Parcelable { for (LinkProperties l: source.mStackedLinks.values()) { addStackedLink(l); } + setMtu(source.getMtu()); } } @@ -128,6 +131,9 @@ public class LinkProperties implements Parcelable { return interfaceNames; } + /** + * Returns all the addresses on this link. + */ public Collection getAddresses() { Collection addresses = new ArrayList(); for (LinkAddress linkAddress : mLinkAddresses) { @@ -136,14 +142,73 @@ public class LinkProperties implements Parcelable { return Collections.unmodifiableCollection(addresses); } - public void addLinkAddress(LinkAddress address) { - if (address != null) mLinkAddresses.add(address); + /** + * Returns all the addresses on this link and all the links stacked above it. + */ + public Collection getAllAddresses() { + Collection addresses = new ArrayList(); + for (LinkAddress linkAddress : mLinkAddresses) { + addresses.add(linkAddress.getAddress()); + } + for (LinkProperties stacked: mStackedLinks.values()) { + addresses.addAll(stacked.getAllAddresses()); + } + return addresses; } + /** + * Adds a link address if it does not exist, or update it if it does. + * @param address The {@code LinkAddress} to add. + * @return true if the address was added, false if it already existed. + */ + public boolean addLinkAddress(LinkAddress address) { + // TODO: when the LinkAddress has other attributes beyond the + // address and the prefix length, update them here. + if (address != null && !mLinkAddresses.contains(address)) { + mLinkAddresses.add(address); + return true; + } + return false; + } + + /** + * Removes a link address. + * @param address The {@code LinkAddress} to remove. + * @return true if the address was removed, false if it did not exist. + */ + public boolean removeLinkAddress(LinkAddress toRemove) { + return mLinkAddresses.remove(toRemove); + } + + /** + * Returns all the addresses on this link. + */ public Collection getLinkAddresses() { return Collections.unmodifiableCollection(mLinkAddresses); } + /** + * Returns all the addresses on this link and all the links stacked above it. + */ + public Collection getAllLinkAddresses() { + Collection addresses = new ArrayList(); + addresses.addAll(mLinkAddresses); + for (LinkProperties stacked: mStackedLinks.values()) { + addresses.addAll(stacked.getAllLinkAddresses()); + } + return addresses; + } + + /** + * Replaces the LinkAddresses on this link with the given collection of addresses. + */ + public void setLinkAddresses(Collection addresses) { + mLinkAddresses.clear(); + for (LinkAddress address: addresses) { + addLinkAddress(address); + } + } + public void addDns(InetAddress dns) { if (dns != null) mDnses.add(dns); } @@ -160,6 +225,14 @@ public class LinkProperties implements Parcelable { mDomains = domains; } + public void setMtu(int mtu) { + mMtu = mtu; + } + + public int getMtu() { + return mMtu; + } + private RouteInfo routeWithInterface(RouteInfo route) { return new RouteInfo( route.getDestination(), @@ -213,11 +286,14 @@ public class LinkProperties implements Parcelable { * of stacked links. If link is null, nothing changes. * * @param link The link to add. + * @return true if the link was stacked, false otherwise. */ - public void addStackedLink(LinkProperties link) { + public boolean addStackedLink(LinkProperties link) { if (link != null && link.getInterfaceName() != null) { mStackedLinks.put(link.getInterfaceName(), link); + return true; } + return false; } /** @@ -226,12 +302,15 @@ public class LinkProperties implements Parcelable { * If there a stacked link with the same interfacename as link, it is * removed. Otherwise, nothing changes. * - * @param link The link to add. + * @param link The link to remove. + * @return true if the link was removed, false otherwise. */ - public void removeStackedLink(LinkProperties link) { + public boolean removeStackedLink(LinkProperties link) { if (link != null && link.getInterfaceName() != null) { - mStackedLinks.remove(link.getInterfaceName()); + LinkProperties removed = mStackedLinks.remove(link.getInterfaceName()); + return removed != null; } + return false; } /** @@ -253,6 +332,7 @@ public class LinkProperties implements Parcelable { mRoutes.clear(); mHttpProxy = null; mStackedLinks.clear(); + mMtu = 0; } /** @@ -277,6 +357,8 @@ public class LinkProperties implements Parcelable { String domainName = "Domains: " + mDomains; + String mtu = "MTU: " + mMtu; + String routes = " Routes: ["; for (RouteInfo route : mRoutes) routes += route.toString() + ","; routes += "] "; @@ -290,7 +372,8 @@ public class LinkProperties implements Parcelable { } stacked += "] "; } - return "{" + ifaceName + linkAddresses + routes + dns + domainName + proxy + stacked + "}"; + return "{" + ifaceName + linkAddresses + routes + dns + domainName + mtu + + proxy + stacked + "}"; } /** @@ -307,6 +390,20 @@ public class LinkProperties implements Parcelable { return false; } + /** + * Returns true if this link has an IPv6 address. + * + * @return {@code true} if there is an IPv6 address, {@code false} otherwise. + */ + 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 * @@ -391,6 +488,16 @@ public class LinkProperties implements Parcelable { 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. + */ + public boolean isIdenticalMtu(LinkProperties target) { + return getMtu() == target.getMtu(); + } + @Override /** * Compares this {@code LinkProperties} instance against the target @@ -422,17 +529,16 @@ public class LinkProperties implements Parcelable { isIdenticalDnses(target) && isIdenticalRoutes(target) && isIdenticalHttpProxy(target) && - isIdenticalStackedLinks(target); + isIdenticalStackedLinks(target) && + isIdenticalMtu(target); } /** - * Return two lists, a list of addresses that would be removed from - * mLinkAddresses and a list of addresses that would be added to - * mLinkAddress which would then result in target and mLinkAddresses - * being the same list. + * Compares the addresses in this LinkProperties with another + * LinkProperties, examining only addresses on the base link. * - * @param target is a LinkProperties with the new list of addresses - * @return the removed and added lists. + * @param target a LinkProperties with the new list of addresses + * @return the differences between the addresses. */ public CompareResult compareAddresses(LinkProperties target) { /* @@ -456,13 +562,11 @@ public class LinkProperties implements Parcelable { } /** - * Return two lists, a list of dns addresses that would be removed from - * mDnses and a list of addresses that would be added to - * mDnses which would then result in target and mDnses - * being the same list. + * Compares the DNS addresses in this LinkProperties with another + * LinkProperties, examining only DNS addresses on the base link. * - * @param target is a LinkProperties with the new list of dns addresses - * @return the removed and added lists. + * @param target a LinkProperties with the new list of dns addresses + * @return the differences between the DNS addresses. */ public CompareResult compareDnses(LinkProperties target) { /* @@ -487,15 +591,13 @@ public class LinkProperties implements Parcelable { } /** - * Return two lists, a list of routes that would be removed from - * mRoutes and a list of routes that would be added to - * mRoutes which would then result in target and mRoutes - * being the same list. + * Compares all routes in this LinkProperties with another LinkProperties, + * examining both the the base link and all stacked links. * - * @param target is a LinkProperties with the new list of routes - * @return the removed and added lists. + * @param target a LinkProperties with the new list of routes + * @return the differences between the routes. */ - public CompareResult compareRoutes(LinkProperties target) { + public CompareResult compareAllRoutes(LinkProperties target) { /* * Duplicate the RouteInfos into removed, we will be removing * routes which are common between mRoutes and target @@ -530,7 +632,8 @@ public class LinkProperties implements Parcelable { + ((null == mDomains) ? 0 : mDomains.hashCode()) + mRoutes.size() * 41 + ((null == mHttpProxy) ? 0 : mHttpProxy.hashCode()) - + mStackedLinks.hashCode() * 47); + + mStackedLinks.hashCode() * 47) + + mMtu * 51; } /** @@ -548,7 +651,7 @@ public class LinkProperties implements Parcelable { dest.writeByteArray(d.getAddress()); } dest.writeString(mDomains); - + dest.writeInt(mMtu); dest.writeInt(mRoutes.size()); for(RouteInfo route : mRoutes) { dest.writeParcelable(route, flags); @@ -587,6 +690,7 @@ public class LinkProperties implements Parcelable { } catch (UnknownHostException e) { } } netProp.setDomains(in.readString()); + netProp.setMtu(in.readInt()); addressCount = in.readInt(); for (int i=0; i #include #include @@ -36,7 +37,8 @@ int dhcp_do_request(const char * const ifname, const char *server, uint32_t *lease, const char *vendorInfo, - const char *domains); + const char *domains, + const char *mtu); int dhcp_do_request_renew(const char * const ifname, const char *ipaddr, @@ -46,7 +48,8 @@ int dhcp_do_request_renew(const char * const ifname, const char *server, uint32_t *lease, const char *vendorInfo, - const char *domains); + const char *domains, + const char *mtu); int dhcp_stop(const char *ifname); int dhcp_release_lease(const char *ifname); @@ -125,19 +128,20 @@ static jboolean android_net_utils_runDhcpCommon(JNIEnv* env, jobject clazz, jstr 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); + dns, server, &lease, vendorInfo, domains, mtu); } else { result = ::dhcp_do_request(nameStr, ipaddr, gateway, &prefixLength, - dns, server, &lease, vendorInfo, domains); + dns, server, &lease, vendorInfo, domains, mtu); } if (result != 0) { - ALOGD("dhcp_do_request failed"); + ALOGD("dhcp_do_request failed : %s (%s)", nameStr, renew ? "renew" : "new"); } env->ReleaseStringUTFChars(ifname, nameStr); @@ -239,6 +243,13 @@ 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"); + } +} + // ---------------------------------------------------------------------------- /* @@ -255,6 +266,7 @@ static JNINativeMethod gNetworkUtilMethods[] = { { "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 }, }; int register_android_net_NetworkUtils(JNIEnv* env) diff --git a/core/tests/coretests/src/android/net/LinkPropertiesTest.java b/core/tests/coretests/src/android/net/LinkPropertiesTest.java index d6a7ee27a2..e63f6b08f6 100644 --- a/core/tests/coretests/src/android/net/LinkPropertiesTest.java +++ b/core/tests/coretests/src/android/net/LinkPropertiesTest.java @@ -25,13 +25,18 @@ import java.net.InetAddress; import java.util.ArrayList; public class LinkPropertiesTest extends TestCase { - private static String ADDRV4 = "75.208.6.1"; - private static String ADDRV6 = "2001:0db8:85a3:0000:0000:8a2e:0370:7334"; - private static String DNS1 = "75.208.7.1"; - private static String DNS2 = "69.78.7.1"; - private static String GATEWAY1 = "75.208.8.1"; - private static String GATEWAY2 = "69.78.8.1"; + 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. @@ -53,6 +58,9 @@ public class LinkPropertiesTest extends TestCase { 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)); @@ -76,43 +84,40 @@ public class LinkPropertiesTest extends TestCase { LinkProperties source = new LinkProperties(); source.setInterfaceName(NAME); // set 2 link addresses - source.addLinkAddress(new LinkAddress( - NetworkUtils.numericToInetAddress(ADDRV4), 32)); - source.addLinkAddress(new LinkAddress( - NetworkUtils.numericToInetAddress(ADDRV6), 128)); + source.addLinkAddress(LINKADDRV4); + source.addLinkAddress(LINKADDRV6); // set 2 dnses - source.addDns(NetworkUtils.numericToInetAddress(DNS1)); - source.addDns(NetworkUtils.numericToInetAddress(DNS2)); + source.addDns(DNS1); + source.addDns(DNS2); // set 2 gateways - source.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress(GATEWAY1))); - source.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress(GATEWAY2))); + 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(new LinkAddress( - NetworkUtils.numericToInetAddress(ADDRV4), 32)); - target.addLinkAddress(new LinkAddress( - NetworkUtils.numericToInetAddress(ADDRV6), 128)); - target.addDns(NetworkUtils.numericToInetAddress(DNS1)); - target.addDns(NetworkUtils.numericToInetAddress(DNS2)); - target.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress(GATEWAY1))); - target.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress(GATEWAY2))); + target.addLinkAddress(LINKADDRV4); + target.addLinkAddress(LINKADDRV6); + target.addDns(DNS1); + target.addDns(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(new LinkAddress( - NetworkUtils.numericToInetAddress(ADDRV4), 32)); - target.addLinkAddress(new LinkAddress( - NetworkUtils.numericToInetAddress(ADDRV6), 128)); - target.addDns(NetworkUtils.numericToInetAddress(DNS1)); - target.addDns(NetworkUtils.numericToInetAddress(DNS2)); - target.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress(GATEWAY1))); - target.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress(GATEWAY2))); + target.addLinkAddress(LINKADDRV4); + target.addLinkAddress(LINKADDRV6); + target.addDns(DNS1); + target.addDns(DNS2); + target.addRoute(new RouteInfo(GATEWAY1)); + target.addRoute(new RouteInfo(GATEWAY2)); + target.setMtu(MTU); assertFalse(source.equals(target)); target.clear(); @@ -120,38 +125,48 @@ public class LinkPropertiesTest extends TestCase { // change link addresses target.addLinkAddress(new LinkAddress( NetworkUtils.numericToInetAddress("75.208.6.2"), 32)); - target.addLinkAddress(new LinkAddress( - NetworkUtils.numericToInetAddress(ADDRV6), 128)); - target.addDns(NetworkUtils.numericToInetAddress(DNS1)); - target.addDns(NetworkUtils.numericToInetAddress(DNS2)); - target.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress(GATEWAY1))); - target.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress(GATEWAY2))); + target.addLinkAddress(LINKADDRV6); + target.addDns(DNS1); + target.addDns(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(new LinkAddress( - NetworkUtils.numericToInetAddress(ADDRV4), 32)); - target.addLinkAddress(new LinkAddress( - NetworkUtils.numericToInetAddress(ADDRV6), 128)); + target.addLinkAddress(LINKADDRV4); + target.addLinkAddress(LINKADDRV6); // change dnses target.addDns(NetworkUtils.numericToInetAddress("75.208.7.2")); - target.addDns(NetworkUtils.numericToInetAddress(DNS2)); - target.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress(GATEWAY1))); - target.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress(GATEWAY2))); + target.addDns(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(new LinkAddress( - NetworkUtils.numericToInetAddress(ADDRV4), 32)); - target.addLinkAddress(new LinkAddress( - NetworkUtils.numericToInetAddress(ADDRV6), 128)); - target.addDns(NetworkUtils.numericToInetAddress(DNS1)); - target.addDns(NetworkUtils.numericToInetAddress(DNS2)); + target.addLinkAddress(LINKADDRV4); + target.addLinkAddress(LINKADDRV6); + target.addDns(DNS1); + target.addDns(DNS2); // change gateway target.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress("75.208.8.2"))); - target.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress(GATEWAY2))); + target.addRoute(new RouteInfo(GATEWAY2)); + target.setMtu(MTU); + assertFalse(source.equals(target)); + + target.clear(); + target.setInterfaceName(NAME); + target.addLinkAddress(LINKADDRV4); + target.addLinkAddress(LINKADDRV6); + target.addDns(DNS1); + target.addDns(DNS2); + target.addRoute(new RouteInfo(GATEWAY1)); + target.addRoute(new RouteInfo(GATEWAY2)); + // change mtu + target.setMtu(1440); assertFalse(source.equals(target)); } catch (Exception e) { @@ -166,28 +181,26 @@ public class LinkPropertiesTest extends TestCase { LinkProperties source = new LinkProperties(); source.setInterfaceName(NAME); // set 2 link addresses - source.addLinkAddress(new LinkAddress( - NetworkUtils.numericToInetAddress(ADDRV4), 32)); - source.addLinkAddress(new LinkAddress( - NetworkUtils.numericToInetAddress(ADDRV6), 128)); + source.addLinkAddress(LINKADDRV4); + source.addLinkAddress(LINKADDRV6); // set 2 dnses - source.addDns(NetworkUtils.numericToInetAddress(DNS1)); - source.addDns(NetworkUtils.numericToInetAddress(DNS2)); + source.addDns(DNS1); + source.addDns(DNS2); // set 2 gateways - source.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress(GATEWAY1))); - source.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress(GATEWAY2))); + source.addRoute(new RouteInfo(GATEWAY1)); + source.addRoute(new RouteInfo(GATEWAY2)); + source.setMtu(MTU); LinkProperties target = new LinkProperties(); // Exchange order target.setInterfaceName(NAME); - target.addLinkAddress(new LinkAddress( - NetworkUtils.numericToInetAddress(ADDRV6), 128)); - target.addLinkAddress(new LinkAddress( - NetworkUtils.numericToInetAddress(ADDRV4), 32)); - target.addDns(NetworkUtils.numericToInetAddress(DNS2)); - target.addDns(NetworkUtils.numericToInetAddress(DNS1)); - target.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress(GATEWAY2))); - target.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress(GATEWAY1))); + target.addLinkAddress(LINKADDRV6); + target.addLinkAddress(LINKADDRV4); + target.addDns(DNS2); + target.addDns(DNS1); + target.addRoute(new RouteInfo(GATEWAY2)); + target.addRoute(new RouteInfo(GATEWAY1)); + target.setMtu(MTU); assertLinkPropertiesEqual(source, target); } catch (Exception e) { @@ -200,21 +213,15 @@ public class LinkPropertiesTest extends TestCase { try { LinkProperties source = new LinkProperties(); // set 3 link addresses, eg, [A, A, B] - source.addLinkAddress(new LinkAddress( - NetworkUtils.numericToInetAddress(ADDRV4), 32)); - source.addLinkAddress(new LinkAddress( - NetworkUtils.numericToInetAddress(ADDRV4), 32)); - source.addLinkAddress(new LinkAddress( - NetworkUtils.numericToInetAddress(ADDRV6), 128)); + source.addLinkAddress(LINKADDRV4); + source.addLinkAddress(LINKADDRV4); + source.addLinkAddress(LINKADDRV6); LinkProperties target = new LinkProperties(); // set 3 link addresses, eg, [A, B, B] - target.addLinkAddress(new LinkAddress( - NetworkUtils.numericToInetAddress(ADDRV4), 32)); - target.addLinkAddress(new LinkAddress( - NetworkUtils.numericToInetAddress(ADDRV6), 128)); - target.addLinkAddress(new LinkAddress( - NetworkUtils.numericToInetAddress(ADDRV6), 128)); + target.addLinkAddress(LINKADDRV4); + target.addLinkAddress(LINKADDRV6); + target.addLinkAddress(LINKADDRV6); assertLinkPropertiesEqual(source, target); } catch (Exception e) { @@ -232,7 +239,7 @@ public class LinkPropertiesTest extends TestCase { public void testRouteInterfaces() { LinkAddress prefix = new LinkAddress( NetworkUtils.numericToInetAddress("2001:db8::"), 32); - InetAddress address = NetworkUtils.numericToInetAddress(ADDRV6); + InetAddress address = ADDRV6; // Add a route with no interface to a LinkProperties with no interface. No errors. LinkProperties lp = new LinkProperties(); @@ -265,7 +272,7 @@ public class LinkPropertiesTest extends TestCase { assertAllRoutesHaveInterface("wlan0", lp); // Routes with null interfaces are converted to wlan0. - r = RouteInfo.makeHostRoute(NetworkUtils.numericToInetAddress(ADDRV6), null); + r = RouteInfo.makeHostRoute(ADDRV6, null); lp.addRoute(r); assertEquals(3, lp.getRoutes().size()); assertAllRoutesHaveInterface("wlan0", lp); @@ -273,28 +280,45 @@ public class LinkPropertiesTest extends TestCase { // Check comparisons work. LinkProperties lp2 = new LinkProperties(lp); assertAllRoutesHaveInterface("wlan0", lp); - assertEquals(0, lp.compareRoutes(lp2).added.size()); - assertEquals(0, lp.compareRoutes(lp2).removed.size()); + assertEquals(0, lp.compareAllRoutes(lp2).added.size()); + assertEquals(0, lp.compareAllRoutes(lp2).removed.size()); lp2.setInterfaceName("p2p0"); assertAllRoutesHaveInterface("p2p0", lp2); - assertEquals(3, lp.compareRoutes(lp2).added.size()); - assertEquals(3, lp.compareRoutes(lp2).removed.size()); + 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. @@ -306,5 +330,76 @@ public class LinkPropertiesTest extends TestCase { 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)); + } + + @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. + assertTrue(lp.addLinkAddress(LINKADDRV6)); + assertFalse(lp.hasIPv4Address()); + assertTrue(lp.hasIPv6Address()); + + assertTrue(lp.removeLinkAddress(LINKADDRV6)); + assertTrue(lp.addLinkAddress(LINKADDRV4)); + assertTrue(lp.hasIPv4Address()); + assertFalse(lp.hasIPv6Address()); + + assertTrue(lp.addLinkAddress(LINKADDRV6)); + 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)); + assertTrue(lp.hasIPv4Address()); + assertTrue(lp.removeLinkAddress(LINKADDRV4)); + assertFalse(lp.hasIPv4Address()); + assertFalse(lp.removeLinkAddress(LINKADDRV4)); + } + + @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/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index 8c388edb14..a9b4f19f7a 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -31,10 +31,12 @@ 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.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; @@ -56,13 +58,12 @@ import android.net.INetworkPolicyManager; import android.net.INetworkStatsService; import android.net.LinkAddress; import android.net.LinkProperties; -import android.net.Uri; import android.net.LinkProperties.CompareResult; +import android.net.LinkQualityInfo; import android.net.MobileDataStateTracker; import android.net.NetworkConfig; import android.net.NetworkInfo; import android.net.NetworkInfo.DetailedState; -import android.net.NetworkInfo.State; import android.net.NetworkQuotaInfo; import android.net.NetworkState; import android.net.NetworkStateTracker; @@ -70,6 +71,8 @@ import android.net.NetworkUtils; import android.net.Proxy; import android.net.ProxyProperties; import android.net.RouteInfo; +import android.net.SamplingDataTracker; +import android.net.Uri; import android.net.wifi.WifiStateTracker; import android.net.wimax.WimaxManagerConstants; import android.os.AsyncTask; @@ -86,7 +89,6 @@ import android.os.ParcelFileDescriptor; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; -import android.os.ResultReceiver; import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; @@ -97,10 +99,12 @@ 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; @@ -110,7 +114,9 @@ import com.android.internal.telephony.PhoneConstants; 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.PacManager; import com.android.server.connectivity.Tethering; import com.android.server.connectivity.Vpn; import com.android.server.net.BaseNetworkObserver; @@ -140,9 +146,12 @@ 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; /** @@ -170,6 +179,23 @@ public class ConnectivityService extends IConnectivityManager.Stub { 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 = (VDBG ? 30 : 12 * 60); + + // start network sampling a minute after booting ... + private static final int DEFAULT_START_SAMPLING_INTERVAL_IN_SECONDS = (VDBG ? 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; @@ -178,7 +204,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { private KeyStore mKeyStore; - private Vpn mVpn; + @GuardedBy("mVpns") + private final SparseArray mVpns = new SparseArray(); private VpnCallback mVpnCallback = new VpnCallback(); private boolean mLockdownEnabled; @@ -230,7 +257,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { private Object mDnsLock = new Object(); private int mNumDnsEntries; - private boolean mDnsOverridden = false; private boolean mTestMode; private static ConnectivityService sServiceInstance; @@ -247,6 +273,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { 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 @@ -301,29 +330,33 @@ public class ConnectivityService extends IConnectivityManager.Stub { */ private static final int EVENT_SET_DEPENDENCY_MET = 10; - /** - * used internally to restore DNS properties back to the - * default network - */ - private static final int EVENT_RESTORE_DNS = 11; - /** * used internally to send a sticky broadcast delayed. */ - private static final int EVENT_SEND_STICKY_BROADCAST_INTENT = 12; + 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 = 13; + private static final int EVENT_SET_POLICY_DATA_ENABLE = 12; - private static final int EVENT_VPN_STATE_CHANGED = 14; + 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 = 15; + private static final int EVENT_ENABLE_FAIL_FAST_MOBILE_DATA = 14; + + /** + * user 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; /** Handler used for internal events. */ private InternalHandler mHandler; @@ -344,10 +377,19 @@ public class ConnectivityService extends IConnectivityManager.Stub { 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 mAddedRoutes = new ArrayList(); + // this collection corresponds to the entries of mAddedRoutes that have routing exemptions + // used to handle cleanup of exempt rules + @GuardedBy("mRoutesLock") + private Collection mExemptAddresses = new ArrayList(); + // used in DBG mode to track inet condition reports private static final int INET_CONDITION_LOG_MAX_SIZE = 15; private ArrayList mInetLog; @@ -360,6 +402,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { // track the global proxy. private ProxyProperties mGlobalProxy = null; + private PacManager mPacManager = null; + private SettingsObserver mSettingsObserver; NetworkConfig[] mNetConfigs; @@ -379,13 +423,12 @@ public class ConnectivityService extends IConnectivityManager.Stub { // 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; - // We only want one checkMobileProvisioning after booting. - volatile boolean mFirstProvisioningCheckStarted = false; - public ConnectivityService(Context context, INetworkManagementService netd, INetworkStatsService statsService, INetworkPolicyManager policyManager) { // Currently, omitting a NetworkFactory will create one internally @@ -461,6 +504,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { 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; @@ -481,6 +525,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { 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); @@ -507,6 +552,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { // ignore it - leave the entry null } } + if (VDBG) log("mNetworksDefined=" + mNetworksDefined); mProtectedNetworks = new ArrayList(); int[] protectedNetworks = context.getResources().getIntArray( @@ -589,9 +635,12 @@ public class ConnectivityService extends IConnectivityManager.Stub { mTethering = new Tethering(mContext, mNetd, statsService, this, mHandler.getLooper()); - mVpn = new Vpn(mContext, mVpnCallback, mNetd, this); - mVpn.startMonitoring(mContext, mTrackerHandler); - + //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 { @@ -609,8 +658,37 @@ public class ConnectivityService extends IConnectivityManager.Stub { mSettingsObserver = new SettingsObserver(mHandler, EVENT_APPLY_GLOBAL_HTTP_PROXY); mSettingsObserver.observe(mContext); - mCaptivePortalTracker = CaptivePortalTracker.makeCaptivePortalTracker(mContext, this); - loadGlobalProxy(); + 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); } /** @@ -879,6 +957,45 @@ public class ConnectivityService extends IConnectivityManager.Stub { return getNetworkInfo(mActiveDefaultNetwork, uid); } + /** + * 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)) { @@ -1241,8 +1358,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { 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 { @@ -1254,9 +1373,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { 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) { @@ -1290,11 +1411,12 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } 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 - not a live request, ignoring"); + if (VDBG) log("stopUsingNetworkFeature: X not a live request, ignoring"); return 1; } } @@ -1461,7 +1583,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { try { InetAddress addr = InetAddress.getByAddress(hostAddress); LinkProperties lp = tracker.getLinkProperties(); - boolean ok = addRouteToAddress(lp, addr); + boolean ok = addRouteToAddress(lp, addr, EXEMPT); if (DBG) log("requestRouteToHostAddress ok=" + ok); return ok; } catch (UnknownHostException e) { @@ -1473,24 +1595,25 @@ public class ConnectivityService extends IConnectivityManager.Stub { return false; } - private boolean addRoute(LinkProperties p, RouteInfo r, boolean toDefaultTable) { - return modifyRoute(p, r, 0, ADD, toDefaultTable); + private boolean addRoute(LinkProperties p, RouteInfo r, boolean toDefaultTable, + boolean exempt) { + return modifyRoute(p, r, 0, ADD, toDefaultTable, exempt); } private boolean removeRoute(LinkProperties p, RouteInfo r, boolean toDefaultTable) { - return modifyRoute(p, r, 0, REMOVE, toDefaultTable); + return modifyRoute(p, r, 0, REMOVE, toDefaultTable, UNEXEMPT); } - private boolean addRouteToAddress(LinkProperties lp, InetAddress addr) { - return modifyRouteToAddress(lp, addr, ADD, TO_DEFAULT_TABLE); + private boolean addRouteToAddress(LinkProperties lp, InetAddress addr, boolean exempt) { + return modifyRouteToAddress(lp, addr, ADD, TO_DEFAULT_TABLE, exempt); } private boolean removeRouteToAddress(LinkProperties lp, InetAddress addr) { - return modifyRouteToAddress(lp, addr, REMOVE, TO_DEFAULT_TABLE); + return modifyRouteToAddress(lp, addr, REMOVE, TO_DEFAULT_TABLE, UNEXEMPT); } private boolean modifyRouteToAddress(LinkProperties lp, InetAddress addr, boolean doAdd, - boolean toDefaultTable) { + boolean toDefaultTable, boolean exempt) { RouteInfo bestRoute = RouteInfo.selectBestRoute(lp.getAllRoutes(), addr); if (bestRoute == null) { bestRoute = RouteInfo.makeHostRoute(addr, lp.getInterfaceName()); @@ -1505,11 +1628,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { bestRoute = RouteInfo.makeHostRoute(addr, bestRoute.getGateway(), iface); } } - return modifyRoute(lp, bestRoute, 0, doAdd, toDefaultTable); + return modifyRoute(lp, bestRoute, 0, doAdd, toDefaultTable, exempt); } private boolean modifyRoute(LinkProperties lp, RouteInfo r, int cycleCount, boolean doAdd, - boolean toDefaultTable) { + boolean toDefaultTable, boolean exempt) { if ((lp == null) || (r == null)) { if (DBG) log("modifyRoute got unexpected null: " + lp + ", " + r); return false; @@ -1538,15 +1661,25 @@ public class ConnectivityService extends IConnectivityManager.Stub { bestRoute.getGateway(), ifaceName); } - modifyRoute(lp, bestRoute, cycleCount+1, doAdd, toDefaultTable); + modifyRoute(lp, bestRoute, cycleCount+1, doAdd, toDefaultTable, exempt); } } if (doAdd) { if (VDBG) log("Adding " + r + " for interface " + ifaceName); try { if (toDefaultTable) { - mAddedRoutes.add(r); // only track default table - only one apps can effect - mNetd.addRoute(ifaceName, r); + synchronized (mRoutesLock) { + // only track default table - only one apps can effect + mAddedRoutes.add(r); + mNetd.addRoute(ifaceName, r); + if (exempt) { + LinkAddress dest = r.getDestination(); + if (!mExemptAddresses.contains(dest)) { + mNetd.setHostExemption(dest); + mExemptAddresses.add(dest); + } + } + } } else { mNetd.addSecondaryRoute(ifaceName, r); } @@ -1559,18 +1692,25 @@ public class ConnectivityService extends IConnectivityManager.Stub { // 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) { - mAddedRoutes.remove(r); - if (mAddedRoutes.contains(r) == false) { - if (VDBG) log("Removing " + r + " for interface " + ifaceName); - try { - mNetd.removeRoute(ifaceName, r); - } catch (Exception e) { - // never crash - catch them all - if (VDBG) loge("Exception trying to remove a route: " + e); - return false; + synchronized (mRoutesLock) { + mAddedRoutes.remove(r); + if (mAddedRoutes.contains(r) == false) { + if (VDBG) log("Removing " + r + " for interface " + ifaceName); + try { + mNetd.removeRoute(ifaceName, r); + LinkAddress dest = r.getDestination(); + 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("not removing " + r + " as it's still in use"); } } else { if (VDBG) log("Removing " + r + " for interface " + ifaceName); @@ -1747,6 +1887,16 @@ public class ConnectivityService extends IConnectivityManager.Stub { "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 @@ -1852,6 +2002,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { */ if (mNetConfigs[prevNetType].isDefault()) { if (mActiveDefaultNetwork == prevNetType) { + if (DBG) { + log("tryFailover: set mActiveDefaultNetwork=-1, prevNetType=" + prevNetType); + } mActiveDefaultNetwork = -1; } @@ -1862,6 +2015,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { // 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; @@ -1876,6 +2030,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { // 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; @@ -2044,6 +2199,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { } void systemReady() { + mCaptivePortalTracker = CaptivePortalTracker.makeCaptivePortalTracker(mContext, this); + loadGlobalProxy(); + synchronized(this) { mSystemReady = true; if (mInitialBroadcast != null) { @@ -2074,10 +2232,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { }; private boolean isNewNetTypePreferredOverCurrentNetType(int type) { - if ((type != mNetworkPreference && - mNetConfigs[mActiveDefaultNetwork].priority > - mNetConfigs[type].priority) || - mNetworkPreference == mActiveDefaultNetwork) return false; + if (((type != mNetworkPreference) + && (mNetConfigs[mActiveDefaultNetwork].priority > mNetConfigs[type].priority)) + || (mNetworkPreference == mActiveDefaultNetwork)) { + return false; + } return true; } @@ -2091,6 +2250,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { 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()) { @@ -2141,6 +2305,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { } thisNet.setTeardownRequested(false); updateNetworkSettings(thisNet); + updateMtuSizeSettings(thisNet); handleConnectivityChange(newNetType, false); sendConnectedBroadcastDelayed(info, getConnectivityChangeDelay()); @@ -2172,15 +2337,26 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } + if (DBG) log("handleCaptivePortalTrackerCheck: call captivePortalCheckComplete ni=" + info); thisNet.captivePortalCheckComplete(); } /** @hide */ + @Override public void captivePortalCheckComplete(NetworkInfo info) { enforceConnectivityInternalPermission(); + if (DBG) log("captivePortalCheckComplete: ni=" + info); mNetTrackers[info.getType()].captivePortalCheckComplete(); } + /** @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 interface. * @@ -2241,6 +2417,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { */ private void handleConnectivityChange(int netType, 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 @@ -2305,10 +2486,12 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } mCurrentLinkProperties[netType] = newLp; - boolean resetDns = updateRoutes(newLp, curLp, mNetConfigs[netType].isDefault()); + boolean resetDns = updateRoutes(newLp, curLp, mNetConfigs[netType].isDefault(), exempt); 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) { @@ -2318,7 +2501,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { // 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) { - mVpn.interfaceStatusChanged(iface, false); + synchronized(mVpns) { + for (int i = 0; i < mVpns.size(); i++) { + mVpns.valueAt(i).interfaceStatusChanged(iface, false); + } + } } } if (resetDns) { @@ -2341,6 +2528,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { // Update 464xlat state. NetworkStateTracker tracker = mNetTrackers[netType]; if (mClat.requiresClat(netType, tracker)) { + // 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 @@ -2377,13 +2565,13 @@ public class ConnectivityService extends IConnectivityManager.Stub { * returns a boolean indicating the routes changed */ private boolean updateRoutes(LinkProperties newLp, LinkProperties curLp, - boolean isLinkDefault) { + boolean isLinkDefault, boolean exempt) { Collection routesToAdd = null; CompareResult dnsDiff = new CompareResult(); CompareResult routeDiff = new CompareResult(); if (curLp != null) { // check for the delta between the current set and the new - routeDiff = curLp.compareRoutes(newLp); + routeDiff = curLp.compareAllRoutes(newLp); dnsDiff = curLp.compareDnses(newLp); } else if (newLp != null) { routeDiff.added = newLp.getAllRoutes(); @@ -2394,6 +2582,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { for (RouteInfo r : routeDiff.removed) { if (isLinkDefault || ! r.isDefaultRoute()) { + if (VDBG) log("updateRoutes: default remove route r=" + r); removeRoute(curLp, r, TO_DEFAULT_TABLE); } if (isLinkDefault == false) { @@ -2413,7 +2602,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { } if (newLp != null) { for (InetAddress newDns : newLp.getDnses()) { - addRouteToAddress(newLp, newDns); + addRouteToAddress(newLp, newDns, exempt); } } } else { @@ -2422,28 +2611,30 @@ public class ConnectivityService extends IConnectivityManager.Stub { removeRouteToAddress(curLp, oldDns); } for (InetAddress newDns : dnsDiff.added) { - addRouteToAddress(newLp, newDns); + addRouteToAddress(newLp, newDns, exempt); } } } for (RouteInfo r : routeDiff.added) { if (isLinkDefault || ! r.isDefaultRoute()) { - addRoute(newLp, r, TO_DEFAULT_TABLE); + addRoute(newLp, r, TO_DEFAULT_TABLE, exempt); } else { // add to a secondary route table - addRoute(newLp, r, TO_SECONDARY_TABLE); + addRoute(newLp, r, TO_SECONDARY_TABLE, UNEXEMPT); // 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(); - if (TextUtils.isEmpty(ifaceName) == false && mAddedRoutes.contains(r) == false) { - if (VDBG) log("Removing " + r + " for interface " + ifaceName); - try { - mNetd.removeRoute(ifaceName, r); - } catch (Exception e) { - // never crash - catch them all - if (DBG) loge("Exception trying to remove a route: " + e); + synchronized (mRoutesLock) { + if (!TextUtils.isEmpty(ifaceName) && !mAddedRoutes.contains(r)) { + if (VDBG) log("Removing " + r + " for interface " + ifaceName); + try { + mNetd.removeRoute(ifaceName, r); + } catch (Exception e) { + // never crash - catch them all + if (DBG) loge("Exception trying to remove a route: " + e); + } } } } @@ -2452,13 +2643,33 @@ public class ConnectivityService extends IConnectivityManager.Stub { return routesChanged; } - /** + * Reads the network specific MTU size from reources. + * and set it on it's iface. + */ + private void updateMtuSizeSettings(NetworkStateTracker nt) { + final String iface = nt.getLinkProperties().getInterfaceName(); + final int mtu = nt.getLinkProperties().getMtu(); + + if (mtu < 68 || mtu > 10000) { + loge("Unexpected mtu value: " + nt); + 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) { + private void updateNetworkSettings(NetworkStateTracker nt) { String key = nt.getTcpBufferSizesPropName(); String bufferSizes = key == null ? null : SystemProperties.get(key); @@ -2480,7 +2691,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } - /** + /** * 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 * @@ -2563,7 +2774,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { // Caller must grab mDnsLock. private void updateDnsLocked(String network, String iface, - Collection dnses, String domains) { + Collection dnses, String domains, boolean defaultDns) { int last = 0; if (dnses.size() == 0 && mDefaultDns != null) { dnses = new ArrayList(); @@ -2575,7 +2786,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { try { mNetd.setDnsServersForInterface(iface, NetworkUtils.makeStrings(dnses), domains); - mNetd.setDefaultInterfaceForDns(iface); + if (defaultDns) { + mNetd.setDefaultInterfaceForDns(iface); + } + for (InetAddress dns : dnses) { ++last; String key = "net.dns" + last; @@ -2588,7 +2802,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { } mNumDnsEntries = last; } catch (Exception e) { - if (DBG) loge("exception setting default dns interface: " + e); + loge("exception setting default dns interface: " + e); } } @@ -2602,9 +2816,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { if (mNetConfigs[netType].isDefault()) { String network = nt.getNetworkInfo().getTypeName(); synchronized (mDnsLock) { - if (!mDnsOverridden) { - updateDnsLocked(network, p.getInterfaceName(), dnses, p.getDomains()); - } + updateDnsLocked(network, p.getInterfaceName(), dnses, p.getDomains(), true); } } else { try { @@ -2728,27 +2940,33 @@ public class ConnectivityService extends IConnectivityManager.Stub { public void handleMessage(Message msg) { NetworkInfo info; switch (msg.what) { - case NetworkStateTracker.EVENT_STATE_CHANGED: + case NetworkStateTracker.EVENT_STATE_CHANGED: { info = (NetworkInfo) msg.obj; - int type = info.getType(); NetworkInfo.State state = info.getState(); if (VDBG || (state == NetworkInfo.State.CONNECTED) || - (state == NetworkInfo.State.DISCONNECTED)) { + (state == NetworkInfo.State.DISCONNECTED) || + (state == NetworkInfo.State.SUSPENDED)) { log("ConnectivityChange for " + info.getTypeName() + ": " + state + "/" + info.getDetailedState()); } - // After booting we'll check once for mobile provisioning - // if we've provisioned by and connected. - if (!mFirstProvisioningCheckStarted + // 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)) { - log("check provisioning after booting"); - mFirstProvisioningCheckStarted = true; - checkMobileProvisioning(true, CheckMp.MAX_TIMEOUT_MS, null); + && (((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( @@ -2760,6 +2978,29 @@ public class ConnectivityService extends IConnectivityManager.Stub { } else if (info.getDetailedState() == DetailedState.CAPTIVE_PORTAL_CHECK) { handleCaptivePortalTrackerCheck(info); + } else 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 = getLinkProperties(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); + } } else if (state == NetworkInfo.State.DISCONNECTED) { handleDisconnect(info); } else if (state == NetworkInfo.State.SUSPENDED) { @@ -2778,18 +3019,21 @@ public class ConnectivityService extends IConnectivityManager.Stub { mLockdownTracker.onNetworkInfoChanged(info); } break; - case NetworkStateTracker.EVENT_CONFIGURATION_CHANGED: + } + case NetworkStateTracker.EVENT_CONFIGURATION_CHANGED: { info = (NetworkInfo) msg.obj; // TODO: Temporary allowing network configuration // change not resetting sockets. // @see bug/4455071 handleConnectivityChange(info.getType(), false); break; - case NetworkStateTracker.EVENT_NETWORK_SUBTYPE_CHANGED: + } + case NetworkStateTracker.EVENT_NETWORK_SUBTYPE_CHANGED: { info = (NetworkInfo) msg.obj; - type = info.getType(); + int type = info.getType(); updateNetworkSettings(mNetTrackers[type]); break; + } } } } @@ -2803,7 +3047,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { public void handleMessage(Message msg) { NetworkInfo info; switch (msg.what) { - case EVENT_CLEAR_NET_TRANSITION_WAKELOCK: + case EVENT_CLEAR_NET_TRANSITION_WAKELOCK: { String causedBy = null; synchronized (ConnectivityService.this) { if (msg.arg1 == mNetTransitionWakeLockSerialNumber && @@ -2816,56 +3060,44 @@ public class ConnectivityService extends IConnectivityManager.Stub { log("NetTransition Wakelock for " + causedBy + " released by timeout"); } break; - case EVENT_RESTORE_DEFAULT_NETWORK: + } + case EVENT_RESTORE_DEFAULT_NETWORK: { FeatureUser u = (FeatureUser)msg.obj; u.expire(); break; - case EVENT_INET_CONDITION_CHANGE: - { + } + case EVENT_INET_CONDITION_CHANGE: { int netType = msg.arg1; int condition = msg.arg2; handleInetConditionChange(netType, condition); break; } - case EVENT_INET_CONDITION_HOLD_END: - { + case EVENT_INET_CONDITION_HOLD_END: { int netType = msg.arg1; int sequence = msg.arg2; handleInetConditionHoldEnd(netType, sequence); break; } - case EVENT_SET_NETWORK_PREFERENCE: - { + case EVENT_SET_NETWORK_PREFERENCE: { int preference = msg.arg1; handleSetNetworkPreference(preference); break; } - case EVENT_SET_MOBILE_DATA: - { + case EVENT_SET_MOBILE_DATA: { boolean enabled = (msg.arg1 == ENABLED); handleSetMobileData(enabled); break; } - case EVENT_APPLY_GLOBAL_HTTP_PROXY: - { + case EVENT_APPLY_GLOBAL_HTTP_PROXY: { handleDeprecatedGlobalHttpProxy(); break; } - case EVENT_SET_DEPENDENCY_MET: - { + case EVENT_SET_DEPENDENCY_MET: { boolean met = (msg.arg1 == ENABLED); handleSetDependencyMet(msg.arg2, met); break; } - case EVENT_RESTORE_DNS: - { - if (mActiveDefaultNetwork != -1) { - handleDnsConfigurationChange(mActiveDefaultNetwork); - } - break; - } - case EVENT_SEND_STICKY_BROADCAST_INTENT: - { + case EVENT_SEND_STICKY_BROADCAST_INTENT: { Intent intent = (Intent)msg.obj; sendStickyBroadcast(intent); break; @@ -2894,6 +3126,15 @@ public class ConnectivityService extends IConnectivityManager.Stub { 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((ProxyProperties)msg.obj); + break; } } } @@ -2981,12 +3222,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { return mTethering.getTetheredIfaces(); } - @Override - public String[] getTetheredIfacePairs() { - enforceTetherAccessPermission(); - return mTethering.getTetheredIfacePairs(); - } - public String[] getTetheringErroredIfaces() { enforceTetherAccessPermission(); return mTethering.getErroredIfaces(); @@ -3122,13 +3357,15 @@ public class ConnectivityService extends IConnectivityManager.Stub { // of proxy info to all the JVMs. // enforceAccessPermission(); synchronized (mProxyLock) { - if (mGlobalProxy != null) return mGlobalProxy; - return (mDefaultProxyDisabled ? null : mDefaultProxy); + ProxyProperties ret = mGlobalProxy; + if ((ret == null) && !mDefaultProxyDisabled) ret = mDefaultProxy; + return ret; } } public void setGlobalProxy(ProxyProperties proxyProperties) { enforceConnectivityInternalPermission(); + synchronized (mProxyLock) { if (proxyProperties == mGlobalProxy) return; if (proxyProperties != null && proxyProperties.equals(mGlobalProxy)) return; @@ -3137,7 +3374,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { String host = ""; int port = 0; String exclList = ""; - if (proxyProperties != null && !TextUtils.isEmpty(proxyProperties.getHost())) { + String pacFileUrl = ""; + if (proxyProperties != null && (!TextUtils.isEmpty(proxyProperties.getHost()) || + !TextUtils.isEmpty(proxyProperties.getPacFileUrl()))) { if (!proxyProperties.isValid()) { if (DBG) log("Invalid proxy properties, ignoring: " + proxyProperties.toString()); @@ -3147,6 +3386,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { host = mGlobalProxy.getHost(); port = mGlobalProxy.getPort(); exclList = mGlobalProxy.getExclusionList(); + if (proxyProperties.getPacFileUrl() != null) { + pacFileUrl = proxyProperties.getPacFileUrl(); + } } else { mGlobalProxy = null; } @@ -3157,6 +3399,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { 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); } @@ -3174,8 +3417,14 @@ public class ConnectivityService extends IConnectivityManager.Stub { 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); - if (!TextUtils.isEmpty(host)) { - ProxyProperties proxyProperties = new ProxyProperties(host, port, exclList); + String pacFileUrl = Settings.Global.getString(res, Settings.Global.GLOBAL_HTTP_PROXY_PAC); + if (!TextUtils.isEmpty(host) || !TextUtils.isEmpty(pacFileUrl)) { + ProxyProperties proxyProperties; + if (!TextUtils.isEmpty(pacFileUrl)) { + proxyProperties = new ProxyProperties(pacFileUrl); + } else { + proxyProperties = new ProxyProperties(host, port, exclList); + } if (!proxyProperties.isValid()) { if (DBG) log("Invalid proxy properties, ignoring: " + proxyProperties.toString()); return; @@ -3198,7 +3447,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { } private void handleApplyDefaultProxy(ProxyProperties proxy) { - if (proxy != null && TextUtils.isEmpty(proxy.getHost())) { + if (proxy != null && TextUtils.isEmpty(proxy.getHost()) + && TextUtils.isEmpty(proxy.getPacFileUrl())) { proxy = null; } synchronized (mProxyLock) { @@ -3222,6 +3472,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { 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) { @@ -3238,6 +3492,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { private void sendProxyBroadcast(ProxyProperties proxy) { if (proxy == null) proxy = new ProxyProperties("", 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 | @@ -3332,8 +3587,12 @@ public class ConnectivityService extends IConnectivityManager.Stub { throwIfLockdownEnabled(); try { int type = mActiveDefaultNetwork; + int user = UserHandle.getUserId(Binder.getCallingUid()); if (ConnectivityManager.isNetworkTypeValid(type) && mNetTrackers[type] != null) { - mVpn.protect(socket, mNetTrackers[type].getLinkProperties().getInterfaceName()); + synchronized(mVpns) { + mVpns.get(user).protect(socket, + mNetTrackers[type].getLinkProperties().getInterfaceName()); + } return true; } } catch (Exception e) { @@ -3357,7 +3616,27 @@ public class ConnectivityService extends IConnectivityManager.Stub { @Override public boolean prepareVpn(String oldPackage, String newPackage) { throwIfLockdownEnabled(); - return mVpn.prepare(oldPackage, newPackage); + 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); + } } /** @@ -3370,7 +3649,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { @Override public ParcelFileDescriptor establishVpn(VpnConfig config) { throwIfLockdownEnabled(); - return mVpn.establish(config); + int user = UserHandle.getUserId(Binder.getCallingUid()); + synchronized(mVpns) { + return mVpns.get(user).establish(config); + } } /** @@ -3384,7 +3666,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { if (egress == null) { throw new IllegalStateException("Missing active network connection"); } - mVpn.startLegacyVpn(profile, mKeyStore, egress); + int user = UserHandle.getUserId(Binder.getCallingUid()); + synchronized(mVpns) { + mVpns.get(user).startLegacyVpn(profile, mKeyStore, egress); + } } /** @@ -3396,7 +3681,24 @@ public class ConnectivityService extends IConnectivityManager.Stub { @Override public LegacyVpnInfo getLegacyVpnInfo() { throwIfLockdownEnabled(); - return mVpn.getLegacyVpnInfo(); + 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(); + } } /** @@ -3417,7 +3719,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { mHandler.obtainMessage(EVENT_VPN_STATE_CHANGED, info).sendToTarget(); } - public void override(List dnsServers, List searchDomains) { + public void override(String iface, List dnsServers, List searchDomains) { if (dnsServers == null) { restore(); return; @@ -3449,8 +3751,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { // Apply DNS changes. synchronized (mDnsLock) { - updateDnsLocked("VPN", "VPN", addresses, domains); - mDnsOverridden = true; + updateDnsLocked("VPN", iface, addresses, domains, false); } // Temporarily disable the default proxy (not global). @@ -3465,12 +3766,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { } public void restore() { - synchronized (mDnsLock) { - if (mDnsOverridden) { - mDnsOverridden = false; - mHandler.sendEmptyMessage(EVENT_RESTORE_DNS); - } - } synchronized (mProxyLock) { mDefaultProxyDisabled = false; if (mGlobalProxy == null && mDefaultProxy != null) { @@ -3478,6 +3773,69 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } } + + 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 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) { + try { + mNetd.setUidRangeRoute(interfaze,uidStart, uidEnd); + if (forwardDns) mNetd.setDnsInterfaceForUidRange(interfaze, uidStart, uidEnd); + } catch (RemoteException e) { + } + + } + + public void clearUidForwarding(String interfaze, int uidStart, int uidEnd, + boolean forwardDns) { + try { + mNetd.clearUidRangeRoute(interfaze, uidStart, uidEnd); + if (forwardDns) mNetd.clearDnsInterfaceForUidRange(uidStart, uidEnd); + } catch (RemoteException e) { + } + + } } @Override @@ -3498,7 +3856,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { final String profileName = new String(mKeyStore.get(Credentials.LOCKDOWN_VPN)); final VpnProfile profile = VpnProfile.decode( profileName, mKeyStore.get(Credentials.VPN + profileName)); - setLockdownTracker(new LockdownVpnTracker(mContext, mNetd, this, mVpn, profile)); + int user = UserHandle.getUserId(Binder.getCallingUid()); + synchronized(mVpns) { + setLockdownTracker(new LockdownVpnTracker(mContext, mNetd, this, mVpns.get(user), + profile)); + } } else { setLockdownTracker(null); } @@ -3578,72 +3940,124 @@ public class ConnectivityService extends IConnectivityManager.Stub { 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; + + private AtomicBoolean mIsCheckingMobileProvisioning = new AtomicBoolean(false); + @Override - public int checkMobileProvisioning(boolean sendNotification, int suggestedTimeOutMs, - final ResultReceiver resultReceiver) { - log("checkMobileProvisioning: E sendNotification=" + sendNotification - + " suggestedTimeOutMs=" + suggestedTimeOutMs - + " resultReceiver=" + resultReceiver); - enforceChangePermission(); - - mFirstProvisioningCheckStarted = true; - - int 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)) { - log("checkMobileProvisioning: X no mobile network"); - if (resultReceiver != null) { - resultReceiver.send(ConnectivityManager.CMP_RESULT_CODE_NO_CONNECTION, null); - } - return timeOutMs; - } + 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) { - log("CheckMp.onComplete: result=" + result); - if (resultReceiver != null) { - log("CheckMp.onComplete: send result"); - resultReceiver.send(result, null); - } + if (DBG) log("CheckMp.onComplete: result=" + result); NetworkInfo ni = mNetTrackers[ConnectivityManager.TYPE_MOBILE_HIPRI].getNetworkInfo(); switch(result) { - case ConnectivityManager.CMP_RESULT_CODE_CONNECTABLE: - case ConnectivityManager.CMP_RESULT_CODE_NO_CONNECTION: { - log("CheckMp.onComplete: ignore, connected or no connection"); + 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 ConnectivityManager.CMP_RESULT_CODE_REDIRECTED: { - log("CheckMp.onComplete: warm sim"); + 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) { - log("CheckMp.onComplete: warm sim (redirected), url=" + url); - setNotificationVisible(true, ni, url); + if (DBG) log("CheckMp.onComplete: warm (redirected), url=" + url); + setProvNotificationVisible(true, + ConnectivityManager.TYPE_MOBILE_HIPRI, ni.getExtraInfo(), + url); } else { - log("CheckMp.onComplete: warm sim (redirected), no url"); + if (DBG) log("CheckMp.onComplete: warm (redirected), no url"); } break; } - case ConnectivityManager.CMP_RESULT_CODE_NO_DNS: - case ConnectivityManager.CMP_RESULT_CODE_NO_TCP_CONNECTION: { + case CMP_RESULT_CODE_PROVISIONING_NETWORK: { String url = getMobileProvisioningUrl(); if (TextUtils.isEmpty(url) == false) { - log("CheckMp.onComplete: warm sim (no dns/tcp), url=" + url); - setNotificationVisible(true, ni, url); + if (DBG) log("CheckMp.onComplete: warm (no dns/tcp), url=" + url); + setProvNotificationVisible(true, + ConnectivityManager.TYPE_MOBILE_HIPRI, ni.getExtraInfo(), + url); } else { - log("CheckMp.onComplete: warm sim (no dns/tcp), no url"); + if (DBG) log("CheckMp.onComplete: warm (no dns/tcp), no url"); } break; } @@ -3652,16 +4066,16 @@ public class ConnectivityService extends IConnectivityManager.Stub { break; } } + mIsCheckingMobileProvisioning.set(false); } }; CheckMp.Params params = new CheckMp.Params(checkMp.getDefaultUrl(), timeOutMs, cb); - log("checkMobileProvisioning: params=" + params); - setNotificationVisible(false, null, null); + if (DBG) log("checkMobileProvisioning: params=" + params); checkMp.execute(params); } finally { Binder.restoreCallingIdentity(token); - log("checkMobileProvisioning: X"); + if (DBG) log("checkMobileProvisioning: X"); } return timeOutMs; } @@ -3733,27 +4147,72 @@ public class ConnectivityService extends IConnectivityManager.Stub { * a known address that fetches the data we expect. */ private synchronized Integer isMobileOk(Params params) { - Integer result = ConnectivityManager.CMP_RESULT_CODE_NO_CONNECTION; + 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; + } + + // 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 { - if (mCs.isNetworkSupported(ConnectivityManager.TYPE_MOBILE) == false) { - log("isMobileOk: not mobile capable"); - result = ConnectivityManager.CMP_RESULT_CODE_NO_CONNECTION; - return result; - } - - // Enable fail fast as we'll do retries here and use a - // hipri connection so the default connection stays active. - log("isMobileOk: start hipri url=" + params.mUrl); - mCs.setEnableFailFastMobileData(DctConstants.ENABLED); - mCs.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE, - Phone.FEATURE_ENABLE_HIPRI, new Binder()); - // 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(1); + } + } + + 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(1); + } + + // Continue trying to connect until time has run out while(SystemClock.elapsedRealtime() < endTime) { try { // Wait for hipri to connect. @@ -3762,13 +4221,26 @@ public class ConnectivityService extends IConnectivityManager.Stub { NetworkInfo.State state = mCs .getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI).getState(); if (state != NetworkInfo.State.CONNECTED) { - log("isMobileOk: not connected ni=" + + if (true/*VDBG*/) { + log("isMobileOk: not connected ni=" + mCs.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI)); + } sleep(1); - result = ConnectivityManager.CMP_RESULT_CODE_NO_CONNECTION; + 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 is will try every address but that will bypass the @@ -3778,8 +4250,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { try { addresses = InetAddress.getAllByName(orgUri.getHost()); } catch (UnknownHostException e) { - log("isMobileOk: UnknownHostException"); - result = ConnectivityManager.CMP_RESULT_CODE_NO_DNS; + result = CMP_RESULT_CODE_NO_DNS; + log("isMobileOk: X UnknownHostException result=" + result); return result; } log("isMobileOk: addresses=" + inetAddressesToString(addresses)); @@ -3787,29 +4259,40 @@ public class ConnectivityService extends IConnectivityManager.Stub { // Get the type of addresses supported by this link LinkProperties lp = mCs.getLinkProperties( ConnectivityManager.TYPE_MOBILE_HIPRI); - boolean linkHasIpv4 = hasIPv4Address(lp); - boolean linkHasIpv6 = hasIPv6Address(lp); + boolean linkHasIpv4 = lp.hasIPv4Address(); + boolean linkHasIpv6 = lp.hasIPv6Address(); log("isMobileOk: linkHasIpv4=" + linkHasIpv4 + " linkHasIpv6=" + linkHasIpv6); - // Loop through at most 3 valid addresses or all of the address or until - // we run out of time - int loops = Math.min(3, addresses.length); - for(int validAddr=0, addrTried=0; - (validAddr < loops) && (addrTried < addresses.length) - && (SystemClock.elapsedRealtime() < endTime); - addrTried ++) { + final ArrayList validAddresses = + new ArrayList(addresses.length); - // Choose the address at random but make sure its type is supported - InetAddress hostAddr = addresses[rand.nextInt(addresses.length)]; - if (((hostAddr instanceof Inet4Address) && linkHasIpv4) - || ((hostAddr instanceof Inet6Address) && linkHasIpv6)) { - // Valid address, so use it - validAddr += 1; - } else { - // Invalid address so try next address - continue; + 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 3 valid addresses or until + // we run out of time + if (addrTried++ >= 3) { + log("too many loops tried - giving up"); + break; + } + if (SystemClock.elapsedRealtime() >= endTime) { + log("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, @@ -3825,10 +4308,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { } // Rewrite the url to have numeric address to use the specific route. - // I also set the "Connection" to "Close" as by default "Keep-Alive" - // is used which is useless in this case. - URL newUrl = new URL(orgUri.getScheme() + "://" - + hostAddr.getHostAddress() + orgUri.getPath()); + // Add a pointless random query param to fool proxies into not caching. + URL newUrl = new URL(orgUri.getScheme(), + hostAddr.getHostAddress(), + orgUri.getPath() + "?q=" + rand.nextInt(Integer.MAX_VALUE)); log("isMobileOk: newUrl=" + newUrl); HttpURLConnection urlConn = null; @@ -3841,27 +4324,45 @@ public class ConnectivityService extends IConnectivityManager.Stub { 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(); - if (responseCode == 204) { - result = ConnectivityManager.CMP_RESULT_CODE_CONNECTABLE; - } else { - result = ConnectivityManager.CMP_RESULT_CODE_REDIRECTED; - } - log("isMobileOk: connected responseCode=" + responseCode); + + // For debug display the headers + Map> headers = urlConn.getHeaderFields(); + log("isMobileOk: headers=" + headers); + + // Close the connection urlConn.disconnect(); urlConn = null; - return result; + + if (responseCode == 204) { + // Return + result = CMP_RESULT_CODE_CONNECTABLE; + log("isMobileOk: X 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=" + e); + result = CMP_RESULT_CODE_NO_TCP_CONNECTION; if (urlConn != null) { urlConn.disconnect(); urlConn = null; } } } - result = ConnectivityManager.CMP_RESULT_CODE_NO_TCP_CONNECTION; - log("isMobileOk: loops|timed out"); + log("isMobileOk: X loops|timed out result=" + result); return result; } catch (Exception e) { log("isMobileOk: Exception e=" + e); @@ -3874,6 +4375,23 @@ public class ConnectivityService extends IConnectivityManager.Stub { 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(1); + continue; + } + } + log("isMobileOk: X result=" + result); } return result; @@ -3934,29 +4452,59 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } - public boolean hasIPv4Address(LinkProperties lp) { - return lp.hasIPv4Address(); - } - - // Not implemented in LinkProperties, do it here. - public boolean hasIPv6Address(LinkProperties lp) { - for (LinkAddress address : lp.getLinkAddresses()) { - if (address.getAddress() instanceof Inet6Address) { - return true; - } - } - return false; - } - private void log(String s) { Slog.d(ConnectivityService.TAG, "[" + CHECKMP_TAG + "] " + s); } } - private static final String NOTIFICATION_ID = "CaptivePortal.Notification"; + // 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 void setNotificationVisible(boolean visible, NetworkInfo networkInfo, String url) { - log("setNotificationVisible: E visible=" + visible + " ni=" + networkInfo + " url=" + url); + 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) { + // Notication mark notification as not visible + setProvNotificationVisible(false, ConnectivityManager.TYPE_MOBILE_HIPRI, null, null); + + // If provisioning network handle as a special case, + // otherwise launch browser with the intent directly. + NetworkInfo ni = getProvisioningNetworkInfo(); + if ((ni != null) && ni.isConnectedToProvisioningNetwork()) { + if (DBG) log("handleMobileProvisioningAction: on provisioning network"); + MobileDataStateTracker mdst = (MobileDataStateTracker) + mNetTrackers[ConnectivityManager.TYPE_MOBILE]; + mdst.enableMobileProvisioning(url); + } else { + if (DBG) log("handleMobileProvisioningAction: on default network"); + Intent newIntent = + new Intent(Intent.ACTION_VIEW, 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 @@ -3966,50 +4514,64 @@ public class ConnectivityService extends IConnectivityManager.Stub { CharSequence title; CharSequence details; int icon; - switch (networkInfo.getType()) { + Intent intent; + Notification notification = new Notification(); + switch (networkType) { case ConnectivityManager.TYPE_WIFI: - log("setNotificationVisible: TYPE_WIFI"); title = r.getString(R.string.wifi_available_sign_in, 0); details = r.getString(R.string.network_available_sign_in_detailed, - networkInfo.getExtraInfo()); + 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: - log("setNotificationVisible: 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: - log("setNotificationVisible: other type=" + networkInfo.getType()); title = r.getString(R.string.network_available_sign_in, 0); details = r.getString(R.string.network_available_sign_in_detailed, - networkInfo.getExtraInfo()); + 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 notification = new Notification(); notification.when = 0; notification.icon = icon; notification.flags = Notification.FLAG_AUTO_CANCEL; - Intent 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); notification.tickerText = title; notification.setLatestEventInfo(mContext, title, details, notification.contentIntent); - log("setNotificaitionVisible: notify notificaiton=" + notification); - notificationManager.notify(NOTIFICATION_ID, 1, notification); + try { + notificationManager.notify(NOTIFICATION_ID, networkType, notification); + } catch (NullPointerException npe) { + loge("setNotificaitionVisible: visible notificationManager npe=" + npe); + npe.printStackTrace(); + } } else { - log("setNotificaitionVisible: cancel"); - notificationManager.cancel(NOTIFICATION_ID, 1); + try { + notificationManager.cancel(NOTIFICATION_ID, networkType); + } catch (NullPointerException npe) { + loge("setNotificaitionVisible: cancel notificationManager npe=" + npe); + npe.printStackTrace(); + } } - log("setNotificationVisible: X visible=" + visible + " ni=" + networkInfo + " url=" + url); + mIsNotificationVisible = visible; } /** Location to an updatable file listing carrier provisioning urls. @@ -4103,7 +4665,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { return null; } - private String getMobileRedirectedProvisioningUrl() { + @Override + public String getMobileRedirectedProvisioningUrl() { + enforceConnectivityInternalPermission(); String url = getProvisioningUrlBaseFromFile(REDIRECTED_PROVISIONING); if (TextUtils.isEmpty(url)) { url = mContext.getResources().getString(R.string.mobile_redirected_provisioning_url); @@ -4111,14 +4675,15 @@ public class ConnectivityService extends IConnectivityManager.Stub { 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("getProvisioningUrl: mobile_provisioining_url from resource =" + url); + log("getMobileProvisioningUrl: mobile_provisioining_url from resource =" + url); } else { - log("getProvisioningUrl: mobile_provisioning_url from File =" + url); + log("getMobileProvisioningUrl: mobile_provisioning_url from File =" + url); } // populate the iccid, imei and phone number in the provisioning url. if (!TextUtils.isEmpty(url)) { @@ -4134,4 +4699,152 @@ public class ConnectivityService extends IConnectivityManager.Stub { 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)) { + return mNetTrackers[networkType].getLinkQualityInfo(); + } else { + return null; + } + } + + @Override + public LinkQualityInfo getActiveLinkQualityInfo() { + enforceAccessPermission(); + if (isNetworkTypeValid(mActiveDefaultNetwork)) { + return mNetTrackers[mActiveDefaultNetwork].getLinkQualityInfo(); + } else { + return null; + } + } + + @Override + public LinkQualityInfo[] getAllLinkQualityInfo() { + enforceAccessPermission(); + final ArrayList 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 mapIfaceToSample = + new HashMap(); + 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); + } } 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 0000000000..56dd7c40df --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java @@ -0,0 +1,175 @@ +/* + * 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.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 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); + + sendMessage("613 IfaceClass idle eth0"); + expectSoon(observer).interfaceClassDataActivityChanged("eth0", false); + + sendMessage("613 IfaceClass reallyactive rmnet0"); + expectSoon(observer).interfaceClassDataActivityChanged("rmnet0", false); + + sendMessage("613 InterfaceClass reallyactive rmnet0"); + // Invalid group. + + + /** + * IP address changes. + */ + sendMessage("614 Address updated fe80::1/64 wlan0 128 253"); + expectSoon(observer).addressUpdated("fe80::1/64", "wlan0", 128, 253); + + // There is no "added". + sendMessage("614 Address added fe80::1/64 wlan0 128 253"); + expectSoon(observer).addressRemoved("fe80::1/64", "wlan0", 128, 253); + + sendMessage("614 Address removed 2001:db8::1/64 wlan0 1 0"); + expectSoon(observer).addressRemoved("2001:db8::1/64", "wlan0", 1, 0); + + sendMessage("666 Address added 2001:db8::1/64 wlan0 1 0"); + // Invalid code. + + // 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 index cdc4d784cf..a1af8cb79f 100644 --- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java @@ -40,7 +40,6 @@ 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.aryEq; import static org.easymock.EasyMock.capture; import static org.easymock.EasyMock.createMock; import static org.easymock.EasyMock.eq; @@ -63,6 +62,7 @@ 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; @@ -73,13 +73,13 @@ 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; -import libcore.io.IoUtils; - /** * Tests for {@link NetworkStatsService}. */ @@ -878,8 +878,8 @@ public class NetworkStatsServiceTest extends AndroidTestCase { mAlarmManager.remove(isA(PendingIntent.class)); expectLastCall().anyTimes(); - mAlarmManager.setInexactRepeating( - eq(AlarmManager.ELAPSED_REALTIME), anyLong(), anyLong(), isA(PendingIntent.class)); + mAlarmManager.set(eq(AlarmManager.ELAPSED_REALTIME), anyLong(), anyLong(), anyLong(), + isA(PendingIntent.class), isA(WorkSource.class)); expectLastCall().atLeastOnce(); mNetManager.setGlobalAlert(anyLong()); @@ -918,8 +918,7 @@ public class NetworkStatsServiceTest extends AndroidTestCase { expect(mNetManager.getNetworkStatsUidDetail(eq(UID_ALL))).andReturn(detail).atLeastOnce(); // also include tethering details, since they are folded into UID - expect(mConnManager.getTetheredIfacePairs()).andReturn(tetherIfacePairs).atLeastOnce(); - expect(mNetManager.getNetworkStatsTethering(aryEq(tetherIfacePairs))) + expect(mNetManager.getNetworkStatsTethering()) .andReturn(tetherStats).atLeastOnce(); }