/* * 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.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.RemoteException; import android.os.Handler; import android.os.ServiceManager; import android.os.SystemProperties; import com.android.internal.telephony.ITelephony; import com.android.internal.telephony.Phone; import com.android.internal.telephony.TelephonyIntents; import android.net.NetworkInfo.DetailedState; import android.telephony.TelephonyManager; import android.util.Log; import android.text.TextUtils; import java.util.List; import java.util.ArrayList; /** * Track the state of mobile data connectivity. This is done by * receiving broadcast intents from the Phone process whenever * the state of data connectivity changes. * * {@hide} */ public class MobileDataStateTracker extends NetworkStateTracker { private static final String TAG = "MobileDataStateTracker"; private static final boolean DBG = false; private Phone.DataState mMobileDataState; private ITelephony mPhoneService; private static final String[] sDnsPropNames = { "net.rmnet0.dns1", "net.rmnet0.dns2", "net.ccinet0.dns1", "net.ccinet0.dns2", "net.eth0.dns1", "net.eth0.dns2", "net.eth0.dns3", "net.eth0.dns4", "net.gprs.dns1", "net.gprs.dns2" }; private List<String> mDnsServers; private String mInterfaceName; private int mDefaultGatewayAddr; private int mLastCallingPid = -1; /** * Create a new MobileDataStateTracker * @param context the application context of the caller * @param target a message handler for getting callbacks about state changes */ public MobileDataStateTracker(Context context, Handler target) { super(context, target, ConnectivityManager.TYPE_MOBILE, TelephonyManager.getDefault().getNetworkType(), "MOBILE", TelephonyManager.getDefault().getNetworkTypeName()); mPhoneService = null; mDnsServers = new ArrayList<String>(); } /** * Begin monitoring mobile data connectivity. */ public void startMonitoring() { IntentFilter filter = new IntentFilter(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED); filter.addAction(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED); filter.addAction(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED); Intent intent = mContext.registerReceiver(new MobileDataStateReceiver(), filter); if (intent != null) mMobileDataState = getMobileDataState(intent); else mMobileDataState = Phone.DataState.DISCONNECTED; } private static Phone.DataState getMobileDataState(Intent intent) { String str = intent.getStringExtra(Phone.STATE_KEY); if (str != null) return Enum.valueOf(Phone.DataState.class, str); else return Phone.DataState.DISCONNECTED; } private class MobileDataStateReceiver extends BroadcastReceiver { public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED)) { Phone.DataState state = getMobileDataState(intent); String reason = intent.getStringExtra(Phone.STATE_CHANGE_REASON_KEY); String apnName = intent.getStringExtra(Phone.DATA_APN_KEY); boolean unavailable = intent.getBooleanExtra(Phone.NETWORK_UNAVAILABLE_KEY, false); if (DBG) Log.d(TAG, "Received " + intent.getAction() + " broadcast - state = " + state + ", unavailable = " + unavailable + ", reason = " + (reason == null ? "(unspecified)" : reason)); mNetworkInfo.setIsAvailable(!unavailable); if (mMobileDataState != state) { mMobileDataState = state; switch (state) { case DISCONNECTED: setDetailedState(DetailedState.DISCONNECTED, reason, apnName); if (mInterfaceName != null) { NetworkUtils.resetConnections(mInterfaceName); } mInterfaceName = null; mDefaultGatewayAddr = 0; break; case CONNECTING: setDetailedState(DetailedState.CONNECTING, reason, apnName); break; case SUSPENDED: setDetailedState(DetailedState.SUSPENDED, reason, apnName); break; case CONNECTED: mInterfaceName = intent.getStringExtra(Phone.DATA_IFACE_NAME_KEY); if (mInterfaceName == null) { Log.d(TAG, "CONNECTED event did not supply interface name."); } setupDnsProperties(); setDetailedState(DetailedState.CONNECTED, reason, apnName); break; } } } else if (intent.getAction().equals(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED)) { String reason = intent.getStringExtra(Phone.FAILURE_REASON_KEY); String apnName = intent.getStringExtra(Phone.DATA_APN_KEY); if (DBG) Log.d(TAG, "Received " + intent.getAction() + " broadcast" + reason == null ? "" : "(" + reason + ")"); setDetailedState(DetailedState.FAILED, reason, apnName); } TelephonyManager tm = TelephonyManager.getDefault(); setRoamingStatus(tm.isNetworkRoaming()); setSubtype(tm.getNetworkType(), tm.getNetworkTypeName()); } } /** * Make sure that route(s) exist to the carrier DNS server(s). */ public void addPrivateRoutes() { if (mInterfaceName != null) { for (String addrString : mDnsServers) { int addr = NetworkUtils.lookupHost(addrString); if (addr != -1) { NetworkUtils.addHostRoute(mInterfaceName, addr); } } } } public void removePrivateRoutes() { if(mInterfaceName != null) { NetworkUtils.removeHostRoutes(mInterfaceName); } } public void removeDefaultRoute() { if(mInterfaceName != null) { mDefaultGatewayAddr = NetworkUtils.getDefaultRoute(mInterfaceName); NetworkUtils.removeDefaultRoute(mInterfaceName); } } public void restoreDefaultRoute() { // 0 is not a valid address for a gateway if (mInterfaceName != null && mDefaultGatewayAddr != 0) { NetworkUtils.setDefaultRoute(mInterfaceName, mDefaultGatewayAddr); } } private void getPhoneService(boolean forceRefresh) { if ((mPhoneService == null) || forceRefresh) { mPhoneService = ITelephony.Stub.asInterface(ServiceManager.getService("phone")); } } /** * Report whether data connectivity is possible. */ public boolean isAvailable() { getPhoneService(false); /* * If the phone process has crashed in the past, we'll get a * RemoteException and need to re-reference the service. */ for (int retry = 0; retry < 2; retry++) { if (mPhoneService == null) break; try { return mPhoneService.isDataConnectivityPossible(); } catch (RemoteException e) { // First-time failed, get the phone service again if (retry == 0) getPhoneService(true); } } return false; } /** * Return the IP addresses of the DNS servers available for the mobile data * network interface. * @return a list of DNS addresses, with no holes. */ public String[] getNameServers() { return getNameServerList(sDnsPropNames); } /** * {@inheritDoc} * The mobile data network subtype indicates what generation network technology is in effect, * e.g., GPRS, EDGE, UMTS, etc. */ public int getNetworkSubtype() { return TelephonyManager.getDefault().getNetworkType(); } /** * Return the system properties name associated with the tcp buffer sizes * for this network. */ public String getTcpBufferSizesPropName() { String networkTypeStr = "unknown"; TelephonyManager tm = new TelephonyManager(mContext); switch(tm.getNetworkType()) { case TelephonyManager.NETWORK_TYPE_GPRS: networkTypeStr = "gprs"; break; case TelephonyManager.NETWORK_TYPE_EDGE: networkTypeStr = "edge"; break; case TelephonyManager.NETWORK_TYPE_UMTS: networkTypeStr = "umts"; break; } return "net.tcp.buffersize." + networkTypeStr; } /** * Tear down mobile data connectivity, i.e., disable the ability to create * mobile data connections. */ @Override public boolean teardown() { getPhoneService(false); /* * If the phone process has crashed in the past, we'll get a * RemoteException and need to re-reference the service. */ for (int retry = 0; retry < 2; retry++) { if (mPhoneService == null) { Log.w(TAG, "Ignoring mobile data teardown request because could not acquire PhoneService"); break; } try { return mPhoneService.disableDataConnectivity(); } catch (RemoteException e) { if (retry == 0) getPhoneService(true); } } Log.w(TAG, "Failed to tear down mobile data connectivity"); return false; } /** * Re-enable mobile data connectivity after a {@link #teardown()}. */ public boolean reconnect() { getPhoneService(false); /* * If the phone process has crashed in the past, we'll get a * RemoteException and need to re-reference the service. */ for (int retry = 0; retry < 2; retry++) { if (mPhoneService == null) { Log.w(TAG, "Ignoring mobile data connect request because could not acquire PhoneService"); break; } try { return mPhoneService.enableDataConnectivity(); } catch (RemoteException e) { if (retry == 0) getPhoneService(true); } } Log.w(TAG, "Failed to set up mobile data connectivity"); return false; } /** * Turn on or off the mobile radio. No connectivity will be possible while the * radio is off. The operation is a no-op if the radio is already in the desired state. * @param turnOn {@code true} if the radio should be turned on, {@code false} if */ public boolean setRadio(boolean turnOn) { getPhoneService(false); /* * If the phone process has crashed in the past, we'll get a * RemoteException and need to re-reference the service. */ for (int retry = 0; retry < 2; retry++) { if (mPhoneService == null) { Log.w(TAG, "Ignoring mobile radio request because could not acquire PhoneService"); break; } try { return mPhoneService.setRadio(turnOn); } catch (RemoteException e) { if (retry == 0) getPhoneService(true); } } Log.w(TAG, "Could not set radio power to " + (turnOn ? "on" : "off")); return false; } /** * Tells the phone sub-system that the caller wants to * begin using the named feature. The only supported feature at * this time is {@code Phone.FEATURE_ENABLE_MMS}, which allows an application * to specify that it wants to send and/or receive MMS data. * @param feature the name of the feature to be used * @param callingPid the process ID of the process that is issuing this request * @param callingUid the user ID of the process that is issuing this request * @return an integer value representing the outcome of the request. * The interpretation of this value is feature-specific. * specific, except that the value {@code -1} * always indicates failure. For {@code Phone.FEATURE_ENABLE_MMS}, * the other possible return values are * <ul> * <li>{@code Phone.APN_ALREADY_ACTIVE}</li> * <li>{@code Phone.APN_REQUEST_STARTED}</li> * <li>{@code Phone.APN_TYPE_NOT_AVAILABLE}</li> * <li>{@code Phone.APN_REQUEST_FAILED}</li> * </ul> */ public int startUsingNetworkFeature(String feature, int callingPid, int callingUid) { if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_MMS)) { mLastCallingPid = callingPid; return setEnableApn(Phone.APN_TYPE_MMS, true); } else { return -1; } } /** * Tells the phone sub-system that the caller is finished * using the named feature. The only supported feature at * this time is {@code Phone.FEATURE_ENABLE_MMS}, which allows an application * to specify that it wants to send and/or receive MMS data. * @param feature the name of the feature that is no longer needed * @param callingPid the process ID of the process that is issuing this request * @param callingUid the user ID of the process that is issuing this request * @return an integer value representing the outcome of the request. * The interpretation of this value is feature-specific, except that * the value {@code -1} always indicates failure. */ public int stopUsingNetworkFeature(String feature, int callingPid, int callingUid) { if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_MMS)) { return setEnableApn(Phone.APN_TYPE_MMS, false); } else { return -1; } } /** * Ensure that a network route exists to deliver traffic to the specified * host via the mobile data network. * @param hostAddress the IP address of the host to which the route is desired, * in network byte order. * @return {@code true} on success, {@code false} on failure */ @Override public boolean requestRouteToHost(int hostAddress) { if (mInterfaceName != null && hostAddress != -1) { if (DBG) { Log.d(TAG, "Requested host route to " + Integer.toHexString(hostAddress)); } return NetworkUtils.addHostRoute(mInterfaceName, hostAddress) == 0; } else { return false; } } @Override public String toString() { StringBuffer sb = new StringBuffer("Mobile data state: "); sb.append(mMobileDataState); return sb.toString(); } private void setupDnsProperties() { mDnsServers.clear(); // Set up per-process DNS server list on behalf of the MMS process int i = 1; if (mInterfaceName != null) { for (String propName : sDnsPropNames) { if (propName.indexOf(mInterfaceName) != -1) { String propVal = SystemProperties.get(propName); if (propVal != null && propVal.length() != 0 && !propVal.equals("0.0.0.0")) { mDnsServers.add(propVal); if (mLastCallingPid != -1) { SystemProperties.set("net.dns" + i + "." + mLastCallingPid, propVal); } ++i; } } } } if (i == 1) { Log.d(TAG, "DNS server addresses are not known."); } else if (mLastCallingPid != -1) { /* * Bump the property that tells the name resolver library * to reread the DNS server list from the properties. */ String propVal = SystemProperties.get("net.dnschange"); if (propVal.length() != 0) { try { int n = Integer.parseInt(propVal); SystemProperties.set("net.dnschange", "" + (n+1)); } catch (NumberFormatException e) { } } } mLastCallingPid = -1; } /** * Internal method supporting the ENABLE_MMS feature. * @param apnType the type of APN to be enabled or disabled (e.g., mms) * @param enable {@code true} to enable the specified APN type, * {@code false} to disable it. * @return an integer value representing the outcome of the request. */ private int setEnableApn(String apnType, boolean enable) { getPhoneService(false); /* * If the phone process has crashed in the past, we'll get a * RemoteException and need to re-reference the service. */ for (int retry = 0; retry < 2; retry++) { if (mPhoneService == null) { Log.w(TAG, "Ignoring feature request because could not acquire PhoneService"); break; } try { if (enable) { return mPhoneService.enableApnType(apnType); } else { return mPhoneService.disableApnType(apnType); } } catch (RemoteException e) { if (retry == 0) getPhoneService(true); } } Log.w(TAG, "Could not " + (enable ? "enable" : "disable") + " APN type \"" + apnType + "\""); return Phone.APN_REQUEST_FAILED; } }