From 2eb29a4ef68981317e057f32b955ea8e88c7d2bb Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Queru Date: Thu, 12 Nov 2009 18:45:53 -0800 Subject: [PATCH] eclair snapshot --- .../java/android/net/ConnectivityManager.java | 59 +- .../android/net/IConnectivityManager.aidl | 4 +- core/java/android/net/NetworkInfo.java | 2 +- core/java/android/net/NetworkUtils.java | 3 + core/jni/android_net_NetUtils.cpp | 12 + .../android/server/ConnectivityService.java | 1077 +++++++++++++---- 6 files changed, 883 insertions(+), 274 deletions(-) diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 1429bc1aff..a127df01b8 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -18,6 +18,7 @@ package android.net; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.os.Binder; import android.os.RemoteException; /** @@ -114,15 +115,64 @@ public class ConnectivityManager public static final String ACTION_BACKGROUND_DATA_SETTING_CHANGED = "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED"; - public static final int TYPE_MOBILE = 0; - public static final int TYPE_WIFI = 1; + /** + * The Default Mobile data connection. When active, all data traffic + * will use this connection by default. Should not coexist with other + * default connections. + */ + public static final int TYPE_MOBILE = 0; + /** + * The Default WIFI data connection. When active, all data traffic + * will use this connection by default. Should not coexist with other + * default connections. + */ + public static final int TYPE_WIFI = 1; + /** + * An MMS-specific Mobile data connection. This connection may be the + * same as {@link #TYPEMOBILE} but it may be different. This is used + * by applications needing to talk to the carrier's Multimedia Messaging + * Service servers. It may coexist with default data connections. + * {@hide} + */ + public static final int TYPE_MOBILE_MMS = 2; + /** + * A SUPL-specific Mobile data connection. This connection may be the + * same as {@link #TYPEMOBILE} but it may be different. This is used + * by applications needing to talk to the carrier's Secure User Plane + * Location servers for help locating the device. It may coexist with + * default data connections. + * {@hide} + */ + public static final int TYPE_MOBILE_SUPL = 3; + /** + * A DUN-specific Mobile data connection. This connection may be the + * same as {@link #TYPEMOBILE} but it may be different. This is used + * by applicaitons performing a Dial Up Networking bridge so that + * the carrier is aware of DUN traffic. It may coexist with default data + * connections. + * {@hide} + */ + public static final int TYPE_MOBILE_DUN = 4; + /** + * A High Priority Mobile data connection. This connection is typically + * the same as {@link #TYPEMOBILE} but the routing setup is different. + * Only requesting processes will have access to the Mobile DNS servers + * and only IP's explicitly requested via {@link #requestRouteToHost} + * will route over this interface. + *{@hide} + */ + public static final int TYPE_MOBILE_HIPRI = 5; + /** {@hide} */ + public static final int MAX_RADIO_TYPE = TYPE_WIFI; + /** {@hide} */ + public static final int MAX_NETWORK_TYPE = TYPE_MOBILE_HIPRI; 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; + return networkType >= 0 && networkType <= MAX_NETWORK_TYPE; } public void setNetworkPreference(int preference) { @@ -195,7 +245,8 @@ public class ConnectivityManager */ public int startUsingNetworkFeature(int networkType, String feature) { try { - return mService.startUsingNetworkFeature(networkType, feature); + return mService.startUsingNetworkFeature(networkType, feature, + new Binder()); } catch (RemoteException e) { return -1; } diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index de6859809d..9f59ccede4 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -17,6 +17,7 @@ package android.net; import android.net.NetworkInfo; +import android.os.IBinder; /** * Interface that answers queries about, and allows changing, the @@ -39,7 +40,8 @@ interface IConnectivityManager boolean setRadio(int networkType, boolean turnOn); - int startUsingNetworkFeature(int networkType, in String feature); + int startUsingNetworkFeature(int networkType, in String feature, + in IBinder binder); int stopUsingNetworkFeature(int networkType, in String feature); diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java index 9f539373b6..649cb8cfc8 100644 --- a/core/java/android/net/NetworkInfo.java +++ b/core/java/android/net/NetworkInfo.java @@ -131,7 +131,7 @@ public class NetworkInfo implements Parcelable { mSubtypeName = subtypeName; setDetailedState(DetailedState.IDLE, null, null); mState = State.UNKNOWN; - mIsAvailable = true; + mIsAvailable = false; // until we're told otherwise, assume unavailable mIsRoaming = false; } diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java index 1153648729..a3ae01b559 100644 --- a/core/java/android/net/NetworkUtils.java +++ b/core/java/android/net/NetworkUtils.java @@ -25,6 +25,9 @@ import java.net.UnknownHostException; * {@hide} */ public class NetworkUtils { + /** Bring the named network interface up. */ + public native static int enableInterface(String interfaceName); + /** Bring the named network interface down. */ public native static int disableInterface(String interfaceName); diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp index 8383deb59a..feb0dadc15 100644 --- a/core/jni/android_net_NetUtils.cpp +++ b/core/jni/android_net_NetUtils.cpp @@ -23,6 +23,7 @@ #include extern "C" { +int ifc_enable(const char *ifname); 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); @@ -66,6 +67,16 @@ static struct fieldIds { jfieldID leaseDuration; } dhcpInfoFieldIds; +static jint android_net_utils_enableInterface(JNIEnv* env, jobject clazz, jstring ifname) +{ + int result; + + const char *nameStr = env->GetStringUTFChars(ifname, NULL); + result = ::ifc_enable(nameStr); + env->ReleaseStringUTFChars(ifname, nameStr); + return (jint)result; +} + static jint android_net_utils_disableInterface(JNIEnv* env, jobject clazz, jstring ifname) { int result; @@ -209,6 +220,7 @@ static jboolean android_net_utils_configureInterface(JNIEnv* env, static JNINativeMethod gNetworkUtilMethods[] = { /* name, signature, funcPtr */ + { "enableInterface", "(Ljava/lang/String;)I", (void *)android_net_utils_enableInterface }, { "disableInterface", "(Ljava/lang/String;)I", (void *)android_net_utils_disableInterface }, { "addHostRoute", "(Ljava/lang/String;I)I", (void *)android_net_utils_addHostRoute }, { "removeHostRoutes", "(Ljava/lang/String;)I", (void *)android_net_utils_removeHostRoutes }, diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index 493bd09906..78215b0af0 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -30,51 +30,136 @@ import android.net.NetworkStateTracker; import android.net.wifi.WifiStateTracker; import android.os.Binder; import android.os.Handler; +import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; import android.provider.Settings; +import android.text.TextUtils; import android.util.EventLog; import android.util.Log; +import com.android.internal.telephony.Phone; + import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; /** * @hide */ public class ConnectivityService extends IConnectivityManager.Stub { - private static final boolean DBG = false; + private static final boolean DBG = true; 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; + // how long to wait before switching back to a radio's default network + private static final int RESTORE_DEFAULT_NETWORK_DELAY = 1 * 60 * 1000; + // system property that can override the above value + private static final String NETWORK_RESTORE_DELAY_PROP_NAME = + "android.telephony.apn-restore"; + /** * 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 WifiStateTracker mWifiStateTracker; - private MobileDataStateTracker mMobileDataStateTracker; + + /** + * A per Net list of the PID's that requested access to the net + * used both as a refcount and for per-PID DNS selection + */ + private List mNetRequestersPids[]; + private WifiWatchdogService mWifiWatchdogService; + // priority order of the nettrackers + // (excluding dynamically set mNetworkPreference) + // TODO - move mNetworkTypePreference into this + private int[] mPriorityList; + private Context mContext; private int mNetworkPreference; - private NetworkStateTracker mActiveNetwork; + private int mActiveDefaultNetwork = -1; private int mNumDnsEntries; - private static int sDnsChangeCounter; private boolean mTestMode; private static ConnectivityService sServiceInstance; + private Handler mHandler; + + // list of DeathRecipients used to make sure features are turned off when + // a process dies + private List mFeatureUsers; + + private boolean mSystemReady; + private ArrayList mDeferredBroadcasts; + + private class NetworkAttributes { + /** + * Class for holding settings read from resources. + */ + public String mName; + public int mType; + public int mRadio; + public int mPriority; + public NetworkAttributes(String init) { + String fragments[] = init.split(","); + mName = fragments[0].toLowerCase(); + if (fragments[1].toLowerCase().equals("wifi")) { + mRadio = ConnectivityManager.TYPE_WIFI; + } else { + mRadio = ConnectivityManager.TYPE_MOBILE; + } + if (mName.equals("default")) { + mType = mRadio; + } else if (mName.equals("mms")) { + mType = ConnectivityManager.TYPE_MOBILE_MMS; + } else if (mName.equals("supl")) { + mType = ConnectivityManager.TYPE_MOBILE_SUPL; + } else if (mName.equals("dun")) { + mType = ConnectivityManager.TYPE_MOBILE_DUN; + } else if (mName.equals("hipri")) { + mType = ConnectivityManager.TYPE_MOBILE_HIPRI; + } + mPriority = Integer.parseInt(fragments[2]); + } + public boolean isDefault() { + return (mType == mRadio); + } + } + NetworkAttributes[] mNetAttributes; + + private class RadioAttributes { + public String mName; + public int mPriority; + public int mSimultaneity; + public int mType; + public RadioAttributes(String init) { + String fragments[] = init.split(","); + mName = fragments[0].toLowerCase(); + mPriority = Integer.parseInt(fragments[1]); + mSimultaneity = Integer.parseInt(fragments[2]); + if (mName.equals("wifi")) { + mType = ConnectivityManager.TYPE_WIFI; + } else { + mType = ConnectivityManager.TYPE_MOBILE; + } + } + } + RadioAttributes[] mRadioAttributes; + private static class ConnectivityThread extends Thread { private Context mContext; - + private ConnectivityThread(Context context) { super("ConnectivityThread"); mContext = context; @@ -89,11 +174,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { } Looper.loop(); } - + public static ConnectivityService getServiceInstance(Context context) { ConnectivityThread thread = new ConnectivityThread(context); thread.start(); - + synchronized (thread) { while (sServiceInstance == null) { try { @@ -101,27 +186,71 @@ public class ConnectivityService extends IConnectivityManager.Stub { thread.wait(); } catch (InterruptedException ignore) { Log.e(TAG, - "Unexpected InterruptedException while waiting for ConnectivityService thread"); + "Unexpected InterruptedException while waiting"+ + " for ConnectivityService thread"); } } } - + 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]; - Handler handler = new MyHandler(); - + mNetTrackers = new NetworkStateTracker[ + ConnectivityManager.MAX_NETWORK_TYPE+1]; + mHandler = new MyHandler(); + mNetworkPreference = getPersistedNetworkPreference(); - + + // Load device network attributes from resources + mNetAttributes = new NetworkAttributes[ + ConnectivityManager.MAX_NETWORK_TYPE+1]; + mRadioAttributes = new RadioAttributes[ + ConnectivityManager.MAX_RADIO_TYPE+1]; + String[] naStrings = context.getResources().getStringArray( + com.android.internal.R.array.networkAttributes); + // TODO - what if the setting has gaps/unknown types? + for (String a : naStrings) { + NetworkAttributes n = new NetworkAttributes(a); + mNetAttributes[n.mType] = n; + } + String[] raStrings = context.getResources().getStringArray( + com.android.internal.R.array.radioAttributes); + for (String a : raStrings) { + RadioAttributes r = new RadioAttributes(a); + mRadioAttributes[r.mType] = r; + } + + // high priority first + mPriorityList = new int[naStrings.length]; + { + int priority = 0; //lowest + int nextPos = naStrings.length-1; + while (nextPos>-1) { + for (int i = 0; i < mNetAttributes.length; i++) { + if(mNetAttributes[i].mPriority == priority) { + mPriorityList[nextPos--] = i; + } + } + priority++; + } + } + + mNetRequestersPids = + new ArrayList[ConnectivityManager.MAX_NETWORK_TYPE+1]; + for (int i=0; i<=ConnectivityManager.MAX_NETWORK_TYPE; i++) { + mNetRequestersPids[i] = new ArrayList(); + } + + mFeatureUsers = new ArrayList(); + /* * Create the network state trackers for Wi-Fi and mobile * data. Maybe this could be done with a factory class, @@ -130,15 +259,36 @@ public class ConnectivityService extends IConnectivityManager.Stub { * to change very often. */ if (DBG) Log.v(TAG, "Starting Wifi Service."); - mWifiStateTracker = new WifiStateTracker(context, handler); - WifiService wifiService = new WifiService(context, mWifiStateTracker); + WifiStateTracker wst = new WifiStateTracker(context, mHandler); + WifiService wifiService = new WifiService(context, wst); ServiceManager.addService(Context.WIFI_SERVICE, wifiService); - mNetTrackers[ConnectivityManager.TYPE_WIFI] = mWifiStateTracker; + mNetTrackers[ConnectivityManager.TYPE_WIFI] = wst; + + mNetTrackers[ConnectivityManager.TYPE_MOBILE] = + new MobileDataStateTracker(context, mHandler, + ConnectivityManager.TYPE_MOBILE, Phone.APN_TYPE_DEFAULT, + "MOBILE"); + + mNetTrackers[ConnectivityManager.TYPE_MOBILE_MMS] = + new MobileDataStateTracker(context, mHandler, + ConnectivityManager.TYPE_MOBILE_MMS, Phone.APN_TYPE_MMS, + "MOBILE_MMS"); + + mNetTrackers[ConnectivityManager.TYPE_MOBILE_SUPL] = + new MobileDataStateTracker(context, mHandler, + ConnectivityManager.TYPE_MOBILE_SUPL, Phone.APN_TYPE_SUPL, + "MOBILE_SUPL"); + + mNetTrackers[ConnectivityManager.TYPE_MOBILE_DUN] = + new MobileDataStateTracker(context, mHandler, + ConnectivityManager.TYPE_MOBILE_DUN, Phone.APN_TYPE_DUN, + "MOBILE_DUN"); + + mNetTrackers[ConnectivityManager.TYPE_MOBILE_HIPRI] = + new MobileDataStateTracker(context, mHandler, + ConnectivityManager.TYPE_MOBILE_HIPRI, Phone.APN_TYPE_HIPRI, + "MOBILE_HIPRI"); - mMobileDataStateTracker = new MobileDataStateTracker(context, handler); - mNetTrackers[ConnectivityManager.TYPE_MOBILE] = mMobileDataStateTracker; - - mActiveNetwork = null; mNumDnsEntries = 0; mTestMode = SystemProperties.get("cm.test.mode").equals("true") @@ -148,16 +298,17 @@ public class ConnectivityService extends IConnectivityManager.Stub { t.startMonitoring(); // Constructing this starts it too - mWifiWatchdogService = new WifiWatchdogService(context, mWifiStateTracker); + mWifiWatchdogService = new WifiWatchdogService(context, wst); } /** - * Sets the preferred network. + * Sets the preferred network. * @param preference the new preference */ public synchronized void setNetworkPreference(int preference) { enforceChangePermission(); - if (ConnectivityManager.isNetworkTypeValid(preference)) { + if (ConnectivityManager.isNetworkTypeValid(preference) && + mNetAttributes[preference].isDefault()) { if (mNetworkPreference != preference) { persistNetworkPreference(preference); mNetworkPreference = preference; @@ -173,9 +324,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { private void persistNetworkPreference(int networkPreference) { final ContentResolver cr = mContext.getContentResolver(); - Settings.Secure.putInt(cr, Settings.Secure.NETWORK_PREFERENCE, networkPreference); + Settings.Secure.putInt(cr, Settings.Secure.NETWORK_PREFERENCE, + networkPreference); } - + private int getPersistedNetworkPreference() { final ContentResolver cr = mContext.getContentResolver(); @@ -187,31 +339,30 @@ public class ConnectivityService extends IConnectivityManager.Stub { return ConnectivityManager.DEFAULT_NETWORK_PREFERENCE; } - + /** - * Make the state of network connectivity conform to the preference settings. + * 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) + if (mNetTrackers[mNetworkPreference].getNetworkInfo().isConnected()) 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 (!mNetTrackers[mNetworkPreference].isAvailable()) + return; - if (t.getNetworkInfo().getType() != mNetworkPreference) { - NetworkStateTracker otherTracker = mNetTrackers[otherNetType]; - if (otherTracker.isAvailable()) { - teardown(t); - } + for (int t=0; t <= ConnectivityManager.MAX_RADIO_TYPE; t++) { + if (t != mNetworkPreference && + mNetTrackers[t].getNetworkInfo().isConnected()) { + if (DBG) { + Log.d(TAG, "tearing down " + + mNetTrackers[t].getNetworkInfo() + + " in enforcePreference"); } + teardown(mNetTrackers[t]); } } } @@ -229,13 +380,21 @@ public class ConnectivityService extends IConnectivityManager.Stub { * Return NetworkInfo for the active (i.e., connected) network interface. * It is assumed that at most one network is active at a time. If more * than one is active, it is indeterminate which will be returned. - * @return the info for the active network, or {@code null} if none is active + * @return the info for the active network, or {@code null} if none is + * active */ public NetworkInfo getActiveNetworkInfo() { enforceAccessPermission(); - for (NetworkStateTracker t : mNetTrackers) { + for (int type=0; type <= ConnectivityManager.MAX_NETWORK_TYPE; type++) { + if (!mNetAttributes[type].isDefault()) { + continue; + } + NetworkStateTracker t = mNetTrackers[type]; NetworkInfo info = t.getNetworkInfo(); if (info.isConnected()) { + if (DBG && type != mActiveDefaultNetwork) Log.e(TAG, + "connected default network is not " + + "mActiveDefaultNetwork!"); return info; } } @@ -280,36 +439,253 @@ public class ConnectivityService extends IConnectivityManager.Stub { return tracker != null && tracker.setRadio(turnOn); } - public int startUsingNetworkFeature(int networkType, String feature) { - enforceChangePermission(); - if (!ConnectivityManager.isNetworkTypeValid(networkType)) { - return -1; + /** + * Used to notice when the calling process dies so we can self-expire + * + * Also used to know if the process has cleaned up after itself when + * our auto-expire timer goes off. The timer has a link to an object. + * + */ + private class FeatureUser implements IBinder.DeathRecipient { + int mNetworkType; + String mFeature; + IBinder mBinder; + int mPid; + int mUid; + + FeatureUser(int type, String feature, IBinder binder) { + super(); + mNetworkType = type; + mFeature = feature; + mBinder = binder; + mPid = getCallingPid(); + mUid = getCallingUid(); + + try { + mBinder.linkToDeath(this, 0); + } catch (RemoteException e) { + binderDied(); + } } - NetworkStateTracker tracker = mNetTrackers[networkType]; - if (tracker != null) { - return tracker.startUsingNetworkFeature(feature, getCallingPid(), getCallingUid()); + + void unlinkDeathRecipient() { + mBinder.unlinkToDeath(this, 0); + } + + public void binderDied() { + Log.d(TAG, "ConnectivityService FeatureUser binderDied(" + + mNetworkType + ", " + mFeature + ", " + mBinder); + stopUsingNetworkFeature(this, false); + } + + public void expire() { + Log.d(TAG, "ConnectivityService FeatureUser expire(" + + mNetworkType + ", " + mFeature + ", " + mBinder); + stopUsingNetworkFeature(this, false); } - return -1; } + // javadoc from interface + public int startUsingNetworkFeature(int networkType, String feature, + IBinder binder) { + if (DBG) { + Log.d(TAG, "startUsingNetworkFeature for net " + networkType + + ": " + feature); + } + enforceChangePermission(); + if (!ConnectivityManager.isNetworkTypeValid(networkType)) { + return Phone.APN_REQUEST_FAILED; + } + + FeatureUser f = new FeatureUser(networkType, feature, binder); + + // TODO - move this into the MobileDataStateTracker + int usedNetworkType = networkType; + if(networkType == ConnectivityManager.TYPE_MOBILE) { + if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_MMS)) { + usedNetworkType = ConnectivityManager.TYPE_MOBILE_MMS; + } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_SUPL)) { + usedNetworkType = ConnectivityManager.TYPE_MOBILE_SUPL; + } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_DUN)) { + usedNetworkType = ConnectivityManager.TYPE_MOBILE_DUN; + } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_HIPRI)) { + usedNetworkType = ConnectivityManager.TYPE_MOBILE_HIPRI; + } + } + NetworkStateTracker network = mNetTrackers[usedNetworkType]; + if (network != null) { + if (usedNetworkType != networkType) { + Integer currentPid = new Integer(getCallingPid()); + + NetworkStateTracker radio = mNetTrackers[networkType]; + NetworkInfo ni = network.getNetworkInfo(); + + if (ni.isAvailable() == false) { + if (DBG) Log.d(TAG, "special network not available"); + return Phone.APN_TYPE_NOT_AVAILABLE; + } + + synchronized(this) { + mFeatureUsers.add(f); + if (!mNetRequestersPids[usedNetworkType].contains(currentPid)) { + // this gets used for per-pid dns when connected + mNetRequestersPids[usedNetworkType].add(currentPid); + } + } + mHandler.sendMessageDelayed(mHandler.obtainMessage( + NetworkStateTracker.EVENT_RESTORE_DEFAULT_NETWORK, + f), getRestoreDefaultNetworkDelay()); + + + if ((ni.isConnectedOrConnecting() == true) && + !network.isTeardownRequested()) { + if (ni.isConnected() == true) { + // add the pid-specific dns + handleDnsConfigurationChange(); + if (DBG) Log.d(TAG, "special network already active"); + return Phone.APN_ALREADY_ACTIVE; + } + if (DBG) Log.d(TAG, "special network already connecting"); + return Phone.APN_REQUEST_STARTED; + } + + // check if the radio in play can make another contact + // assume if cannot for now + + if (DBG) Log.d(TAG, "reconnecting to special network"); + network.reconnect(); + return Phone.APN_REQUEST_STARTED; + } else { + synchronized(this) { + mFeatureUsers.add(f); + } + mHandler.sendMessageDelayed(mHandler.obtainMessage( + NetworkStateTracker.EVENT_RESTORE_DEFAULT_NETWORK, + f), getRestoreDefaultNetworkDelay()); + + return network.startUsingNetworkFeature(feature, + getCallingPid(), getCallingUid()); + } + } + return Phone.APN_TYPE_NOT_AVAILABLE; + } + + // javadoc from interface public int stopUsingNetworkFeature(int networkType, String feature) { + int pid = getCallingPid(); + int uid = getCallingUid(); + + FeatureUser u = null; + boolean found = false; + + synchronized(this) { + for (int i = 0; i < mFeatureUsers.size() ; i++) { + u = (FeatureUser)mFeatureUsers.get(i); + if (uid == u.mUid && pid == u.mPid && + networkType == u.mNetworkType && + TextUtils.equals(feature, u.mFeature)) { + found = true; + break; + } + } + } + if (found && u != null) { + // stop regardless of how many other time this proc had called start + return stopUsingNetworkFeature(u, true); + } else { + // none found! + return 1; + } + } + + private int stopUsingNetworkFeature(FeatureUser u, boolean ignoreDups) { + int networkType = u.mNetworkType; + String feature = u.mFeature; + int pid = u.mPid; + int uid = u.mUid; + + NetworkStateTracker tracker = null; + boolean callTeardown = false; // used to carry our decision outside of sync block + + if (DBG) { + Log.d(TAG, "stopUsingNetworkFeature for net " + networkType + + ": " + feature); + } enforceChangePermission(); if (!ConnectivityManager.isNetworkTypeValid(networkType)) { return -1; } - NetworkStateTracker tracker = mNetTrackers[networkType]; - if (tracker != null) { - return tracker.stopUsingNetworkFeature(feature, getCallingPid(), getCallingUid()); + + // need to link the mFeatureUsers list with the mNetRequestersPids state in this + // sync block + synchronized(this) { + // check if this process still has an outstanding start request + if (!mFeatureUsers.contains(u)) { + return 1; + } + u.unlinkDeathRecipient(); + mFeatureUsers.remove(mFeatureUsers.indexOf(u)); + // If we care about duplicate requests, check for that here. + // + // This is done to support the extension of a request - the app + // can request we start the network feature again and renew the + // auto-shutoff delay. Normal "stop" calls from the app though + // do not pay attention to duplicate requests - in effect the + // API does not refcount and a single stop will counter multiple starts. + if (ignoreDups == false) { + for (int i = 0; i < mFeatureUsers.size() ; i++) { + FeatureUser x = (FeatureUser)mFeatureUsers.get(i); + if (x.mUid == u.mUid && x.mPid == u.mPid && + x.mNetworkType == u.mNetworkType && + TextUtils.equals(x.mFeature, u.mFeature)) { + return 1; + } + } + } + + // TODO - move to MobileDataStateTracker + int usedNetworkType = networkType; + if (networkType == ConnectivityManager.TYPE_MOBILE) { + if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_MMS)) { + usedNetworkType = ConnectivityManager.TYPE_MOBILE_MMS; + } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_SUPL)) { + usedNetworkType = ConnectivityManager.TYPE_MOBILE_SUPL; + } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_DUN)) { + usedNetworkType = ConnectivityManager.TYPE_MOBILE_DUN; + } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_HIPRI)) { + usedNetworkType = ConnectivityManager.TYPE_MOBILE_HIPRI; + } + } + tracker = mNetTrackers[usedNetworkType]; + if(usedNetworkType != networkType) { + Integer currentPid = new Integer(pid); + reassessPidDns(pid, true); + mNetRequestersPids[usedNetworkType].remove(currentPid); + if (mNetRequestersPids[usedNetworkType].size() != 0) { + if (DBG) Log.d(TAG, "not tearing down special network - " + + "others still using it"); + return 1; + } + callTeardown = true; + } + } + + if (callTeardown) { + tracker.teardown(); + return 1; + } else { + // do it the old fashioned way + return tracker.stopUsingNetworkFeature(feature, pid, uid); } - 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 + * @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) { @@ -318,19 +694,14 @@ public class ConnectivityService extends IConnectivityManager.Stub { 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; + + if (!tracker.getNetworkInfo().isConnected() || tracker.isTeardownRequested()) { + if (DBG) { + Log.d(TAG, "requestRouteToHost on down network (" + networkType + " - dropped"); + } + return false; } + return tracker.requestRouteToHost(hostAddress); } /** @@ -340,7 +711,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { return Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.BACKGROUND_DATA, 1) == 1; } - + /** * @see ConnectivityManager#setBackgroundDataSetting(boolean) */ @@ -348,22 +719,24 @@ public class ConnectivityService extends IConnectivityManager.Stub { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_BACKGROUND_DATA_SETTING, "ConnectivityService"); - + if (getBackgroundDataSetting() == allowBackgroundDataUsage) return; Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Secure.BACKGROUND_DATA, allowBackgroundDataUsage ? 1 : 0); - + Settings.Secure.BACKGROUND_DATA, + allowBackgroundDataUsage ? 1 : 0); + Intent broadcast = new Intent( ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED); mContext.sendBroadcast(broadcast); - } - + } + private int getNumConnectedNetworks() { int numConnectedNets = 0; for (NetworkStateTracker nt : mNetTrackers) { - if (nt.getNetworkInfo().isConnected() && !nt.isTeardownRequested()) { + if (nt.getNetworkInfo().isConnected() && + !nt.isTeardownRequested()) { ++numConnectedNets; } } @@ -371,64 +744,46 @@ public class ConnectivityService extends IConnectivityManager.Stub { } private void enforceAccessPermission() { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_NETWORK_STATE, - "ConnectivityService"); + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.ACCESS_NETWORK_STATE, + "ConnectivityService"); } private void enforceChangePermission() { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_NETWORK_STATE, - "ConnectivityService"); - + 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. + * 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()); + int prevNetType = info.getType(); - mNetTrackers[info.getType()].setTeardownRequested(false); + mNetTrackers[prevNetType].setTeardownRequested(false); /* * If the disconnected network is not the active one, then don't report * this as a loss of connectivity. What probably happened is that we're * getting the disconnect for a network that we explicitly disabled * in accordance with network preference policies. */ - if (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; - } - - /** - * See if the other network is available to fail over to. - * If is not available, we enable it anyway, so that it - * will be able to connect when it does become available, - * but we report a total loss of connectivity rather than - * report that we are attempting to fail over. - */ - NetworkInfo switchTo = null; - if (newNet.isAvailable()) { - mActiveNetwork = newNet; - switchTo = newNet.getNetworkInfo(); - switchTo.setFailover(true); - if (!switchTo.isConnectedOrConnecting()) { - newNet.reconnect(); + if (!mNetAttributes[prevNetType].isDefault()) { + List pids = mNetRequestersPids[prevNetType]; + for (int i = 0; i newPriority) { + newType = checkType; + newPriority = mRadioAttributes[mNetAttributes[newType]. + mRadio].mPriority; + } } } - 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 (newType != -1) { + newNet = mNetTrackers[newType]; + /** + * See if the other network is available to fail over to. + * If is not available, we enable it anyway, so that it + * will be able to connect when it does become available, + * but we report a total loss of connectivity rather than + * report that we are attempting to fail over. + */ + if (newNet.isAvailable()) { + NetworkInfo switchTo = newNet.getNetworkInfo(); + switchTo.setFailover(true); + if (!switchTo.isConnectedOrConnecting() || + newNet.isTeardownRequested()) { + newNet.reconnect(); + } + if (DBG) { + if (switchTo.isConnected()) { + 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); + newNet.reconnect(); + } + } else { + intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, + true); + } + } + + // do this before we broadcast the change + handleConnectivityChange(); + + sendStickyBroadcast(intent); /* - * If the failover network is already connected, then immediately send out - * a followup broadcast indicating successful failover + * If the failover network is already connected, then immediately send + * out a followup broadcast indicating successful failover */ - if (switchTo != null && otherNetworkConnected) - sendConnectedBroadcast(switchTo); + if (newNet != null && newNet.getNetworkInfo().isConnected()) + sendConnectedBroadcast(newNet.getNetworkInfo()); } private void sendConnectedBroadcast(NetworkInfo info) { @@ -477,9 +893,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { intent.putExtra(ConnectivityManager.EXTRA_REASON, info.getReason()); } if (info.getExtraInfo() != null) { - intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, info.getExtraInfo()); + intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, + info.getExtraInfo()); } - mContext.sendStickyBroadcast(intent); + sendStickyBroadcast(intent); } /** @@ -488,106 +905,124 @@ public class ConnectivityService extends IConnectivityManager.Stub { */ private void handleConnectionFailure(NetworkInfo info) { mNetTrackers[info.getType()].setTeardownRequested(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); + String reason = info.getReason(); + String extraInfo = info.getExtraInfo(); + + if (DBG) { + String reasonText; + if (reason == null) { + reasonText = "."; + } else { + reasonText = " (" + reason + ")."; } - - Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION); - intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info); + 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); + if (getActiveNetworkInfo() == null) { intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true); - if (reason != null) { - intent.putExtra(ConnectivityManager.EXTRA_REASON, reason); + } + 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); + } + sendStickyBroadcast(intent); + } + + private void sendStickyBroadcast(Intent intent) { + synchronized(this) { + if (mSystemReady) { + mContext.sendStickyBroadcast(intent); + } else { + if (mDeferredBroadcasts == null) { + mDeferredBroadcasts = new ArrayList(); + } + mDeferredBroadcasts.add(intent); } - if (extraInfo != null) { - intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, extraInfo); + } + } + + void systemReady() { + synchronized(this) { + mSystemReady = true; + if (mDeferredBroadcasts != null) { + int count = mDeferredBroadcasts.size(); + for (int i = 0; i < count; i++) { + mContext.sendStickyBroadcast(mDeferredBroadcasts.get(i)); + } + mDeferredBroadcasts = null; } - 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()); + int type = info.getType(); // 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 connected to a non-preferred - * network that now needs to be torn down. - */ - NetworkInfo wifiInfo = mWifiStateTracker.getNetworkInfo(); - NetworkInfo mobileInfo = mMobileDataStateTracker.getNetworkInfo(); - if (wifiInfo.isConnected() && mobileInfo.isConnected()) { - if (mNetworkPreference == ConnectivityManager.TYPE_WIFI) - deadnet = mMobileDataStateTracker; - else - deadnet = mWifiStateTracker; - } + NetworkStateTracker thisNet = mNetTrackers[type]; - boolean toredown = false; + // if this is a default net and other default is running + // kill the one not preferred + if (mNetAttributes[type].isDefault()) { + if (mActiveDefaultNetwork != -1 && mActiveDefaultNetwork != type) { + if ((type != mNetworkPreference && + mNetAttributes[mActiveDefaultNetwork].mPriority > + mNetAttributes[type].mPriority) || + mNetworkPreference == mActiveDefaultNetwork) { + // don't accept this one + if (DBG) Log.v(TAG, "Not broadcasting CONNECT_ACTION " + + "to torn down network " + info.getTypeName()); + teardown(thisNet); + return; + } else { + // tear down the other + NetworkStateTracker otherNet = + mNetTrackers[mActiveDefaultNetwork]; + if (DBG) Log.v(TAG, "Policy requires " + + otherNet.getNetworkInfo().getTypeName() + + " teardown"); + if (!teardown(otherNet)) { + Log.e(TAG, "Network declined teardown request"); + return; + } + if (isFailover) { + otherNet.releaseWakeLock(); + } + } + } + mActiveDefaultNetwork = type; + } thisNet.setTeardownRequested(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"); - } - } - - /* - * Note that if toredown is true, deadnet cannot be null, so there is - * no danger of a null pointer exception here.. - */ - 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()); - } + thisNet.updateNetworkSettings(); + handleConnectivityChange(); + sendConnectedBroadcast(info); } 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."); + 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) { + private void handleNotificationChange(boolean visible, int id, + Notification notification) { NotificationManager notificationManager = (NotificationManager) mContext .getSystemService(Context.NOTIFICATION_SERVICE); - + if (visible) { notificationManager.notify(id, notification); } else { @@ -605,79 +1040,173 @@ public class ConnectivityService extends IConnectivityManager.Stub { */ private void handleConnectivityChange() { /* + * If a non-default network is enabled, add the host routes that + * will allow it's DNS servers to be accessed. Only * 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(); + handleDnsConfigurationChange(); + + for (int netType : mPriorityList) { + if (mNetTrackers[netType].getNetworkInfo().isConnected()) { + if (mNetAttributes[netType].isDefault()) { + mNetTrackers[netType].addDefaultRoute(); + } else { + mNetTrackers[netType].addPrivateDnsRoutes(); + } + } else { + if (mNetAttributes[netType].isDefault()) { + mNetTrackers[netType].removeDefaultRoute(); + } else { + mNetTrackers[netType].removePrivateDnsRoutes(); + } + } } } - 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; + /** + * Adjust the per-process dns entries (net.dns.) based + * on the highest priority active net which this process requested. + * If there aren't any, clear it out + */ + private void reassessPidDns(int myPid, boolean doBump) + { + if (DBG) Log.d(TAG, "reassessPidDns for pid " + myPid); + for(int i : mPriorityList) { + if (mNetAttributes[i].isDefault()) { + continue; + } + NetworkStateTracker nt = mNetTrackers[i]; + if (nt.getNetworkInfo().isConnected() && + !nt.isTeardownRequested()) { + List pids = mNetRequestersPids[i]; + for (int j=0; j= 0; x--) { + int netType = mPriorityList[x]; NetworkStateTracker nt = mNetTrackers[netType]; - if (nt.getNetworkInfo().isConnected() && !nt.isTeardownRequested()) { - ++numConnectedNets; + if (nt != null && nt.getNetworkInfo().isConnected() && + !nt.isTeardownRequested()) { 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]; + if (mNetAttributes[netType].isDefault()) { + int j = 1; + for (String dns : dnsList) { + if (dns != null && !TextUtils.equals(dns, "0.0.0.0")) { + if (DBG) { + Log.d(TAG, "adding dns " + dns + " for " + + nt.getNetworkInfo().getTypeName()); + } + SystemProperties.set("net.dns" + j++, dns); + } + } + for (int k=j ; k