commit 54a942fddf3148e3a361e63f98f316df415b499e Author: The Android Open Source Project Date: Tue Oct 21 07:00:00 2008 -0700 Initial Contribution diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java new file mode 100644 index 0000000000..213813a814 --- /dev/null +++ b/core/java/android/net/ConnectivityManager.java @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.os.RemoteException; + +/** + * Class that answers queries about the state of network connectivity. It also + * notifies applications when network connectivity changes. Get an instance + * of this class by calling + * {@link android.content.Context#getSystemService(String) Context.getSystemService(Context.CONNECTIVITY_SERVICE)}. + *

+ * The primary responsibilities of this class are to: + *

    + *
  1. Monitor network connections (Wi-Fi, GPRS, UMTS, etc.)
  2. + *
  3. Send broadcast intents when network connectivity changes
  4. + *
  5. Attempt to "fail over" to another network when connectivity to a network + * is lost
  6. + *
  7. Provide an API that allows applications to query the coarse-grained or fine-grained + * state of the available networks
  8. + *
+ */ +public class ConnectivityManager +{ + /** + * A change in network connectivity has occurred. A connection has either + * been established or lost. The NetworkInfo for the affected network is + * sent as an extra; it should be consulted to see what kind of + * connectivity event occurred. + *

+ * If this is a connection that was the result of failing over from a + * disconnected network, then the FAILOVER_CONNECTION boolean extra is + * set to true. + *

+ * For a loss of connectivity, if the connectivity manager is attempting + * to connect (or has already connected) to another network, the + * NetworkInfo for the new network is also passed as an extra. This lets + * any receivers of the broadcast know that they should not necessarily + * tell the user that no data traffic will be possible. Instead, the + * reciever should expect another broadcast soon, indicating either that + * the failover attempt succeeded (and so there is still overall data + * connectivity), or that the failover attempt failed, meaning that all + * connectivity has been lost. + *

+ * For a disconnect event, the boolean extra EXTRA_NO_CONNECTIVITY + * is set to {@code true} if there are no connected networks at all. + */ + public static final String CONNECTIVITY_ACTION = "android.net.conn.CONNECTIVITY_CHANGE"; + /** + * The lookup key for a {@link NetworkInfo} object. Retrieve with + * {@link android.content.Intent#getParcelableExtra(String)}. + */ + public static final String EXTRA_NETWORK_INFO = "networkInfo"; + /** + * The lookup key for a boolean that indicates whether a connect event + * is for a network to which the connectivity manager was failing over + * following a disconnect on another network. + * Retrieve it with {@link android.content.Intent#getBooleanExtra(String,boolean)}. + */ + public static final String EXTRA_IS_FAILOVER = "isFailover"; + /** + * The lookup key for a {@link NetworkInfo} object. This is supplied when + * there is another network that it may be possible to connect to. Retrieve with + * {@link android.content.Intent#getParcelableExtra(String)}. + */ + public static final String EXTRA_OTHER_NETWORK_INFO = "otherNetwork"; + /** + * The lookup key for a boolean that indicates whether there is a + * complete lack of connectivity, i.e., no network is available. + * Retrieve it with {@link android.content.Intent#getBooleanExtra(String,boolean)}. + */ + public static final String EXTRA_NO_CONNECTIVITY = "noConnectivity"; + /** + * The lookup key for a string that indicates why an attempt to connect + * to a network failed. The string has no particular structure. It is + * intended to be used in notifications presented to users. Retrieve + * it with {@link android.content.Intent#getStringExtra(String)}. + */ + public static final String EXTRA_REASON = "reason"; + /** + * The lookup key for a string that provides optionally supplied + * extra information about the network state. The information + * may be passed up from the lower networking layers, and its + * meaning may be specific to a particular network type. Retrieve + * it with {@link android.content.Intent#getStringExtra(String)}. + */ + public static final String EXTRA_EXTRA_INFO = "extraInfo"; + + public static final int TYPE_MOBILE = 0; + public static final int TYPE_WIFI = 1; + + public static final int DEFAULT_NETWORK_PREFERENCE = TYPE_WIFI; + + private IConnectivityManager mService; + + static public boolean isNetworkTypeValid(int networkType) { + return networkType == TYPE_WIFI || networkType == TYPE_MOBILE; + } + + public void setNetworkPreference(int preference) { + try { + mService.setNetworkPreference(preference); + } catch (RemoteException e) { + } + } + + public int getNetworkPreference() { + try { + return mService.getNetworkPreference(); + } catch (RemoteException e) { + return -1; + } + } + + public NetworkInfo getActiveNetworkInfo() { + try { + return mService.getActiveNetworkInfo(); + } catch (RemoteException e) { + return null; + } + } + + public NetworkInfo getNetworkInfo(int networkType) { + try { + return mService.getNetworkInfo(networkType); + } catch (RemoteException e) { + return null; + } + } + + public NetworkInfo[] getAllNetworkInfo() { + try { + return mService.getAllNetworkInfo(); + } catch (RemoteException e) { + return null; + } + } + + /** {@hide} */ + public boolean setRadios(boolean turnOn) { + try { + return mService.setRadios(turnOn); + } catch (RemoteException e) { + return false; + } + } + + /** {@hide} */ + public boolean setRadio(int networkType, boolean turnOn) { + try { + return mService.setRadio(networkType, turnOn); + } catch (RemoteException e) { + return false; + } + } + + /** + * Tells the underlying networking system that the caller wants to + * begin using the named feature. The interpretation of {@code feature} + * is completely up to each networking implementation. + * @param networkType specifies which network the request pertains to + * @param feature the name of the feature to be used + * @return an integer value representing the outcome of the request. + * The interpretation of this value is specific to each networking + * implementation+feature combination, except that the value {@code -1} + * always indicates failure. + */ + public int startUsingNetworkFeature(int networkType, String feature) { + try { + return mService.startUsingNetworkFeature(networkType, feature); + } catch (RemoteException e) { + return -1; + } + } + + /** + * Tells the underlying networking system that the caller is finished + * using the named feature. The interpretation of {@code feature} + * is completely up to each networking implementation. + * @param networkType specifies which network the request pertains to + * @param feature the name of the feature that is no longer needed + * @return an integer value representing the outcome of the request. + * The interpretation of this value is specific to each networking + * implementation+feature combination, except that the value {@code -1} + * always indicates failure. + */ + public int stopUsingNetworkFeature(int networkType, String feature) { + try { + return mService.stopUsingNetworkFeature(networkType, feature); + } catch (RemoteException e) { + return -1; + } + } + + /** + * Ensure that a network route exists to deliver traffic to the specified + * host via the specified network interface. An attempt to add a route that + * already exists is ignored, but treated as successful. + * @param networkType the type of the network over which traffic to the specified + * host is to be routed + * @param hostAddress the IP address of the host to which the route is desired + * @return {@code true} on success, {@code false} on failure + */ + public boolean requestRouteToHost(int networkType, int hostAddress) { + try { + return mService.requestRouteToHost(networkType, hostAddress); + } catch (RemoteException e) { + return false; + } + } + + /** + * Don't allow use of default constructor. + */ + @SuppressWarnings({"UnusedDeclaration"}) + private ConnectivityManager() { + } + + /** + * {@hide} + */ + public ConnectivityManager(IConnectivityManager service) { + if (service == null) { + throw new IllegalArgumentException( + "ConnectivityManager() cannot be constructed with null service"); + } + mService = service; + } +} diff --git a/core/java/android/net/DhcpInfo.java b/core/java/android/net/DhcpInfo.java new file mode 100644 index 0000000000..1178bec39b --- /dev/null +++ b/core/java/android/net/DhcpInfo.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.os.Parcelable; +import android.os.Parcel; + +/** + * A simple object for retrieving the results of a DHCP request. + */ +public class DhcpInfo implements Parcelable { + public int ipAddress; + public int gateway; + public int netmask; + + public int dns1; + public int dns2; + + public int serverAddress; + public int leaseDuration; + + public DhcpInfo() { + super(); + } + + public String toString() { + StringBuffer str = new StringBuffer(); + + str.append("ipaddr "); putAddress(str, ipAddress); + str.append(" gateway "); putAddress(str, gateway); + str.append(" netmask "); putAddress(str, netmask); + str.append(" dns1 "); putAddress(str, dns1); + str.append(" dns2 "); putAddress(str, dns2); + str.append(" DHCP server "); putAddress(str, serverAddress); + str.append(" lease ").append(leaseDuration).append(" seconds"); + + return str.toString(); + } + + private static void putAddress(StringBuffer buf, int addr) { + buf.append(addr & 0xff).append('.'). + append((addr >>>= 8) & 0xff).append('.'). + append((addr >>>= 8) & 0xff).append('.'). + append((addr >>>= 8) & 0xff); + } + + /** Implement the Parcelable interface {@hide} */ + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface {@hide} */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(ipAddress); + dest.writeInt(gateway); + dest.writeInt(netmask); + dest.writeInt(dns1); + dest.writeInt(dns2); + dest.writeInt(serverAddress); + dest.writeInt(leaseDuration); + } + + /** Implement the Parcelable interface {@hide} */ + public static final Creator CREATOR = + new Creator() { + public DhcpInfo createFromParcel(Parcel in) { + DhcpInfo info = new DhcpInfo(); + info.ipAddress = in.readInt(); + info.gateway = in.readInt(); + info.netmask = in.readInt(); + info.dns1 = in.readInt(); + info.dns2 = in.readInt(); + info.serverAddress = in.readInt(); + info.leaseDuration = in.readInt(); + return info; + } + + public DhcpInfo[] newArray(int size) { + return new DhcpInfo[size]; + } + }; +} diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl new file mode 100644 index 0000000000..e1d921f4d9 --- /dev/null +++ b/core/java/android/net/IConnectivityManager.aidl @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2008, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.net.NetworkInfo; + +/** + * Interface that answers queries about, and allows changing, the + * state of network connectivity. + */ +/** {@hide} */ +interface IConnectivityManager +{ + void setNetworkPreference(int pref); + + int getNetworkPreference(); + + NetworkInfo getActiveNetworkInfo(); + + NetworkInfo getNetworkInfo(int networkType); + + NetworkInfo[] getAllNetworkInfo(); + + boolean setRadios(boolean onOff); + + boolean setRadio(int networkType, boolean turnOn); + + int startUsingNetworkFeature(int networkType, in String feature); + + int stopUsingNetworkFeature(int networkType, in String feature); + + boolean requestRouteToHost(int networkType, int hostAddress); +} diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java new file mode 100644 index 0000000000..f776abfb05 --- /dev/null +++ b/core/java/android/net/NetworkInfo.java @@ -0,0 +1,305 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.os.Parcelable; +import android.os.Parcel; + +import java.util.EnumMap; + +/** + * Describes the status of a network interface of a given type + * (currently either Mobile or Wifi). + */ +public class NetworkInfo implements Parcelable { + + /** + * Coarse-grained network state. This is probably what most applications should + * use, rather than {@link android.net.NetworkInfo.DetailedState DetailedState}. + * The mapping between the two is as follows: + *

+ * + * + * + * + * + * + * CONNECTED + * + * + * + * + *
Detailed stateCoarse-grained state
IDLEDISCONNECTED
SCANNINGCONNECTING
CONNECTINGCONNECTING
AUTHENTICATINGCONNECTING
CONNECTED
DISCONNECTINGDISCONNECTING
DISCONNECTEDDISCONNECTED
UNAVAILABLEDISCONNECTED
FAILEDDISCONNECTED
+ */ + public enum State { + CONNECTING, CONNECTED, SUSPENDED, DISCONNECTING, DISCONNECTED, UNKNOWN + } + + /** + * The fine-grained state of a network connection. This level of detail + * is probably of interest to few applications. Most should use + * {@link android.net.NetworkInfo.State State} instead. + */ + public enum DetailedState { + /** Ready to start data connection setup. */ + IDLE, + /** Searching for an available access point. */ + SCANNING, + /** Currently setting up data connection. */ + CONNECTING, + /** Network link established, performing authentication. */ + AUTHENTICATING, + /** Awaiting response from DHCP server in order to assign IP address information. */ + OBTAINING_IPADDR, + /** IP traffic should be available. */ + CONNECTED, + /** IP traffic is suspended */ + SUSPENDED, + /** Currently tearing down data connection. */ + DISCONNECTING, + /** IP traffic not available. */ + DISCONNECTED, + /** Attempt to connect failed. */ + FAILED + } + + /** + * This is the map described in the Javadoc comment above. The positions + * of the elements of the array must correspond to the ordinal values + * of DetailedState. + */ + private static final EnumMap stateMap = + new EnumMap(DetailedState.class); + + static { + stateMap.put(DetailedState.IDLE, State.DISCONNECTED); + stateMap.put(DetailedState.SCANNING, State.DISCONNECTED); + stateMap.put(DetailedState.CONNECTING, State.CONNECTING); + stateMap.put(DetailedState.AUTHENTICATING, State.CONNECTING); + stateMap.put(DetailedState.OBTAINING_IPADDR, State.CONNECTING); + stateMap.put(DetailedState.CONNECTED, State.CONNECTED); + stateMap.put(DetailedState.SUSPENDED, State.SUSPENDED); + stateMap.put(DetailedState.DISCONNECTING, State.DISCONNECTING); + stateMap.put(DetailedState.DISCONNECTED, State.DISCONNECTED); + stateMap.put(DetailedState.FAILED, State.DISCONNECTED); + } + + private int mNetworkType; + private State mState; + private DetailedState mDetailedState; + private String mReason; + private String mExtraInfo; + private boolean mIsFailover; + /** + * Indicates whether network connectivity is possible: + */ + private boolean mIsAvailable; + + public NetworkInfo(int type) { + if (!ConnectivityManager.isNetworkTypeValid(type)) { + throw new IllegalArgumentException("Invalid network type: " + type); + } + this.mNetworkType = type; + setDetailedState(DetailedState.IDLE, null, null); + mState = State.UNKNOWN; + mIsAvailable = true; + } + + /** + * Reports the type of network (currently mobile or Wi-Fi) to which the + * info in this object pertains. + * @return the network type + */ + public int getType() { + return mNetworkType; + } + + /** + * Indicates whether network connectivity exists or is in the process + * of being established. This is good for applications that need to + * do anything related to the network other than read or write data. + * For the latter, call {@link #isConnected()} instead, which guarantees + * that the network is fully usable. + * @return {@code true} if network connectivity exists or is in the process + * of being established, {@code false} otherwise. + */ + public boolean isConnectedOrConnecting() { + return mState == State.CONNECTED || mState == State.CONNECTING; + } + + /** + * Indicates whether network connectivity exists and it is possible to establish + * connections and pass data. + * @return {@code true} if network connectivity exists, {@code false} otherwise. + */ + public boolean isConnected() { + return mState == State.CONNECTED; + } + + /** + * Indicates whether network connectivity is possible. A network is unavailable + * when a persistent or semi-persistent condition prevents the possibility + * of connecting to that network. Examples include + *

+ * @return {@code true} if the network is available, {@code false} otherwise + */ + public boolean isAvailable() { + return mIsAvailable; + } + + /** + * Sets if the network is available, ie, if the connectivity is possible. + * @param isAvailable the new availability value. + * + * {@hide} + */ + public void setIsAvailable(boolean isAvailable) { + mIsAvailable = isAvailable; + } + + /** + * Indicates whether the current attempt to connect to the network + * resulted from the ConnectivityManager trying to fail over to this + * network following a disconnect from another network. + * @return {@code true} if this is a failover attempt, {@code false} + * otherwise. + */ + public boolean isFailover() { + return mIsFailover; + } + + /** {@hide} */ + public void setFailover(boolean isFailover) { + mIsFailover = isFailover; + } + + /** + * Reports the current coarse-grained state of the network. + * @return the coarse-grained state + */ + public State getState() { + return mState; + } + + /** + * Reports the current fine-grained state of the network. + * @return the fine-grained state + */ + public DetailedState getDetailedState() { + return mDetailedState; + } + + /** + * Sets the fine-grained state of the network. + * @param detailedState the {@link DetailedState}. + * @param reason a {@code String} indicating the reason for the state change, + * if one was supplied. May be {@code null}. + * @param extraInfo an optional {@code String} providing addditional network state + * information passed up from the lower networking layers. + * + * {@hide} + */ + void setDetailedState(DetailedState detailedState, String reason, String extraInfo) { + this.mDetailedState = detailedState; + this.mState = stateMap.get(detailedState); + this.mReason = reason; + this.mExtraInfo = extraInfo; + } + + /** + * Report the reason an attempt to establish connectivity failed, + * if one is available. + * @return the reason for failure, or null if not available + */ + public String getReason() { + return mReason; + } + + /** + * Report the extra information about the network state, if any was + * provided by the lower networking layers., + * if one is available. + * @return the extra information, or null if not available + */ + public String getExtraInfo() { + return mExtraInfo; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("NetworkInfo: "); + builder.append("type: ").append(getTypeName()).append(", state: ").append(mState). + append("/").append(mDetailedState). + append(", reason: ").append(mReason == null ? "(unspecified)" : mReason). + append(", extra: ").append(mExtraInfo == null ? "(none)" : mExtraInfo). + append(", failover: ").append(mIsFailover). + append(", isAvailable: ").append(mIsAvailable); + return builder.toString(); + } + + public String getTypeName() { + switch (mNetworkType) { + case ConnectivityManager.TYPE_WIFI: + return "WIFI"; + case ConnectivityManager.TYPE_MOBILE: + return "MOBILE"; + default: + return ""; + } + } + + /** Implement the Parcelable interface {@hide} */ + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface {@hide} */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mNetworkType); + dest.writeString(mState.name()); + dest.writeString(mDetailedState.name()); + dest.writeInt(mIsFailover ? 1 : 0); + dest.writeInt(mIsAvailable ? 1 : 0); + dest.writeString(mReason); + dest.writeString(mExtraInfo); + } + + /** Implement the Parcelable interface {@hide} */ + public static final Creator CREATOR = + new Creator() { + public NetworkInfo createFromParcel(Parcel in) { + int netType = in.readInt(); + NetworkInfo netInfo = new NetworkInfo(netType); + netInfo.mState = State.valueOf(in.readString()); + netInfo.mDetailedState = DetailedState.valueOf(in.readString()); + netInfo.mIsFailover = in.readInt() != 0; + netInfo.mIsAvailable = in.readInt() != 0; + netInfo.mReason = in.readString(); + netInfo.mExtraInfo = in.readString(); + return netInfo; + } + + public NetworkInfo[] newArray(int size) { + return new NetworkInfo[size]; + } + }; +} diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java new file mode 100644 index 0000000000..129248aebf --- /dev/null +++ b/core/java/android/net/NetworkUtils.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * Native methods for managing network interfaces. + * + * {@hide} + */ +public class NetworkUtils { + /** Bring the named network interface down. */ + public native static int disableInterface(String interfaceName); + + /** Add a route to the specified host via the named interface. */ + public native static int addHostRoute(String interfaceName, int hostaddr); + + /** Add a default route for the named interface. */ + public native static int setDefaultRoute(String interfaceName, int gwayAddr); + + /** Return the gateway address for the default route for the named interface. */ + public native static int getDefaultRoute(String interfaceName); + + /** Remove host routes that uses the named interface. */ + public native static int removeHostRoutes(String interfaceName); + + /** Remove the default route for the named interface. */ + public native static int removeDefaultRoute(String interfaceName); + + /** Reset any sockets that are connected via the named interface. */ + public native static int resetConnections(String interfaceName); + + /** + * Start the DHCP client daemon, in order to have it request addresses + * for the named interface, and then configure the interface with those + * addresses. This call blocks until it obtains a result (either success + * or failure) from the daemon. + * @param interfaceName the name of the interface to configure + * @param ipInfo if the request succeeds, this object is filled in with + * the IP address information. + * @return {@code true} for success, {@code false} for failure + */ + public native static boolean runDhcp(String interfaceName, DhcpInfo ipInfo); + + /** + * Shut down the DHCP client daemon. + * @param interfaceName the name of the interface for which the daemon + * should be stopped + * @return {@code true} for success, {@code false} for failure + */ + public native static boolean stopDhcp(String interfaceName); + + /** + * Return the last DHCP-related error message that was recorded. + *

NOTE: This string is not localized, but currently it is only + * used in logging. + * @return the most recent error message, if any + */ + public native static String getDhcpError(); + + /** + * When static IP configuration has been specified, configure the network + * interface according to the values supplied. + * @param interfaceName the name of the interface to configure + * @param ipInfo the IP address, default gateway, and DNS server addresses + * with which to configure the interface. + * @return {@code true} for success, {@code false} for failure + */ + public static boolean configureInterface(String interfaceName, DhcpInfo ipInfo) { + return configureNative(interfaceName, + ipInfo.ipAddress, + ipInfo.netmask, + ipInfo.gateway, + ipInfo.dns1, + ipInfo.dns2); + } + + private native static boolean configureNative( + String interfaceName, int ipAddress, int netmask, int gateway, int dns1, int dns2); + + /** + * Look up a host name and return the result as an int. Works if the argument + * is an IP address in dot notation. Obviously, this can only be used for IPv4 + * addresses. + * @param hostname the name of the host (or the IP address) + * @return the IP address as an {@code int} in network byte order + */ + public static int lookupHost(String hostname) { + InetAddress inetAddress; + try { + inetAddress = InetAddress.getByName(hostname); + } catch (UnknownHostException e) { + return -1; + } + byte[] addrBytes; + int addr; + addrBytes = inetAddress.getAddress(); + addr = ((addrBytes[3] & 0xff) << 24) + | ((addrBytes[2] & 0xff) << 16) + | ((addrBytes[1] & 0xff) << 8) + | (addrBytes[0] & 0xff); + return addr; + } +} diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp new file mode 100644 index 0000000000..417ce541e4 --- /dev/null +++ b/core/jni/android_net_NetUtils.cpp @@ -0,0 +1,235 @@ +/* + * Copyright 2008, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "NetUtils" + +#include "jni.h" +#include +#include +#include +#include + +extern "C" { +int ifc_disable(const char *ifname); +int ifc_add_host_route(const char *ifname, uint32_t addr); +int ifc_remove_host_routes(const char *ifname); +int ifc_set_default_route(const char *ifname, uint32_t gateway); +int ifc_get_default_route(const char *ifname); +int ifc_remove_default_route(const char *ifname); +int ifc_reset_connections(const char *ifname); +int ifc_configure(const char *ifname, in_addr_t ipaddr, in_addr_t netmask, in_addr_t gateway, in_addr_t dns1, in_addr_t dns2); + +int dhcp_do_request(const char *ifname, + in_addr_t *ipaddr, + in_addr_t *gateway, + in_addr_t *mask, + in_addr_t *dns1, + in_addr_t *dns2, + in_addr_t *server, + uint32_t *lease); +int dhcp_stop(const char *ifname); +char *dhcp_get_errmsg(); +} + +#define NETUTILS_PKG_NAME "android/net/NetworkUtils" + +namespace android { + +/* + * The following remembers the jfieldID's of the fields + * of the DhcpInfo Java object, so that we don't have + * to look them up every time. + */ +static struct fieldIds { + jclass dhcpInfoClass; + jmethodID constructorId; + jfieldID ipaddress; + jfieldID gateway; + jfieldID netmask; + jfieldID dns1; + jfieldID dns2; + jfieldID serverAddress; + jfieldID leaseDuration; +} dhcpInfoFieldIds; + +static jint android_net_utils_disableInterface(JNIEnv* env, jobject clazz, jstring ifname) +{ + int result; + + const char *nameStr = env->GetStringUTFChars(ifname, NULL); + result = ::ifc_disable(nameStr); + env->ReleaseStringUTFChars(ifname, nameStr); + return (jint)result; +} + +static jint android_net_utils_addHostRoute(JNIEnv* env, jobject clazz, jstring ifname, jint addr) +{ + int result; + + const char *nameStr = env->GetStringUTFChars(ifname, NULL); + result = ::ifc_add_host_route(nameStr, addr); + env->ReleaseStringUTFChars(ifname, nameStr); + return (jint)result; +} + +static jint android_net_utils_removeHostRoutes(JNIEnv* env, jobject clazz, jstring ifname) +{ + int result; + + const char *nameStr = env->GetStringUTFChars(ifname, NULL); + result = ::ifc_remove_host_routes(nameStr); + env->ReleaseStringUTFChars(ifname, nameStr); + return (jint)result; +} + +static jint android_net_utils_setDefaultRoute(JNIEnv* env, jobject clazz, jstring ifname, jint gateway) +{ + int result; + + const char *nameStr = env->GetStringUTFChars(ifname, NULL); + result = ::ifc_set_default_route(nameStr, gateway); + env->ReleaseStringUTFChars(ifname, nameStr); + return (jint)result; +} + +static jint android_net_utils_getDefaultRoute(JNIEnv* env, jobject clazz, jstring ifname) +{ + int result; + + const char *nameStr = env->GetStringUTFChars(ifname, NULL); + result = ::ifc_get_default_route(nameStr); + env->ReleaseStringUTFChars(ifname, nameStr); + return (jint)result; +} + +static jint android_net_utils_removeDefaultRoute(JNIEnv* env, jobject clazz, jstring ifname) +{ + int result; + + const char *nameStr = env->GetStringUTFChars(ifname, NULL); + result = ::ifc_remove_default_route(nameStr); + env->ReleaseStringUTFChars(ifname, nameStr); + return (jint)result; +} + +static jint android_net_utils_resetConnections(JNIEnv* env, jobject clazz, jstring ifname) +{ + int result; + + const char *nameStr = env->GetStringUTFChars(ifname, NULL); + result = ::ifc_reset_connections(nameStr); + env->ReleaseStringUTFChars(ifname, nameStr); + return (jint)result; +} + +static jboolean android_net_utils_runDhcp(JNIEnv* env, jobject clazz, jstring ifname, jobject info) +{ + int result; + in_addr_t ipaddr, gateway, mask, dns1, dns2, server; + uint32_t lease; + + const char *nameStr = env->GetStringUTFChars(ifname, NULL); + result = ::dhcp_do_request(nameStr, &ipaddr, &gateway, &mask, + &dns1, &dns2, &server, &lease); + env->ReleaseStringUTFChars(ifname, nameStr); + if (result == 0 && dhcpInfoFieldIds.dhcpInfoClass != NULL) { + env->SetIntField(info, dhcpInfoFieldIds.ipaddress, ipaddr); + env->SetIntField(info, dhcpInfoFieldIds.gateway, gateway); + env->SetIntField(info, dhcpInfoFieldIds.netmask, mask); + env->SetIntField(info, dhcpInfoFieldIds.dns1, dns1); + env->SetIntField(info, dhcpInfoFieldIds.dns2, dns2); + env->SetIntField(info, dhcpInfoFieldIds.serverAddress, server); + env->SetIntField(info, dhcpInfoFieldIds.leaseDuration, lease); + } + return (jboolean)(result == 0); +} + +static jboolean android_net_utils_stopDhcp(JNIEnv* env, jobject clazz, jstring ifname, jobject info) +{ + int result; + + const char *nameStr = env->GetStringUTFChars(ifname, NULL); + result = ::dhcp_stop(nameStr); + env->ReleaseStringUTFChars(ifname, nameStr); + return (jboolean)(result == 0); +} + +static jstring android_net_utils_getDhcpError(JNIEnv* env, jobject clazz) +{ + return env->NewStringUTF(::dhcp_get_errmsg()); +} + +static jboolean android_net_utils_configureInterface(JNIEnv* env, + jobject clazz, + jstring ifname, + jint ipaddr, + jint mask, + jint gateway, + jint dns1, + jint dns2) +{ + int result; + uint32_t lease; + + const char *nameStr = env->GetStringUTFChars(ifname, NULL); + result = ::ifc_configure(nameStr, ipaddr, mask, gateway, dns1, dns2); + env->ReleaseStringUTFChars(ifname, nameStr); + return (jboolean)(result == 0); +} + +// ---------------------------------------------------------------------------- + +/* + * JNI registration. + */ +static JNINativeMethod gNetworkUtilMethods[] = { + /* name, signature, funcPtr */ + + { "disableInterface", "(Ljava/lang/String;)I", (void *)android_net_utils_disableInterface }, + { "addHostRoute", "(Ljava/lang/String;I)I", (void *)android_net_utils_addHostRoute }, + { "removeHostRoutes", "(Ljava/lang/String;)I", (void *)android_net_utils_removeHostRoutes }, + { "setDefaultRoute", "(Ljava/lang/String;I)I", (void *)android_net_utils_setDefaultRoute }, + { "getDefaultRoute", "(Ljava/lang/String;)I", (void *)android_net_utils_getDefaultRoute }, + { "removeDefaultRoute", "(Ljava/lang/String;)I", (void *)android_net_utils_removeDefaultRoute }, + { "resetConnections", "(Ljava/lang/String;)I", (void *)android_net_utils_resetConnections }, + { "runDhcp", "(Ljava/lang/String;Landroid/net/DhcpInfo;)Z", (void *)android_net_utils_runDhcp }, + { "stopDhcp", "(Ljava/lang/String;)Z", (void *)android_net_utils_stopDhcp }, + { "configureNative", "(Ljava/lang/String;IIIII)Z", (void *)android_net_utils_configureInterface }, + { "getDhcpError", "()Ljava/lang/String;", (void*) android_net_utils_getDhcpError }, +}; + +int register_android_net_NetworkUtils(JNIEnv* env) +{ + jclass netutils = env->FindClass(NETUTILS_PKG_NAME); + LOG_FATAL_IF(netutils == NULL, "Unable to find class " NETUTILS_PKG_NAME); + + dhcpInfoFieldIds.dhcpInfoClass = env->FindClass("android/net/DhcpInfo"); + if (dhcpInfoFieldIds.dhcpInfoClass != NULL) { + dhcpInfoFieldIds.constructorId = env->GetMethodID(dhcpInfoFieldIds.dhcpInfoClass, "", "()V"); + dhcpInfoFieldIds.ipaddress = env->GetFieldID(dhcpInfoFieldIds.dhcpInfoClass, "ipAddress", "I"); + dhcpInfoFieldIds.gateway = env->GetFieldID(dhcpInfoFieldIds.dhcpInfoClass, "gateway", "I"); + dhcpInfoFieldIds.netmask = env->GetFieldID(dhcpInfoFieldIds.dhcpInfoClass, "netmask", "I"); + dhcpInfoFieldIds.dns1 = env->GetFieldID(dhcpInfoFieldIds.dhcpInfoClass, "dns1", "I"); + dhcpInfoFieldIds.dns2 = env->GetFieldID(dhcpInfoFieldIds.dhcpInfoClass, "dns2", "I"); + dhcpInfoFieldIds.serverAddress = env->GetFieldID(dhcpInfoFieldIds.dhcpInfoClass, "serverAddress", "I"); + dhcpInfoFieldIds.leaseDuration = env->GetFieldID(dhcpInfoFieldIds.dhcpInfoClass, "leaseDuration", "I"); + } + + return AndroidRuntime::registerNativeMethods(env, + NETUTILS_PKG_NAME, gNetworkUtilMethods, NELEM(gNetworkUtilMethods)); +} + +}; // namespace android diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java new file mode 100644 index 0000000000..11f34cc861 --- /dev/null +++ b/services/java/com/android/server/ConnectivityService.java @@ -0,0 +1,693 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import android.app.Notification; +import android.app.NotificationManager; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.ConnectivityManager; +import android.net.IConnectivityManager; +import android.net.MobileDataStateTracker; +import android.net.NetworkInfo; +import android.net.NetworkStateTracker; +import android.net.wifi.WifiStateTracker; +import android.os.Binder; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.ServiceManager; +import android.os.SystemProperties; +import android.provider.Settings; +import android.telephony.TelephonyManager; +import android.util.EventLog; +import android.util.Log; + +import java.io.FileDescriptor; +import java.io.PrintWriter; + +/** + * {@hide} + */ +public class ConnectivityService extends IConnectivityManager.Stub { + + private static final boolean DBG = false; + private static final String TAG = "ConnectivityService"; + + // Event log tags (must be in sync with event-log-tags) + private static final int EVENTLOG_CONNECTIVITY_STATE_CHANGED = 50020; + + /** + * Sometimes we want to refer to the individual network state + * trackers separately, and sometimes we just want to treat them + * abstractly. + */ + private NetworkStateTracker mNetTrackers[]; + private boolean mTeardownRequested[]; + private WifiStateTracker mWifiStateTracker; + private MobileDataStateTracker mMobileDataStateTracker; + private WifiWatchdogService mWifiWatchdogService; + + private Context mContext; + private int mNetworkPreference; + private NetworkStateTracker mActiveNetwork; + + private int mNumDnsEntries; + private static int mDnsChangeCounter; + + private boolean mTestMode; + private static ConnectivityService sServiceInstance; + + private static class ConnectivityThread extends Thread { + private Context mContext; + + private ConnectivityThread(Context context) { + super("ConnectivityThread"); + mContext = context; + } + + @Override + public void run() { + Looper.prepare(); + synchronized (this) { + sServiceInstance = new ConnectivityService(mContext); + notifyAll(); + } + Looper.loop(); + } + + public static ConnectivityService getServiceInstance(Context context) { + ConnectivityThread thread = new ConnectivityThread(context); + thread.start(); + + synchronized (thread) { + while (sServiceInstance == null) { + try { + // Wait until sServiceInstance has been initialized. + thread.wait(); + } catch (InterruptedException e) { + } + } + } + + return sServiceInstance; + } + } + + public static ConnectivityService getInstance(Context context) { + return ConnectivityThread.getServiceInstance(context); + } + + private ConnectivityService(Context context) { + if (DBG) Log.v(TAG, "ConnectivityService starting up"); + mContext = context; + mNetTrackers = new NetworkStateTracker[2]; + mTeardownRequested = new boolean[2]; + Handler handler = new MyHandler(); + + mNetworkPreference = getPersistedNetworkPreference(); + + /* + * Create the network state trackers for Wi-Fi and mobile + * data. Maybe this could be done with a factory class, + * but it's not clear that it's worth it, given that + * the number of different network types is not going + * to change very often. + */ + if (DBG) Log.v(TAG, "Starting Wifi Service."); + mWifiStateTracker = new WifiStateTracker(context, handler); + WifiService wifiService = new WifiService(context, mWifiStateTracker); + ServiceManager.addService(Context.WIFI_SERVICE, wifiService); + // The WifiStateTracker should appear first in the list + mNetTrackers[ConnectivityManager.TYPE_WIFI] = mWifiStateTracker; + + mMobileDataStateTracker = new MobileDataStateTracker(context, handler); + mNetTrackers[ConnectivityManager.TYPE_MOBILE] = mMobileDataStateTracker; + + mActiveNetwork = null; + mNumDnsEntries = 0; + + mTestMode = SystemProperties.get("cm.test.mode").equals("true") + && SystemProperties.get("ro.build.type").equals("eng"); + + for (NetworkStateTracker t : mNetTrackers) + t.startMonitoring(); + + // Constructing this starts it too + mWifiWatchdogService = new WifiWatchdogService(context, mWifiStateTracker); + } + + /** + * Sets the preferred network. + * @param preference the new preference + */ + public synchronized void setNetworkPreference(int preference) { + enforceChangePermission(); + if (ConnectivityManager.isNetworkTypeValid(preference)) { + int oldPreference = mNetworkPreference; + persistNetworkPreference(preference); + if (mNetworkPreference != oldPreference) + enforcePreference(); + } + } + + public int getNetworkPreference() { + enforceAccessPermission(); + return mNetworkPreference; + } + + private void persistNetworkPreference(int networkPreference) { + final ContentResolver cr = mContext.getContentResolver(); + Settings.System.putInt(cr, Settings.System.NETWORK_PREFERENCE, networkPreference); + } + + private int getPersistedNetworkPreference() { + final ContentResolver cr = mContext.getContentResolver(); + + final int networkPrefSetting = Settings.System + .getInt(cr, Settings.System.NETWORK_PREFERENCE, -1); + if (networkPrefSetting != -1) { + return networkPrefSetting; + } + + return ConnectivityManager.DEFAULT_NETWORK_PREFERENCE; + } + + /** + * Make the state of network connectivity conform to the preference settings. + * In this method, we only tear down a non-preferred network. Establishing + * a connection to the preferred network is taken care of when we handle + * the disconnect event from the non-preferred network + * (see {@link #handleDisconnect(NetworkInfo)}). + */ + private void enforcePreference() { + if (mActiveNetwork == null) + return; + + for (NetworkStateTracker t : mNetTrackers) { + if (t == mActiveNetwork) { + int netType = t.getNetworkInfo().getType(); + int otherNetType = ((netType == ConnectivityManager.TYPE_WIFI) ? + ConnectivityManager.TYPE_MOBILE : + ConnectivityManager.TYPE_WIFI); + + if (t.getNetworkInfo().getType() != mNetworkPreference) { + NetworkStateTracker otherTracker = mNetTrackers[otherNetType]; + if (otherTracker.isAvailable()) { + teardown(t); + } + } + } + } + } + + private boolean teardown(NetworkStateTracker netTracker) { + if (netTracker.teardown()) { + mTeardownRequested[netTracker.getNetworkInfo().getType()] = true; + return true; + } else { + return false; + } + } + + /** + * Return NetworkInfo for the active network interface. It is assumed that at most + * one network is active at a time. If more than one is active, it is indeterminate + * which will be returned. + * @return the info for the active network, or {@code null} if none is active + */ + public NetworkInfo getActiveNetworkInfo() { + enforceAccessPermission(); + for (NetworkStateTracker t : mNetTrackers) { + NetworkInfo info = t.getNetworkInfo(); + if (info.isConnected()) { + return info; + } + } + return null; + } + + public NetworkInfo getNetworkInfo(int networkType) { + enforceAccessPermission(); + if (ConnectivityManager.isNetworkTypeValid(networkType)) { + NetworkStateTracker t = mNetTrackers[networkType]; + if (t != null) + return t.getNetworkInfo(); + } + return null; + } + + public NetworkInfo[] getAllNetworkInfo() { + enforceAccessPermission(); + NetworkInfo[] result = new NetworkInfo[mNetTrackers.length]; + int i = 0; + for (NetworkStateTracker t : mNetTrackers) { + result[i++] = t.getNetworkInfo(); + } + return result; + } + + public boolean setRadios(boolean turnOn) { + boolean result = true; + enforceChangePermission(); + for (NetworkStateTracker t : mNetTrackers) { + result = t.setRadio(turnOn) && result; + } + return result; + } + + public boolean setRadio(int netType, boolean turnOn) { + enforceChangePermission(); + if (!ConnectivityManager.isNetworkTypeValid(netType)) { + return false; + } + NetworkStateTracker tracker = mNetTrackers[netType]; + return tracker != null && tracker.setRadio(turnOn); + } + + public int startUsingNetworkFeature(int networkType, String feature) { + enforceChangePermission(); + if (!ConnectivityManager.isNetworkTypeValid(networkType)) { + return -1; + } + NetworkStateTracker tracker = mNetTrackers[networkType]; + if (tracker != null) { + return tracker.startUsingNetworkFeature(feature, getCallingPid(), getCallingUid()); + } + return -1; + + } + + public int stopUsingNetworkFeature(int networkType, String feature) { + enforceChangePermission(); + if (!ConnectivityManager.isNetworkTypeValid(networkType)) { + return -1; + } + NetworkStateTracker tracker = mNetTrackers[networkType]; + if (tracker != null) { + return tracker.stopUsingNetworkFeature(feature, getCallingPid(), getCallingUid()); + } + return -1; + } + + /** + * Ensure that a network route exists to deliver traffic to the specified + * host via the specified network interface. + * @param networkType the type of the network over which traffic to the specified + * host is to be routed + * @param hostAddress the IP address of the host to which the route is desired + * @return {@code true} on success, {@code false} on failure + */ + public boolean requestRouteToHost(int networkType, int hostAddress) { + enforceChangePermission(); + if (!ConnectivityManager.isNetworkTypeValid(networkType)) { + return false; + } + NetworkStateTracker tracker = mNetTrackers[networkType]; + /* + * If there's only one connected network, and it's the one requested, + * then we don't have to do anything - the requested route already + * exists. If it's not the requested network, then it's not possible + * to establish the requested route. Finally, if there is more than + * one connected network, then we must insert an entry in the routing + * table. + */ + if (getNumConnectedNetworks() > 1) { + return tracker.requestRouteToHost(hostAddress); + } else { + return tracker.getNetworkInfo().getType() == networkType; + } + } + + private int getNumConnectedNetworks() { + int numConnectedNets = 0; + + for (NetworkStateTracker nt : mNetTrackers) { + if (nt.getNetworkInfo().isConnected() + && !mTeardownRequested[nt.getNetworkInfo().getType()]) { + ++numConnectedNets; + } + } + return numConnectedNets; + } + + private void enforceAccessPermission() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_NETWORK_STATE, + "ConnectivityService"); + } + + private void enforceChangePermission() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_NETWORK_STATE, + "ConnectivityService"); + + } + + /** + * Handle a {@code DISCONNECTED} event. If this pertains to the non-active network, + * we ignore it. If it is for the active network, we send out a broadcast. + * But first, we check whether it might be possible to connect to a different + * network. + * @param info the {@code NetworkInfo} for the network + */ + private void handleDisconnect(NetworkInfo info) { + + if (DBG) Log.v(TAG, "Handle DISCONNECT for " + info.getTypeName()); + + /* + * If the disconnected network is not the active one, then don't report + * this as a loss of connectivity. What probably happened is that we're + * getting the disconnect for a network that we explicitly disabled + * in accordance with network preference policies. + */ + mTeardownRequested[info.getType()] = false; + if (mActiveNetwork == null || info.getType() != mActiveNetwork.getNetworkInfo().getType()) + return; + + NetworkStateTracker newNet; + if (info.getType() == ConnectivityManager.TYPE_MOBILE) { + newNet = mWifiStateTracker; + } else /* info().getType() == TYPE_WIFI */ { + newNet = mMobileDataStateTracker; + } + + NetworkInfo switchTo = null; + if (newNet.isAvailable()) { + mActiveNetwork = newNet; + switchTo = newNet.getNetworkInfo(); + switchTo.setFailover(true); + if (!switchTo.isConnectedOrConnecting()) + newNet.reconnect(); + } + + boolean otherNetworkConnected = false; + Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION); + intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info); + if (info.isFailover()) { + intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true); + info.setFailover(false); + } + if (info.getReason() != null) { + intent.putExtra(ConnectivityManager.EXTRA_REASON, info.getReason()); + } + if (info.getExtraInfo() != null) { + intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, info.getExtraInfo()); + } + if (switchTo != null) { + otherNetworkConnected = switchTo.isConnected(); + if (DBG) { + if (otherNetworkConnected) { + Log.v(TAG, "Switching to already connected " + switchTo.getTypeName()); + } else { + Log.v(TAG, "Attempting to switch to " + switchTo.getTypeName()); + } + } + intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO, switchTo); + } else { + intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true); + } + if (DBG) Log.v(TAG, "Sending DISCONNECT bcast for " + info.getTypeName() + + (switchTo == null ? "" : " other=" + switchTo.getTypeName())); + + mContext.sendStickyBroadcast(intent); + /* + * If the failover network is already connected, then immediately send out + * a followup broadcast indicating successful failover + */ + if (switchTo != null && otherNetworkConnected) + sendConnectedBroadcast(switchTo); + } + + private void sendConnectedBroadcast(NetworkInfo info) { + Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION); + intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info); + if (info.isFailover()) { + intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true); + info.setFailover(false); + } + if (info.getReason() != null) { + intent.putExtra(ConnectivityManager.EXTRA_REASON, info.getReason()); + } + if (info.getExtraInfo() != null) { + intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, info.getExtraInfo()); + } + mContext.sendStickyBroadcast(intent); + } + + private void handleConnectionFailure(NetworkInfo info) { + mTeardownRequested[info.getType()] = false; + if (getActiveNetworkInfo() == null) { + String reason = info.getReason(); + String extraInfo = info.getExtraInfo(); + + if (DBG) { + String reasonText; + if (reason == null) { + reasonText = "."; + } else { + reasonText = " (" + reason + ")."; + } + Log.v(TAG, "Attempt to connect to " + info.getTypeName() + " failed" + reasonText); + } + + Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION); + intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info); + intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true); + if (reason != null) { + intent.putExtra(ConnectivityManager.EXTRA_REASON, reason); + } + if (extraInfo != null) { + intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, extraInfo); + } + if (info.isFailover()) { + intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true); + info.setFailover(false); + } + mContext.sendStickyBroadcast(intent); + } + } + + private void handleConnect(NetworkInfo info) { + if (DBG) Log.v(TAG, "Handle CONNECT for " + info.getTypeName()); + + // snapshot isFailover, because sendConnectedBroadcast() resets it + boolean isFailover = info.isFailover(); + NetworkStateTracker thisNet = mNetTrackers[info.getType()]; + NetworkStateTracker deadnet = null; + NetworkStateTracker otherNet; + if (info.getType() == ConnectivityManager.TYPE_MOBILE) { + otherNet = mWifiStateTracker; + } else /* info().getType() == TYPE_WIFI */ { + otherNet = mMobileDataStateTracker; + } + /* + * Check policy to see whether we are now connected to a network that + * takes precedence over the other one. If so, we need to tear down + * the other one. + */ + NetworkInfo wifiInfo = mWifiStateTracker.getNetworkInfo(); + NetworkInfo mobileInfo = mMobileDataStateTracker.getNetworkInfo(); + if (wifiInfo.isConnected() && mobileInfo.isConnected()) { + if (mNetworkPreference == ConnectivityManager.TYPE_WIFI) + deadnet = mMobileDataStateTracker; + else + deadnet = mWifiStateTracker; + } + + boolean toredown = false; + mTeardownRequested[info.getType()] = false; + if (!mTestMode && deadnet != null) { + if (DBG) Log.v(TAG, "Policy requires " + + deadnet.getNetworkInfo().getTypeName() + " teardown"); + toredown = teardown(deadnet); + if (DBG && !toredown) { + Log.d(TAG, "Network declined teardown request"); + } + } + + if (!toredown || deadnet.getNetworkInfo().getType() != info.getType()) { + mActiveNetwork = thisNet; + if (DBG) Log.v(TAG, "Sending CONNECT bcast for " + info.getTypeName()); + thisNet.updateNetworkSettings(); + sendConnectedBroadcast(info); + if (isFailover) { + otherNet.releaseWakeLock(); + } + } else { + if (DBG) Log.v(TAG, "Not broadcasting CONNECT_ACTION to torn down network " + + info.getTypeName()); + } + } + + private void handleScanResultsAvailable(NetworkInfo info) { + int networkType = info.getType(); + if (networkType != ConnectivityManager.TYPE_WIFI) { + if (DBG) Log.v(TAG, "Got ScanResultsAvailable for " + info.getTypeName() + " network." + + " Don't know how to handle."); + } + + mNetTrackers[networkType].interpretScanResultsAvailable(); + } + + private void handleNotificationChange(boolean visible, int id, Notification notification) { + NotificationManager notificationManager = (NotificationManager) mContext + .getSystemService(Context.NOTIFICATION_SERVICE); + + if (visible) { + notificationManager.notify(id, notification); + } else { + notificationManager.cancel(id); + } + } + + /** + * After any kind of change in the connectivity state of any network, + * make sure that anything that depends on the connectivity state of + * more than one network is set up correctly. We're mainly concerned + * with making sure that the list of DNS servers is set up according + * to which networks are connected, and ensuring that the right routing + * table entries exist. + */ + private void handleConnectivityChange() { + /* + * If both mobile and wifi are enabled, add the host routes that + * will allow MMS traffic to pass on the mobile network. But + * remove the default route for the mobile network, so that there + * will be only one default route, to ensure that all traffic + * except MMS will travel via Wi-Fi. + */ + int numConnectedNets = handleConfigurationChange(); + if (numConnectedNets > 1) { + mMobileDataStateTracker.addPrivateRoutes(); + mMobileDataStateTracker.removeDefaultRoute(); + } else if (mMobileDataStateTracker.getNetworkInfo().isConnected()) { + mMobileDataStateTracker.removePrivateRoutes(); + mMobileDataStateTracker.restoreDefaultRoute(); + } + } + + private int handleConfigurationChange() { + /* + * Set DNS properties. Always put Wi-Fi entries at the front of + * the list if it is active. + */ + int index = 1; + String lastDns = ""; + int numConnectedNets = 0; + int incrValue = ConnectivityManager.TYPE_MOBILE - ConnectivityManager.TYPE_WIFI; + int stopValue = ConnectivityManager.TYPE_MOBILE + incrValue; + + for (int net = ConnectivityManager.TYPE_WIFI; net != stopValue; net += incrValue) { + NetworkStateTracker nt = mNetTrackers[net]; + if (nt.getNetworkInfo().isConnected() + && !mTeardownRequested[nt.getNetworkInfo().getType()]) { + ++numConnectedNets; + String[] dnsList = nt.getNameServers(); + for (int i = 0; i < dnsList.length && dnsList[i] != null; i++) { + // skip duplicate entries + if (!dnsList[i].equals(lastDns)) { + SystemProperties.set("net.dns" + index++, dnsList[i]); + lastDns = dnsList[i]; + } + } + } + } + // Null out any DNS properties that are no longer used + for (int i = index; i <= mNumDnsEntries; i++) { + SystemProperties.set("net.dns" + i, ""); + } + mNumDnsEntries = index - 1; + // Notify the name resolver library of the change + SystemProperties.set("net.dnschange", String.valueOf(mDnsChangeCounter++)); + return numConnectedNets; + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (mContext.checkCallingPermission(android.Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + pw.println("Permission Denial: can't dump ConnectivityService from from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid()); + return; + } + if (mActiveNetwork == null) { + pw.println("No active network"); + } else { + pw.println("Active network: " + mActiveNetwork.getNetworkInfo().getTypeName()); + } + pw.println(); + for (NetworkStateTracker nst : mNetTrackers) { + pw.println(nst.getNetworkInfo()); + pw.println(nst); + pw.println(); + } + } + + private class MyHandler extends Handler { + @Override + public void handleMessage(Message msg) { + NetworkInfo info; + switch (msg.what) { + case NetworkStateTracker.EVENT_STATE_CHANGED: + info = (NetworkInfo) msg.obj; + if (DBG) Log.v(TAG, "ConnectivityChange for " + info.getTypeName() + ": " + + info.getState() + "/" + info.getDetailedState()); + + // Connectivity state changed: + // [31-11] Reserved for future use + // [10-9] Mobile network connection type (as defined by the TelephonyManager) + // [8-3] Detailed state ordinal (as defined by NetworkInfo.DetailedState) + // [2-0] Network type (as defined by ConnectivityManager) + int eventLogParam = (info.getType() & 0x7) | + ((info.getDetailedState().ordinal() & 0x3f) << 3) | + (TelephonyManager.getDefault().getNetworkType() << 9); + EventLog.writeEvent(EVENTLOG_CONNECTIVITY_STATE_CHANGED, eventLogParam); + + if (info.getDetailedState() == NetworkInfo.DetailedState.FAILED) { + handleConnectionFailure(info); + } else if (info.getState() == NetworkInfo.State.DISCONNECTED) { + handleDisconnect(info); + } else if (info.getState() == NetworkInfo.State.SUSPENDED) { + // TODO: need to think this over. + // the logic here is, handle SUSPENDED the same as DISCONNECTED. The + // only difference being we are broadcasting an intent with NetworkInfo + // that's suspended. This allows the applications an opportunity to + // handle DISCONNECTED and SUSPENDED differently, or not. + handleDisconnect(info); + } else if (info.getState() == NetworkInfo.State.CONNECTED) { + handleConnect(info); + } + handleConnectivityChange(); + break; + + case NetworkStateTracker.EVENT_SCAN_RESULTS_AVAILABLE: + info = (NetworkInfo) msg.obj; + handleScanResultsAvailable(info); + break; + + case NetworkStateTracker.EVENT_NOTIFICATION_CHANGED: + handleNotificationChange(msg.arg1 == 1, msg.arg2, (Notification) msg.obj); + + case NetworkStateTracker.EVENT_CONFIGURATION_CHANGED: + handleConfigurationChange(); + break; + } + } + } +}