Update Tethering.

Adds telephony support, async model, multiple tethered iface suport,
better notifications, device config.

bug:2413855
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index d435df5..badb767 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -125,13 +125,21 @@
 
     /**
      * @hide
+     * gives a String[]
      */
-    public static final String EXTRA_AVAILABLE_TETHER_COUNT = "availableCount";
+    public static final String EXTRA_AVAILABLE_TETHER = "availableArray";
 
     /**
      * @hide
+     * gives a String[]
      */
-    public static final String EXTRA_ACTIVE_TETHER_COUNT = "activeCount";
+    public static final String EXTRA_ACTIVE_TETHER = "activeArray";
+
+    /**
+     * @hide
+     * gives a String[]
+     */
+    public static final String EXTRA_ERRORED_TETHER = "erroredArray";
 
     /**
      * The Default Mobile data connection.  When active, all data traffic
@@ -400,4 +408,37 @@
             return false;
         }
     }
+
+    /**
+     * {@hide}
+     */
+    public boolean isTetheringSupported() {
+        try {
+            return mService.isTetheringSupported();
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    /**
+     * {@hide}
+     */
+    public String[] getTetherableUsbRegexs() {
+        try {
+            return mService.getTetherableUsbRegexs();
+        } catch (RemoteException e) {
+            return new String[0];
+        }
+    }
+
+    /**
+     * {@hide}
+     */
+    public String[] getTetherableWifiRegexs() {
+        try {
+            return mService.getTetherableWifiRegexs();
+        } catch (RemoteException e) {
+            return new String[0];
+        }
+    }
 }
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index caa3f2b..508e9c3a 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -55,7 +55,13 @@
 
     boolean untether(String iface);
 
+    boolean isTetheringSupported();
+
     String[] getTetherableIfaces();
 
     String[] getTetheredIfaces();
+
+    String[] getTetherableUsbRegexs();
+
+    String[] getTetherableWifiRegexs();
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 7b52f7f..14e27eb 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2210,6 +2210,12 @@
         public static final String NETWORK_PREFERENCE = "network_preference";
 
         /**
+         * Used to disable Tethering on a device - defaults to true
+         * @hide
+         */
+        public static final String TETHER_SUPPORTED = "tether_supported";
+
+        /**
          * No longer supported.
          */
         public static final String PARENTAL_CONTROL_ENABLED = "parental_control_enabled";
diff --git a/core/java/com/android/internal/app/TetherActivity.java b/core/java/com/android/internal/app/TetherActivity.java
index cb268b3..a48ccf9 100644
--- a/core/java/com/android/internal/app/TetherActivity.java
+++ b/core/java/com/android/internal/app/TetherActivity.java
@@ -32,16 +32,19 @@
 import android.util.Log;
 
 /**
- * This activity is shown to the user for him/her to connect/disconnect a Tether
- * connection.  It will display notification when a suitable connection is made
- * to allow the tether to be setup.  A second notification will be show when a
- * tether is active, allowing the user to manage tethered connections.
+ * This activity is shown to the user in two cases: when a connection is possible via
+ * a usb tether and when any type of tether is connected.  In the connecting case
+ * It allows them to start a USB tether.  In the Tethered/disconnecting case it
+ * will disconnect all tethers.
  */
 public class TetherActivity extends AlertActivity implements
         DialogInterface.OnClickListener {
 
     private static final int POSITIVE_BUTTON = AlertDialog.BUTTON1;
 
+    // count of the number of tethered connections at activity create time.
+    private int mTethered;
+
     /* Used to detect when the USB cable is unplugged, so we can call finish() */
     private BroadcastReceiver mTetherReceiver = new BroadcastReceiver() {
         @Override
@@ -52,8 +55,6 @@
         }
     };
 
-    private boolean mWantTethering;
-
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -61,17 +62,18 @@
         // determine if we advertise tethering or untethering
         ConnectivityManager cm =
                 (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
-        if (cm.getTetheredIfaces().length > 0) {
-            mWantTethering = false;
-        } else if (cm.getTetherableIfaces().length > 0) {
-            mWantTethering = true;
-        } else {
+        mTethered = cm.getTetheredIfaces().length;
+        int tetherable = cm.getTetherableIfaces().length;
+        if ((mTethered == 0) && (tetherable == 0)) {
             finish();
             return;
         }
 
-        // Set up the "dialog"
-        if (mWantTethering == true) {
+        // Set up the dialog
+        // if we have a tethered connection we put up a "Do you want to Disconect" dialog
+        // otherwise we must have a tetherable interface (else we'd return above)
+        // and so we want to put up the "do you want to connect" dialog
+        if (mTethered == 0) {
             mAlertParams.mIconId = com.android.internal.R.drawable.ic_dialog_usb;
             mAlertParams.mTitle = getString(com.android.internal.R.string.tether_title);
             mAlertParams.mMessage = getString(com.android.internal.R.string.tether_message);
@@ -114,17 +116,36 @@
      * {@inheritDoc}
      */
     public void onClick(DialogInterface dialog, int which) {
+        boolean error =  false;
 
         if (which == POSITIVE_BUTTON) {
-            ConnectivityManager connManager =
+            ConnectivityManager cm =
                     (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
             // start/stop tethering
-            if (mWantTethering) {
-                if (!connManager.tether("ppp0")) {
+            String[] tethered = cm.getTetheredIfaces();
+
+            if (tethered.length == 0) {
+                String[] tetherable = cm.getTetherableIfaces();
+                String[] usbRegexs = cm.getTetherableUsbRegexs();
+                for (String t : tetherable) {
+                    for (String r : usbRegexs) {
+                        if (t.matches(r)) {
+                            if (!cm.tether(t))
+                                error = true;
+                            break;
+                        }
+                    }
+                }
+                if (error) {
                     showTetheringError();
                 }
             } else {
-                if (!connManager.untether("ppp0")) {
+                for (String t : tethered) {
+                    if (!cm.untether("ppp0")) {
+                        error = true;
+                    }
+                }
+                if (error) {
                     showUnTetheringError();
                 }
             }
@@ -134,7 +155,12 @@
     }
 
     private void handleTetherStateChanged(Intent intent) {
-        finish();
+        // determine if we advertise tethering or untethering
+        ConnectivityManager cm =
+                (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
+        if (mTethered != cm.getTetheredIfaces().length) {
+            finish();
+        }
     }
 
     private void showTetheringError() {
diff --git a/core/java/com/android/internal/util/HierarchicalStateMachine.java b/core/java/com/android/internal/util/HierarchicalStateMachine.java
index a1c5078..b4af79c 100644
--- a/core/java/com/android/internal/util/HierarchicalStateMachine.java
+++ b/core/java/com/android/internal/util/HierarchicalStateMachine.java
@@ -1021,7 +1021,7 @@
      * @param msg that couldn't be handled.
      */
     protected void unhandledMessage(Message msg) {
-        Log.e(TAG, "unhandledMessage: msg.what=" + msg.what);
+        Log.e(TAG, mName + " - unhandledMessage: msg.what=" + msg.what);
     }
 
     /**
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 40c78f7..5d561b8 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -70,6 +70,22 @@
         <item>"0,1"</item>
     </string-array>
 
+    <!-- List of regexpressions describing the interface (if any) that represent tetherable
+         USB interfaces.  If the device doesn't want to support tething over USB this should
+         be empty.  An example would be "usb.*" -->
+    <string-array translatable="false" name="config_tether_usb_regexs">
+    </string-array>
+
+    <!-- List of regexpressions describing the interface (if any) that represent tetherable
+         Wifi interfaces.  If the device doesn't want to support tethering over Wifi this
+         should be empty.  An example would be "softap.*" -->
+    <string-array translatable="false" name="config_tether_wifi_regexs">
+    </string-array>
+
+    <!-- Dhcp range (min, max) to use for tethering purposes -->
+    <string-array name="config_tether_dhcp_range">
+    </string-array>
+
     <!-- Flag indicating whether the keyguard should be bypassed when
          the slider is open.  This can be set or unset depending how easily
          the slider can be opened (for example, in a pocket or purse). -->
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 4259016..108246d 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -798,6 +798,12 @@
                 "ConnectivityService");
     }
 
+    private void enforceTetherAccessPermission() {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.ACCESS_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
@@ -1289,6 +1295,8 @@
             pw.println(requester.toString());
         }
         pw.println();
+
+        mTethering.dump(fd, pw, args);
     }
 
     // must be stateless - things change under us.
@@ -1386,24 +1394,54 @@
     // javadoc from interface
     public boolean tether(String iface) {
         enforceTetherChangePermission();
-        return mTethering.tether(iface);
+        return isTetheringSupported() && mTethering.tether(iface);
     }
 
     // javadoc from interface
     public boolean untether(String iface) {
         enforceTetherChangePermission();
-        return mTethering.untether(iface);
+        return isTetheringSupported() && mTethering.untether(iface);
+    }
+
+    // TODO - proper iface API for selection by property, inspection, etc
+    public String[] getTetherableUsbRegexs() {
+        enforceTetherAccessPermission();
+        if (isTetheringSupported()) {
+            return mTethering.getTetherableUsbRegexs();
+        } else {
+            return new String[0];
+        }
+    }
+
+    public String[] getTetherableWifiRegexs() {
+        enforceTetherAccessPermission();
+        if (isTetheringSupported()) {
+            return mTethering.getTetherableWifiRegexs();
+        } else {
+            return new String[0];
+        }
     }
 
     // TODO - move iface listing, queries, etc to new module
     // javadoc from interface
     public String[] getTetherableIfaces() {
-        enforceAccessPermission();
+        enforceTetherAccessPermission();
         return mTethering.getTetherableIfaces();
     }
 
     public String[] getTetheredIfaces() {
-        enforceAccessPermission();
+        enforceTetherAccessPermission();
         return mTethering.getTetheredIfaces();
     }
+
+    // if ro.tether.denied = true we default to no tethering
+    // gservices could set the secure setting to 1 though to enable it on a build where it
+    // had previously been turned off.
+    public boolean isTetheringSupported() {
+        enforceTetherAccessPermission();
+        int defaultVal = (SystemProperties.get("ro.tether.denied").equals("true") ? 0 : 1);
+        return ((Settings.Secure.getInt(mContext.getContentResolver(),
+                Settings.Secure.TETHER_SUPPORTED, defaultVal) != 0) &&
+                (mNetTrackers[ConnectivityManager.TYPE_MOBILE_DUN] != null));
+    }
 }
diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java
index f685383..8d4f244 100644
--- a/services/java/com/android/server/connectivity/Tethering.java
+++ b/services/java/com/android/server/connectivity/Tethering.java
@@ -24,19 +24,37 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.net.ConnectivityManager;
+import android.net.IConnectivityManager;
 import android.net.INetworkManagementEventObserver;
+import android.net.NetworkInfo;
+import android.os.Binder;
 import android.os.IBinder;
 import android.os.INetworkManagementService;
+import android.os.Message;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.provider.Settings;
 import android.util.Log;
 
+import com.android.internal.telephony.Phone;
+import com.android.internal.util.HierarchicalState;
+import com.android.internal.util.HierarchicalStateMachine;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Set;
 /**
  * @hide
+ *
+ * Timeout
+ * TODO - review error states - they currently are dead-ends with no recovery possible
+ *
+ * TODO - look for parent classes and code sharing
  */
 public class Tethering extends INetworkManagementEventObserver.Stub {
 
@@ -46,13 +64,24 @@
 
     private boolean mPlaySounds = false;
 
-    private ArrayList<String> mAvailableIfaces;
-    private ArrayList<String> mActiveIfaces;
+    // TODO - remove both of these - should be part of interface inspection/selection stuff
+    private String[] mTetherableUsbRegexs;
+    private String[] mTetherableWifiRegexs;
+
+    private HashMap<String, TetherInterfaceSM> mIfaces;
 
     private ArrayList<String> mActiveTtys;
 
     private BroadcastReceiver mStateReceiver;
 
+    private String[] mDhcpRange;
+
+    private String[] mDnsServers;
+
+    private String mUpstreamIfaceName;
+
+    HierarchicalStateMachine mTetherMasterSM;
+
     public Tethering(Context context) {
         Log.d(TAG, "Tethering starting");
         mContext = context;
@@ -66,212 +95,234 @@
             Log.e(TAG, "Error registering observer :" + e);
         }
 
-        mAvailableIfaces = new ArrayList<String>();
-        mActiveIfaces = new ArrayList<String>();
+        mIfaces = new HashMap<String, TetherInterfaceSM>();
         mActiveTtys = new ArrayList<String>();
 
+        mTetherMasterSM = new TetherMasterSM("TetherMaster");
+        mTetherMasterSM.start();
+
         // TODO - remove this hack after real USB connections are detected.
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_UMS_DISCONNECTED);
         filter.addAction(Intent.ACTION_UMS_CONNECTED);
-        mStateReceiver = new UMSStateReceiver();
+        filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+        mStateReceiver = new StateReceiver();
         mContext.registerReceiver(mStateReceiver, filter);
+
+        mDhcpRange = context.getResources().getStringArray(
+                com.android.internal.R.array.config_tether_dhcp_range);
+        if (mDhcpRange.length == 0) {
+            mDhcpRange = new String[2];
+            mDhcpRange[0] = new String("169.254.2.1");
+            mDhcpRange[1] = new String("169.254.2.64");
+        } else if(mDhcpRange.length == 1) {
+            String[] tmp = new String[2];
+            tmp[0] = mDhcpRange[0];
+            tmp[1] = new String("");
+            mDhcpRange = tmp;
+        }
+
+        mTetherableUsbRegexs = context.getResources().getStringArray(
+                com.android.internal.R.array.config_tether_usb_regexs);
+        mTetherableWifiRegexs = context.getResources().getStringArray(
+                com.android.internal.R.array.config_tether_wifi_regexs);
+
+        String[] ifaces = new String[0];
+        try {
+            ifaces = service.listInterfaces();
+        } catch (Exception e) {
+            Log.e(TAG, "Error listing Interfaces :" + e);
+        }
+        for (String iface : ifaces) {
+            interfaceAdded(iface);
+        }
+
+        // TODO - remove and rely on real notifications of the current iface
+        mDnsServers = new String[2];
+        mDnsServers[0] = "8.8.8.8";
+        mDnsServers[1] = "4.2.2.2";
+        mUpstreamIfaceName = "rmnet0";
     }
 
-    public synchronized void interfaceLinkStatusChanged(String iface, boolean link) {
+    public void interfaceLinkStatusChanged(String iface, boolean link) {
         Log.d(TAG, "interfaceLinkStatusChanged " + iface + ", " + link);
+        boolean found = false;
+        for (String regex : mTetherableWifiRegexs) {
+            if (iface.matches(regex)) {
+                found = true;
+                break;
+            }
+        }
+        for (String regex: mTetherableUsbRegexs) {
+            if (iface.matches(regex)) {
+                found = true;
+                break;
+            }
+        }
+        if (found == false) return;
+
+        synchronized (mIfaces) {
+            TetherInterfaceSM sm = mIfaces.get(iface);
+            if (link) {
+                if (sm == null) {
+                    sm = new TetherInterfaceSM(iface);
+                    mIfaces.put(iface, sm);
+                    sm.start();
+                }
+            } else {
+                if (sm != null) {
+                    sm.sendMessage(sm.obtainMessage(TetherInterfaceSM.CMD_INTERFACE_DOWN));
+                    mIfaces.remove(iface);
+                }
+            }
+        }
     }
 
-    public synchronized void interfaceAdded(String iface) {
-        if (mActiveIfaces.contains(iface)) {
-            Log.e(TAG, "active iface (" + iface + ") reported as added, ignoring");
+    public void interfaceAdded(String iface) {
+        boolean found = false;
+        for (String regex : mTetherableWifiRegexs) {
+            if (iface.matches(regex)) {
+                found = true;
+                break;
+            }
+        }
+        for (String regex : mTetherableUsbRegexs) {
+            if (iface.matches(regex)) {
+                found = true;
+                break;
+            }
+        }
+        if (found == false) {
+            Log.d(TAG, iface + " is not a tetherable iface, ignoring");
             return;
         }
-        if (mAvailableIfaces.contains(iface)) {
-            Log.e(TAG, "available iface (" + iface + ") readded, ignoring");
-            return;
+        synchronized (mIfaces) {
+            TetherInterfaceSM sm = mIfaces.get(iface);
+            if (sm != null) {
+                Log.e(TAG, "active iface (" + iface + ") reported as added, ignoring");
+                return;
+            }
+            sm = new TetherInterfaceSM(iface);
+            mIfaces.put(iface, sm);
+            sm.start();
         }
-        mAvailableIfaces.add(iface);
         Log.d(TAG, "interfaceAdded :" + iface);
-        sendTetherStateChangedBroadcast();
     }
 
-    public synchronized void interfaceRemoved(String iface) {
-        if (mActiveIfaces.contains(iface)) {
-            Log.d(TAG, "removed an active iface (" + iface + ")");
-            untether(iface);
-        }
-        if (mAvailableIfaces.contains(iface)) {
-            mAvailableIfaces.remove(iface);
-            Log.d(TAG, "interfaceRemoved " + iface);
-            sendTetherStateChangedBroadcast();
+    public void interfaceRemoved(String iface) {
+        synchronized (mIfaces) {
+            TetherInterfaceSM sm = mIfaces.get(iface);
+            if (sm == null) {
+                Log.e(TAG, "attempting to remove unknown iface (" + iface + "), ignoring");
+                return;
+            }
+            sm.sendMessage(sm.obtainMessage(TetherInterfaceSM.CMD_INTERFACE_DOWN));
+            mIfaces.remove(iface);
         }
     }
 
-    public synchronized boolean tether(String iface) {
+    public boolean tether(String iface) {
         Log.d(TAG, "Tethering " + iface);
-
-        if (!mAvailableIfaces.contains(iface)) {
+        TetherInterfaceSM sm = null;
+        synchronized (mIfaces) {
+            sm = mIfaces.get(iface);
+        }
+        if (sm == null) {
+            Log.e(TAG, "Tried to Tether an unknown iface :" + iface + ", ignoring");
+            return false;
+        }
+        if (sm.isErrored()) {
+            Log.e(TAG, "Tried to Tether to an errored iface :" + iface + ", ignoring");
+            return false;
+        }
+        if (!sm.isAvailable()) {
             Log.e(TAG, "Tried to Tether an unavailable iface :" + iface + ", ignoring");
             return false;
         }
-        if (mActiveIfaces.contains(iface)) {
-            Log.e(TAG, "Tried to Tether an already Tethered iface :" + iface + ", ignoring");
-            return false;
-        }
-
-        IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
-        INetworkManagementService service = INetworkManagementService.Stub.asInterface(b);
-
-        if (mActiveIfaces.size() == 0) {
-            try {
-                service.setIpForwardingEnabled(true);
-            } catch (Exception e) {
-                Log.e(TAG, "Error in setIpForwardingEnabled(true) :" + e);
-                return false;
-            }
-
-            try {
-                // TODO - don't hardcode this - though with non-conf values (un-routable)
-                // maybe it's not a big deal
-                service.startTethering("169.254.2.1", "169.254.2.64");
-            } catch (Exception e) {
-                Log.e(TAG, "Error in startTethering :" + e);
-                try {
-                    service.setIpForwardingEnabled(false);
-                } catch (Exception ee) {}
-                return false;
-            }
-
-            try {
-                // TODO - maybe use the current connection's dns servers for this
-                String[] dns = new String[2];
-                dns[0] = new String("8.8.8.8");
-                dns[1] = new String("4.2.2.2");
-                service.setDnsForwarders(dns);
-            } catch (Exception e) {
-                Log.e(TAG, "Error in setDnsForwarders :" + e);
-                try {
-                    service.stopTethering();
-                } catch (Exception ee) {}
-                try {
-                    service.setIpForwardingEnabled(false);
-                } catch (Exception ee) {}
-            }
-        }
-
-        try {
-            service.tetherInterface(iface);
-        } catch (Exception e) {
-            Log.e(TAG, "Error in tetherInterface :" + e);
-            if (mActiveIfaces.size() == 0) {
-                try {
-                    service.stopTethering();
-                } catch (Exception ee) {}
-                try {
-                    service.setIpForwardingEnabled(false);
-                } catch (Exception ee) {}
-            }
-            return false;
-        }
-
-        try {
-            // TODO - use the currently active external iface
-            service.enableNat (iface, "rmnet0");
-        } catch (Exception e) {
-            Log.e(TAG, "Error in enableNat :" + e);
-            try {
-                service.untetherInterface(iface);
-            } catch (Exception ee) {}
-            if (mActiveIfaces.size() == 0) {
-                try {
-                    service.stopTethering();
-                } catch (Exception ee) {}
-                try {
-                    service.setIpForwardingEnabled(false);
-                } catch (Exception ee) {}
-            }
-            return false;
-        }
-        mAvailableIfaces.remove(iface);
-        mActiveIfaces.add(iface);
-        Log.d(TAG, "Tethered " + iface);
-        sendTetherStateChangedBroadcast();
+        sm.sendMessage(sm.obtainMessage(TetherInterfaceSM.CMD_TETHER_REQUESTED));
         return true;
     }
 
-    public synchronized boolean untether(String iface) {
+    public boolean untether(String iface) {
         Log.d(TAG, "Untethering " + iface);
-
-        if (mAvailableIfaces.contains(iface)) {
-            Log.e(TAG, "Tried to Untether an available iface :" + iface);
+        TetherInterfaceSM sm = null;
+        synchronized (mIfaces) {
+            sm = mIfaces.get(iface);
+        }
+        if (sm == null) {
+            Log.e(TAG, "Tried to Untether an unknown iface :" + iface + ", ignoring");
             return false;
         }
-        if (!mActiveIfaces.contains(iface)) {
-            Log.e(TAG, "Tried to Untether an inactive iface :" + iface);
+        if (sm.isErrored()) {
+            Log.e(TAG, "Tried to Untethered an errored iface :" + iface + ", ignoring");
             return false;
         }
-
-        IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
-        INetworkManagementService service = INetworkManagementService.Stub.asInterface(b);
-
-        // none of these errors are recoverable - ie, multiple calls won't help
-        // and the user can't do anything.  Basically a reboot is required and probably
-        // the device is misconfigured or something bad has happend.
-        // Because of this, we should try to unroll as much state as we can.
-        try {
-            service.disableNat(iface, "rmnet0");
-        } catch (Exception e) {
-            Log.e(TAG, "Error in disableNat :" + e);
-        }
-        try {
-            service.untetherInterface(iface);
-        } catch (Exception e) {
-            Log.e(TAG, "Error untethering " + iface + ", :" + e);
-        }
-        mActiveIfaces.remove(iface);
-        mAvailableIfaces.add(iface);
-
-        if (mActiveIfaces.size() == 0) {
-            Log.d(TAG, "no active tethers - turning down dhcp/ipforward");
-            try {
-                service.stopTethering();
-            } catch (Exception e) {
-                Log.e(TAG, "Error in stopTethering :" + e);
-            }
-            try {
-                service.setIpForwardingEnabled(false);
-            } catch (Exception e) {
-                Log.e(TAG, "Error in setIpForwardingEnabled(false) :" + e);
-            }
-        }
-        sendTetherStateChangedBroadcast();
-        Log.d(TAG, "Untethered " + iface);
+        sm.sendMessage(sm.obtainMessage(TetherInterfaceSM.CMD_TETHER_UNREQUESTED));
         return true;
     }
 
     private void sendTetherStateChangedBroadcast() {
+        IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
+        IConnectivityManager service = IConnectivityManager.Stub.asInterface(b);
+        try {
+            if (!service.isTetheringSupported()) return;
+        } catch (RemoteException e) {
+            return;
+        }
+
+        ArrayList<String> availableList = new ArrayList<String>();
+        ArrayList<String> activeList = new ArrayList<String>();
+        ArrayList<String> erroredList = new ArrayList<String>();
+
+        synchronized (mIfaces) {
+            Set ifaces = mIfaces.keySet();
+            for (Object iface : ifaces) {
+                TetherInterfaceSM sm = mIfaces.get(iface);
+                if (sm != null) {
+                    if(sm.isErrored()) {
+                        erroredList.add((String)iface);
+                    } else if (sm.isAvailable()) {
+                        availableList.add((String)iface);
+                    } else if (sm.isTethered()) {
+                        activeList.add((String)iface);
+                    }
+                }
+            }
+        }
         Intent broadcast = new Intent(ConnectivityManager.ACTION_TETHER_STATE_CHANGED);
         broadcast.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
-        broadcast.putExtra(ConnectivityManager.EXTRA_AVAILABLE_TETHER_COUNT,
-                mAvailableIfaces.size());
-        broadcast.putExtra(ConnectivityManager.EXTRA_ACTIVE_TETHER_COUNT, mActiveIfaces.size());
-        mContext.sendBroadcast(broadcast);
+        broadcast.putStringArrayListExtra(ConnectivityManager.EXTRA_AVAILABLE_TETHER,
+                availableList);
+        broadcast.putStringArrayListExtra(ConnectivityManager.EXTRA_ACTIVE_TETHER, activeList);
+        broadcast.putStringArrayListExtra(ConnectivityManager.EXTRA_ERRORED_TETHER,
+                erroredList);
+        mContext.sendStickyBroadcast(broadcast);
 
-        // for USB we only have the one, so don't have to deal with additional
-        if (mAvailableIfaces.size() > 0) {
-            // Check if the user wants to be bothered
-            boolean tellUser = (Settings.Secure.getInt(mContext.getContentResolver(),
-                    Settings.Secure.TETHER_NOTIFY, 0) == 1);
-
-            if (tellUser) {
-                showTetherAvailableNotification();
+        // check if we need to send a USB notification
+        // Check if the user wants to be bothered
+        boolean tellUser = (Settings.Secure.getInt(mContext.getContentResolver(),
+                Settings.Secure.TETHER_NOTIFY, 0) == 1);
+        for (Object o : activeList) {
+            String s = (String)o;
+            for (Object regexObject : mTetherableUsbRegexs) {
+                if (s.matches((String)regexObject)) {
+                    showTetheredNotification();
+                    return;
+                }
             }
-        } else if (mActiveIfaces.size() > 0) {
-            showTetheredNotification();
-        } else {
-            clearNotification();
         }
+        if (tellUser) {
+            for (Object o : availableList) {
+                String s = (String)o;
+                for (Object matchObject : mTetherableUsbRegexs) {
+                    if (s.matches((String)matchObject)) {
+                        showTetherAvailableNotification();
+                        return;
+                    }
+                }
+            }
+        }
+        clearNotification();
     }
 
     private void showTetherAvailableNotification() {
@@ -280,7 +331,6 @@
         if (notificationManager == null) {
             return;
         }
-
         Intent intent = new Intent();
         intent.setClass(mContext, com.android.internal.app.TetherActivity.class);
 
@@ -367,18 +417,32 @@
 
 
 
-// TODO - remove this hack after we get proper USB detection
-    private class UMSStateReceiver extends BroadcastReceiver {
+    private class StateReceiver extends BroadcastReceiver {
         public void onReceive(Context content, Intent intent) {
-            if (intent.getAction().equals(Intent.ACTION_UMS_CONNECTED)) {
+            String action = intent.getAction();
+            if (action.equals(Intent.ACTION_UMS_CONNECTED)) {
                 Tethering.this.handleTtyConnect();
-            } else if (intent.getAction().equals(Intent.ACTION_UMS_DISCONNECTED)) {
+            } else if (action.equals(Intent.ACTION_UMS_DISCONNECTED)) {
                 Tethering.this.handleTtyDisconnect();
+            } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
+                IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
+                IConnectivityManager service =
+                        IConnectivityManager.Stub.asInterface(b);
+                try {
+                    NetworkInfo info = service.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_DUN);
+                    int msg;
+                    if (info.isConnected() == true) {
+                        msg = TetherMasterSM.CMD_CELL_DUN_ENABLED;
+                    } else {
+                        msg = TetherMasterSM.CMD_CELL_DUN_DISABLED;
+                    }
+                    mTetherMasterSM.sendMessage(mTetherMasterSM.obtainMessage(msg));
+                } catch (RemoteException e) {}
             }
         }
     }
 
-    private synchronized void handleTtyConnect() {
+    private void handleTtyConnect() {
         Log.d(TAG, "handleTtyConnect");
         // for each of the available Tty not already supported by a ppp session,
         // create a ppp session
@@ -461,23 +525,950 @@
         }
     }
 
-    public synchronized String[] getTetheredIfaces() {
-        int size = mActiveIfaces.size();
-        String[] result = new String[size];
-        size -= 1;
-        for (int i=0; i< size; i++) {
-            result[i] = mActiveIfaces.get(i);
-        }
-        return result;
+    public String[] getTetherableUsbRegexs() {
+        return mTetherableUsbRegexs;
     }
 
-    public synchronized String[] getTetherableIfaces() {
-        int size = mAvailableIfaces.size();
-        String[] result = new String[size];
-        size -= 1;
-        for (int i=0; i< size; i++) {
-            result[i] = mActiveIfaces.get(i);
+    public String[] getTetherableWifiRegexs() {
+        return mTetherableWifiRegexs;
+    }
+
+    public String[] getTetheredIfaces() {
+        ArrayList<String> list = new ArrayList<String>();
+        synchronized (mIfaces) {
+            Set keys = mIfaces.keySet();
+            for (Object key : keys) {
+                TetherInterfaceSM sm = mIfaces.get(key);
+                if (sm.isTethered()) {
+                    list.add((String)key);
+                }
+            }
         }
-        return result;
+        String[] retVal = new String[list.size()];
+        for (int i=0; i < list.size(); i++) {
+            retVal[i] = list.get(i);
+        }
+        return retVal;
+    }
+
+    public String[] getTetherableIfaces() {
+        ArrayList<String> list = new ArrayList<String>();
+        synchronized (mIfaces) {
+            Set keys = mIfaces.keySet();
+            for (Object key : keys) {
+                TetherInterfaceSM sm = mIfaces.get(key);
+                if (sm.isAvailable()) {
+                    list.add((String)key);
+                }
+            }
+        }
+        String[] retVal = new String[list.size()];
+        for (int i=0; i < list.size(); i++) {
+            retVal[i] = list.get(i);
+        }
+        return retVal;
+    }
+
+
+    class TetherInterfaceSM extends HierarchicalStateMachine {
+        // notification from the master SM that it's in tether mode
+        static final int CMD_TETHER_MODE_ALIVE           =  1;
+        // notification from the master SM that it's not in tether mode
+        static final int CMD_TETHER_MODE_DEAD            =  2;
+        // request from the user that it wants to tether
+        static final int CMD_TETHER_REQUESTED            =  3;
+        // request from the user that it wants to untether
+        static final int CMD_TETHER_UNREQUESTED          =  4;
+        // notification that this interface is down
+        static final int CMD_INTERFACE_DOWN              =  5;
+        // notification that this interface is up
+        static final int CMD_INTERFACE_UP                =  6;
+        // notification from the master SM that it had an error turning on cellular dun
+        static final int CMD_CELL_DUN_ERROR              = 10;
+        // notification from the master SM that it had trouble enabling IP Forwarding
+        static final int CMD_IP_FORWARDING_ENABLE_ERROR  = 11;
+        // notification from the master SM that it had trouble disabling IP Forwarding
+        static final int CMD_IP_FORWARDING_DISABLE_ERROR = 12;
+        // notification from the master SM that it had trouble staring tethering
+        static final int CMD_START_TETHERING_ERROR       = 13;
+        // notification from the master SM that it had trouble stopping tethering
+        static final int CMD_STOP_TETHERING_ERROR        = 14;
+        // notification from the master SM that it had trouble setting the DNS forwarders
+        static final int CMD_SET_DNS_FORWARDERS_ERROR    = 15;
+        // a mechanism to transition self to error state from an enter function
+        static final int CMD_TRANSITION_TO_ERROR         = 16;
+
+        private HierarchicalState mDefaultState;
+
+        private HierarchicalState mInitialState;
+        private HierarchicalState mStartingState;
+        private HierarchicalState mTetheredState;
+
+        private HierarchicalState mMasterTetherErrorState;
+        private HierarchicalState mTetherInterfaceErrorState;
+        private HierarchicalState mUntetherInterfaceErrorState;
+        private HierarchicalState mEnableNatErrorState;
+        private HierarchicalState mDisableNatErrorState;
+
+        private HierarchicalState mUnavailableState;
+
+        private boolean mAvailable;
+        private boolean mErrored;
+        private boolean mTethered;
+
+        String mIfaceName;
+
+        TetherInterfaceSM(String name) {
+            super(name);
+            mIfaceName = name;
+
+            mInitialState = new InitialState();
+            addState(mInitialState);
+            mStartingState = new StartingState();
+            addState(mStartingState);
+            mTetheredState = new TetheredState();
+            addState(mTetheredState);
+            mMasterTetherErrorState = new MasterTetherErrorState();
+            addState(mMasterTetherErrorState);
+            mTetherInterfaceErrorState = new TetherInterfaceErrorState();
+            addState(mTetherInterfaceErrorState);
+            mUntetherInterfaceErrorState = new UntetherInterfaceErrorState();
+            addState(mUntetherInterfaceErrorState);
+            mEnableNatErrorState = new EnableNatErrorState();
+            addState(mEnableNatErrorState);
+            mDisableNatErrorState = new DisableNatErrorState();
+            addState(mDisableNatErrorState);
+            mUnavailableState = new UnavailableState();
+            addState(mUnavailableState);
+
+            setInitialState(mInitialState);
+        }
+
+        public String toString() {
+            String res = new String();
+            res += mIfaceName + " - ";
+            HierarchicalState current = getCurrentState();
+            if (current == mInitialState) res += "InitialState";
+            if (current == mStartingState) res += "StartingState";
+            if (current == mTetheredState) res += "TetheredState";
+            if (current == mMasterTetherErrorState) res += "MasterTetherErrorState";
+            if (current == mTetherInterfaceErrorState) res += "TetherInterfaceErrorState";
+            if (current == mUntetherInterfaceErrorState) res += "UntetherInterfaceErrorState";
+            if (current == mEnableNatErrorState) res += "EnableNatErrorState";
+            if (current == mDisableNatErrorState) res += "DisableNatErrorState";
+            if (current == mUnavailableState) res += "UnavailableState";
+            if (mAvailable) res += " - Available";
+            if (mTethered) res += " - Tethered";
+            if (mErrored) res += " - ERRORED";
+            return res;
+        }
+
+        // synchronized between this getter and the following setter
+        public synchronized boolean isAvailable() {
+            return mAvailable;
+        }
+
+        private synchronized void setAvailable(boolean available) {
+            mAvailable = available;
+        }
+
+        // synchronized between this getter and the following setter
+        public synchronized boolean isTethered() {
+            return mTethered;
+        }
+
+        private synchronized void setTethered(boolean tethered) {
+            mTethered = tethered;
+        }
+
+        // synchronized between this getter and the following setter
+        public synchronized boolean isErrored() {
+            return mErrored;
+        }
+
+        private synchronized void setErrored(boolean errored) {
+            mErrored = errored;
+        }
+
+        class InitialState extends HierarchicalState {
+            @Override
+            public void enter() {
+                setAvailable(true);
+                setTethered(false);
+                setErrored(false);
+                sendTetherStateChangedBroadcast();
+            }
+
+            @Override
+            public boolean processMessage(Message message) {
+                Log.d(TAG, "InitialState.processMessage what=" + message.what);
+                boolean retValue = true;
+                switch (message.what) {
+                    case CMD_TETHER_REQUESTED:
+                        Message m = mTetherMasterSM.obtainMessage(
+                                TetherMasterSM.CMD_TETHER_MODE_REQUESTED);
+                        m.obj = TetherInterfaceSM.this;
+                        mTetherMasterSM.sendMessage(m);
+                        transitionTo(mStartingState);
+                        break;
+                    case CMD_INTERFACE_DOWN:
+                        transitionTo(mUnavailableState);
+                        break;
+                    default:
+                        retValue = false;
+                        break;
+                }
+                return retValue;
+            }
+        }
+
+        class StartingState extends HierarchicalState {
+            @Override
+            public void enter() {
+                setAvailable(false);
+                sendTetherStateChangedBroadcast();
+            }
+            @Override
+            public boolean processMessage(Message message) {
+                Log.d(TAG, "StartingState.processMessage what=" + message.what);
+                boolean retValue = true;
+                switch (message.what) {
+                    // maybe a parent class?
+                    case CMD_TETHER_UNREQUESTED:
+                        Message m = mTetherMasterSM.obtainMessage(
+                                TetherMasterSM.CMD_TETHER_MODE_UNREQUESTED);
+                        m.obj = TetherInterfaceSM.this;
+                        mTetherMasterSM.sendMessage(m);
+                        transitionTo(mInitialState);
+                        break;
+                    case CMD_TETHER_MODE_ALIVE:
+                        transitionTo(mTetheredState);
+                        break;
+                    case CMD_CELL_DUN_ERROR:
+                    case CMD_IP_FORWARDING_ENABLE_ERROR:
+                    case CMD_IP_FORWARDING_DISABLE_ERROR:
+                    case CMD_START_TETHERING_ERROR:
+                    case CMD_STOP_TETHERING_ERROR:
+                    case CMD_SET_DNS_FORWARDERS_ERROR:
+                        transitionTo(mMasterTetherErrorState);
+                        break;
+                    case CMD_INTERFACE_DOWN:
+                        m = mTetherMasterSM.obtainMessage(
+                                TetherMasterSM.CMD_TETHER_MODE_UNREQUESTED);
+                        m.obj = TetherInterfaceSM.this;
+                        mTetherMasterSM.sendMessage(m);
+                        transitionTo(mUnavailableState);
+                        break;
+                    default:
+                        retValue = false;
+                }
+                return retValue;
+            }
+        }
+
+        class TetheredState extends HierarchicalState {
+            @Override
+            public void enter() {
+                IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+                INetworkManagementService service =
+                        INetworkManagementService.Stub.asInterface(b);
+                try {
+                    service.tetherInterface(mIfaceName);
+                } catch (Exception e) {
+                    Message m = obtainMessage(CMD_TRANSITION_TO_ERROR);
+                    m.obj = mTetherInterfaceErrorState;
+                    sendMessageAtFrontOfQueue(m);
+                    return;
+                }
+                try {
+                    service.enableNat(mIfaceName, mUpstreamIfaceName);
+                } catch (Exception e) {
+                    Message m = obtainMessage(CMD_TRANSITION_TO_ERROR);
+                    m.obj = mEnableNatErrorState;
+                    sendMessageAtFrontOfQueue(m);
+                    return;
+                }
+                Log.d(TAG, "Tethered " + mIfaceName);
+                setAvailable(false);
+                setTethered(true);
+                sendTetherStateChangedBroadcast();
+            }
+            @Override
+            public boolean processMessage(Message message) {
+                Log.d(TAG, "TetheredState.processMessage what=" + message.what);
+                boolean retValue = true;
+                boolean error = false;
+                switch (message.what) {
+                    case CMD_TETHER_UNREQUESTED:
+                    case CMD_INTERFACE_DOWN:
+                        IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+                        INetworkManagementService service =
+                                INetworkManagementService.Stub.asInterface(b);
+                        try {
+                            service.disableNat(mIfaceName, mUpstreamIfaceName);
+                        } catch (Exception e) {
+                            transitionTo(mDisableNatErrorState);
+                            break;
+                        }
+                        try {
+                            service.untetherInterface(mIfaceName);
+                        } catch (Exception e) {
+                            transitionTo(mUntetherInterfaceErrorState);
+                            break;
+                        }
+                        Message m = mTetherMasterSM.obtainMessage(
+                                TetherMasterSM.CMD_TETHER_MODE_UNREQUESTED);
+                        m.obj = TetherInterfaceSM.this;
+                        mTetherMasterSM.sendMessage(m);
+                        if (message.what == CMD_TETHER_UNREQUESTED) {
+                            transitionTo(mInitialState);
+                        } else if (message.what == CMD_INTERFACE_DOWN) {
+                            transitionTo(mUnavailableState);
+                        }
+                        Log.d(TAG, "Untethered " + mIfaceName);
+                        sendTetherStateChangedBroadcast();
+                        break;
+                    case CMD_CELL_DUN_ERROR:
+                    case CMD_IP_FORWARDING_ENABLE_ERROR:
+                    case CMD_IP_FORWARDING_DISABLE_ERROR:
+                    case CMD_START_TETHERING_ERROR:
+                    case CMD_STOP_TETHERING_ERROR:
+                    case CMD_SET_DNS_FORWARDERS_ERROR:
+                        error = true;
+                        // fall through
+                    case CMD_TETHER_MODE_DEAD:
+                        b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+                        service = INetworkManagementService.Stub.asInterface(b);
+                        try {
+                            service.disableNat(mIfaceName, mUpstreamIfaceName);
+                        } catch (Exception e) {
+                            transitionTo(mDisableNatErrorState);
+                            break;
+                        }
+                        try {
+                            service.untetherInterface(mIfaceName);
+                        } catch (Exception e) {
+                            transitionTo(mUntetherInterfaceErrorState);
+                            break;
+                        }
+                        if (error) {
+                            transitionTo(mMasterTetherErrorState);
+                            break;
+                        }
+                        Log.d(TAG, "Tether lost upstream connection " + mIfaceName);
+                        sendTetherStateChangedBroadcast();
+                        transitionTo(mInitialState);
+                        break;
+                    case CMD_TRANSITION_TO_ERROR:
+                        HierarchicalState s = (HierarchicalState)(message.obj);
+                        transitionTo(s);
+                        break;
+                    default:
+                        retValue = false;
+                        break;
+                }
+                return retValue;
+            }
+        }
+
+        class UnavailableState extends HierarchicalState {
+            @Override
+            public void enter() {
+                setAvailable(false);
+                setErrored(false);
+                setTethered(false);
+                sendTetherStateChangedBroadcast();
+            }
+            @Override
+            public boolean processMessage(Message message) {
+                boolean retValue = true;
+                switch (message.what) {
+                    case CMD_INTERFACE_UP:
+                        transitionTo(mInitialState);
+                        break;
+                    default:
+                        retValue = false;
+                        break;
+                }
+                return retValue;
+            }
+        }
+
+
+        class ErrorState extends HierarchicalState {
+            int mErrorNotification;
+            @Override
+            public boolean processMessage(Message message) {
+                boolean retValue = true;
+                switch (message.what) {
+                    case CMD_TETHER_REQUESTED:
+                        sendTetherStateChangedBroadcast();
+                        break;
+                    default:
+                        retValue = false;
+                        break;
+                }
+                return retValue;
+            }
+        }
+
+        class MasterTetherErrorState extends ErrorState {
+            @Override
+            public void enter() {
+                Log.e(TAG, "Error in Master Tether state " + mIfaceName);
+                setAvailable(false);
+                setErrored(true);
+                sendTetherStateChangedBroadcast();
+            }
+        }
+
+        class TetherInterfaceErrorState extends ErrorState {
+            @Override
+            public void enter() {
+                Log.e(TAG, "Error trying to tether " + mIfaceName);
+                setAvailable(false);
+                setErrored(true);
+                sendTetherStateChangedBroadcast();
+            }
+        }
+
+        class UntetherInterfaceErrorState extends ErrorState {
+            @Override
+            public void enter() {
+                Log.e(TAG, "Error trying to untether " + mIfaceName);
+                setAvailable(false);
+                setErrored(true);
+                sendTetherStateChangedBroadcast();
+            }
+        }
+
+        class EnableNatErrorState extends ErrorState {
+            @Override
+            public void enter() {
+                Log.e(TAG, "Error trying to enable NAT " + mIfaceName);
+                setAvailable(false);
+                setErrored(true);
+
+                IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+                INetworkManagementService service = INetworkManagementService.Stub.asInterface(b);
+                try {
+                    service.untetherInterface(mIfaceName);
+                } catch (Exception e) {}
+                sendTetherStateChangedBroadcast();
+            }
+        }
+
+
+        class DisableNatErrorState extends ErrorState {
+            @Override
+            public void enter() {
+                Log.e(TAG, "Error trying to disable NAT " + mIfaceName);
+                setAvailable(false);
+                setErrored(true);
+
+                IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+                INetworkManagementService service = INetworkManagementService.Stub.asInterface(b);
+                try {
+                    service.untetherInterface(mIfaceName);
+                } catch (Exception e) {}
+                sendTetherStateChangedBroadcast();
+            }
+        }
+    }
+
+    class TetherMasterSM extends HierarchicalStateMachine {
+        // an interface SM has requested Tethering
+        static final int CMD_TETHER_MODE_REQUESTED   = 1;
+        // an interface SM has unrequested Tethering
+        static final int CMD_TETHER_MODE_UNREQUESTED = 2;
+        // we received notice that the cellular DUN connection is up
+        static final int CMD_CELL_DUN_ENABLED        = 3;
+        // we received notice that the cellular DUN connection is down
+        static final int CMD_CELL_DUN_DISABLED       = 4;
+        // we timed out on a cellular DUN toggle
+        static final int CMD_CELL_DUN_TIMEOUT        = 5;
+        // it's time to renew our cellular DUN reservation
+        static final int CMD_CELL_DUN_RENEW          = 6;
+
+        // This indicates what a timeout event relates to.  A state that
+        // sends itself a delayed timeout event and handles incoming timeout events
+        // should inc this when it is entered and whenever it sends a new timeout event.
+        // We do not flush the old ones.
+        private int mSequenceNumber;
+
+        private HierarchicalState mInitialState;
+        private HierarchicalState mCellDunRequestedState;
+        private HierarchicalState mCellDunAliveState;
+        private HierarchicalState mTetherModeAliveState;
+        private HierarchicalState mCellDunUnRequestedState;
+
+        private HierarchicalState mCellDunErrorState;
+        private HierarchicalState mSetIpForwardingEnabledErrorState;
+        private HierarchicalState mSetIpForwardingDisabledErrorState;
+        private HierarchicalState mStartTetheringErrorState;
+        private HierarchicalState mStopTetheringErrorState;
+        private HierarchicalState mSetDnsForwardersErrorState;
+
+        private ArrayList mNotifyList;
+
+
+        private static final int CELL_DUN_TIMEOUT_MS         = 45000;
+        private static final int CELL_DISABLE_DUN_TIMEOUT_MS = 3000;
+        private static final int CELL_DUN_RENEW_MS           = 40000;
+
+        TetherMasterSM(String name) {
+            super(name);
+
+            //Add states
+            mInitialState = new InitialState();
+            addState(mInitialState);
+            mCellDunRequestedState = new CellDunRequestedState();
+            addState(mCellDunRequestedState);
+            mCellDunAliveState = new CellDunAliveState();
+            addState(mCellDunAliveState);
+            mTetherModeAliveState = new TetherModeAliveState();
+            addState(mTetherModeAliveState);
+            mCellDunUnRequestedState = new CellDunUnRequestedState();
+            addState(mCellDunUnRequestedState);
+
+            mCellDunErrorState = new CellDunErrorState();
+            addState(mCellDunErrorState);
+            mSetIpForwardingEnabledErrorState = new SetIpForwardingEnabledErrorState();
+            addState(mSetIpForwardingEnabledErrorState);
+            mSetIpForwardingDisabledErrorState = new SetIpForwardingDisabledErrorState();
+            addState(mSetIpForwardingDisabledErrorState);
+            mStartTetheringErrorState = new StartTetheringErrorState();
+            addState(mStartTetheringErrorState);
+            mStopTetheringErrorState = new StopTetheringErrorState();
+            addState(mStopTetheringErrorState);
+            mSetDnsForwardersErrorState = new SetDnsForwardersErrorState();
+            addState(mSetDnsForwardersErrorState);
+
+            mNotifyList = new ArrayList();
+
+            setInitialState(mInitialState);
+        }
+
+
+        class InitialState extends HierarchicalState {
+            @Override
+            public boolean processMessage(Message message) {
+                Log.d(TAG, "MasterInitialState.processMessage what=" + message.what);
+                boolean retValue = true;
+                switch (message.what) {
+                    case CMD_TETHER_MODE_REQUESTED:
+                        TetherInterfaceSM who = (TetherInterfaceSM)message.obj;
+                        Log.d(TAG, "Tether Mode requested by " + who.toString());
+                        mNotifyList.add(who);
+                        transitionTo(mCellDunRequestedState);
+                        break;
+                    case CMD_TETHER_MODE_UNREQUESTED:
+                        who = (TetherInterfaceSM)message.obj;
+                        Log.d(TAG, "Tether Mode unrequested by " + who.toString());
+                        int index = mNotifyList.indexOf(who);
+                        if (index != -1) {
+                            mNotifyList.remove(who);
+                        }
+                        break;
+                    case CMD_CELL_DUN_ENABLED:
+                        transitionTo(mCellDunAliveState);
+                        break;
+                    default:
+                        retValue = false;
+                        break;
+                }
+                return retValue;
+            }
+        }
+        class CellDunRequestedState extends HierarchicalState {
+            @Override
+            public void enter() {
+                ++mSequenceNumber;
+                IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
+                IConnectivityManager service =
+                        IConnectivityManager.Stub.asInterface(b);
+                int result;
+                try {
+                    result = service.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE,
+                            Phone.FEATURE_ENABLE_DUN, new Binder());
+                } catch (Exception e) {
+                    result = Phone.APN_REQUEST_FAILED;
+                }
+                switch (result) {
+                    case Phone.APN_ALREADY_ACTIVE:
+                        Log.d(TAG, "Dun already active");
+                        sendMessage(obtainMessage(CMD_CELL_DUN_ENABLED));
+                        break;
+                    case Phone.APN_REQUEST_FAILED:
+                    case Phone.APN_TYPE_NOT_AVAILABLE:
+                        Log.d(TAG, "Error bringing up Dun connection");
+                        Message m = obtainMessage(CMD_CELL_DUN_TIMEOUT);
+                        m.arg1 = mSequenceNumber;
+                        sendMessage(m);
+                        break;
+                    case Phone.APN_REQUEST_STARTED:
+                        Log.d(TAG, "Started bringing up Dun connection");
+                        m = obtainMessage(CMD_CELL_DUN_TIMEOUT);
+                        m.arg1 = mSequenceNumber;
+                        sendMessageDelayed(m, CELL_DUN_TIMEOUT_MS);
+                        break;
+                    default:
+                        Log.e(TAG, "Unknown return value from startUsingNetworkFeature " + result);
+                }
+            }
+
+            @Override
+            public boolean processMessage(Message message) {
+                Log.d(TAG, "CellDunRequestedState.processMessage what=" + message.what);
+                boolean retValue = true;
+                switch (message.what) {
+                    case CMD_TETHER_MODE_REQUESTED:
+                        TetherInterfaceSM who = (TetherInterfaceSM)message.obj;
+                        mNotifyList.add(who);
+                        break;
+                    case CMD_TETHER_MODE_UNREQUESTED:
+                        who = (TetherInterfaceSM)message.obj;
+                        int index = mNotifyList.indexOf(who);
+                        if (index != -1) {
+                            mNotifyList.remove(index);
+                            if (mNotifyList.isEmpty()) {
+                                transitionTo(mCellDunUnRequestedState);
+                            }
+                        }
+                        break;
+                    case CMD_CELL_DUN_ENABLED:
+                        IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+                        INetworkManagementService service =
+                                INetworkManagementService.Stub.asInterface(b);
+
+                        try {
+                            service.setIpForwardingEnabled(true);
+                        } catch (Exception e) {
+                            transitionTo(mSetIpForwardingEnabledErrorState);
+                            break;
+                        }
+                        try {
+                            service.startTethering(mDhcpRange[0], mDhcpRange[1]);
+                        } catch (Exception e) {
+                            transitionTo(mStartTetheringErrorState);
+                            break;
+                        }
+                        try {
+                            service.setDnsForwarders(mDnsServers);
+                        } catch (Exception e) {
+                            transitionTo(mSetDnsForwardersErrorState);
+                            break;
+                        }
+                        transitionTo(mTetherModeAliveState);
+                        break;
+                    case CMD_CELL_DUN_DISABLED:
+                        break;
+                    case CMD_CELL_DUN_TIMEOUT:
+                        if (message.arg1 == mSequenceNumber) {
+                            transitionTo(mCellDunErrorState);
+                        }
+                        break;
+                    default:
+                        retValue = false;
+                        break;
+                }
+                return retValue;
+            }
+        }
+
+        class CellDunAliveState extends HierarchicalState {
+            @Override
+            public void enter() {
+                sendMessageDelayed(obtainMessage(CMD_CELL_DUN_RENEW), CELL_DUN_RENEW_MS);
+            }
+
+            @Override
+            public boolean processMessage(Message message) {
+                Log.d(TAG, "CellDunAliveState.processMessage what=" + message.what);
+                boolean retValue = true;
+                switch (message.what) {
+                    case CMD_TETHER_MODE_REQUESTED:
+                        TetherInterfaceSM who = (TetherInterfaceSM)message.obj;
+                        mNotifyList.add(who);
+                        IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+                        INetworkManagementService service =
+                                INetworkManagementService.Stub.asInterface(b);
+
+                        try {
+                            service.setIpForwardingEnabled(true);
+                        } catch (Exception e) {
+                            transitionTo(mSetIpForwardingEnabledErrorState);
+                            break;
+                        }
+                        try {
+                            service.startTethering(mDhcpRange[0], mDhcpRange[1]);
+                        } catch (Exception e) {
+                            transitionTo(mStartTetheringErrorState);
+                            break;
+                        }
+                        try {
+                            service.setDnsForwarders(mDnsServers);
+                        } catch (Exception e) {
+                            transitionTo(mSetDnsForwardersErrorState);
+                            break;
+                        }
+                        transitionTo(mTetherModeAliveState);
+                        break;
+                    case CMD_TETHER_MODE_UNREQUESTED:
+                        who = (TetherInterfaceSM)message.obj;
+                        int index = mNotifyList.indexOf(who);
+                        if (index != -1) {
+                            mNotifyList.remove(index);
+                            if (mNotifyList.isEmpty()) {
+                                transitionTo(mCellDunUnRequestedState);
+                            }
+                        }
+                        break;
+                    case CMD_CELL_DUN_DISABLED:
+                        transitionTo(mInitialState);
+                        break;
+                    case CMD_CELL_DUN_RENEW:
+                        b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
+                        IConnectivityManager cservice = IConnectivityManager.Stub.asInterface(b);
+                        try {
+                            cservice.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE,
+                                    Phone.FEATURE_ENABLE_DUN, new Binder());
+                        } catch (Exception e) {
+                        }
+                        sendMessageDelayed(obtainMessage(CMD_CELL_DUN_RENEW), CELL_DUN_RENEW_MS);
+                        break;
+                    default:
+                        retValue = false;
+                        break;
+                }
+                return retValue;
+            }
+        }
+
+        class TetherModeAliveState extends HierarchicalState {
+            @Override
+            public void enter() {
+                for (Object o : mNotifyList) {
+                    TetherInterfaceSM sm = (TetherInterfaceSM)o;
+                    sm.sendMessage(sm.obtainMessage(TetherInterfaceSM.CMD_TETHER_MODE_ALIVE));
+                }
+            }
+            @Override
+            public boolean processMessage(Message message) {
+                Log.d(TAG, "TetherModeAliveState.processMessage what=" + message.what);
+                boolean retValue = true;
+                switch (message.what) {
+                    case CMD_TETHER_MODE_REQUESTED:
+                        TetherInterfaceSM who = (TetherInterfaceSM)message.obj;
+                        mNotifyList.add(who);
+                        who.sendMessage(who.obtainMessage(TetherInterfaceSM.CMD_TETHER_MODE_ALIVE));
+                        break;
+                    case CMD_TETHER_MODE_UNREQUESTED:
+                        who = (TetherInterfaceSM)message.obj;
+                        int index = mNotifyList.indexOf(who);
+                        if (index != -1) {
+                            mNotifyList.remove(index);
+                            if (mNotifyList.isEmpty()) {
+                                transitionTo(mCellDunUnRequestedState);
+                            }
+                        }
+                        break;
+                    case CMD_CELL_DUN_DISABLED:
+                        int size = mNotifyList.size();
+                        for (int i = 0; i < size; i++) {
+                            TetherInterfaceSM sm = (TetherInterfaceSM)mNotifyList.get(i);
+                            mNotifyList.remove(i);
+                            sm.sendMessage(sm.obtainMessage(
+                                    TetherInterfaceSM.CMD_TETHER_MODE_DEAD));
+                        }
+                        IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+                        INetworkManagementService service =
+                                INetworkManagementService.Stub.asInterface(b);
+                        try {
+                            service.stopTethering();
+                        } catch (Exception e) {
+                            transitionTo(mStopTetheringErrorState);
+                            break;
+                        }
+                        try {
+                            service.setIpForwardingEnabled(false);
+                        } catch (Exception e) {
+                            transitionTo(mSetIpForwardingDisabledErrorState);
+                            break;
+                        }
+                        transitionTo(mInitialState);
+                        break;
+                    default:
+                       retValue = false;
+                       break;
+                }
+                return retValue;
+            }
+        }
+
+        class CellDunUnRequestedState extends HierarchicalState {
+            @Override
+            public void enter() {
+                IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
+                IConnectivityManager service =
+                        IConnectivityManager.Stub.asInterface(b);
+                NetworkInfo dunInfo = null;
+                try {
+                    dunInfo = service.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_DUN);
+                } catch (Exception e) {}
+                if (dunInfo != null && !dunInfo.isConnectedOrConnecting()) {
+                    sendMessage(obtainMessage(CMD_CELL_DUN_DISABLED));
+                    return;
+                }
+                try {
+                    service.stopUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE,
+                            Phone.FEATURE_ENABLE_DUN);
+                } catch (Exception e) {}
+                Message m =  obtainMessage(CMD_CELL_DUN_TIMEOUT);
+                m.arg1 = ++mSequenceNumber;
+                // use a short timeout - this will often be a no-op and
+                // we just want this request to get into the queue before we
+                // try again.
+                sendMessageDelayed(m, CELL_DISABLE_DUN_TIMEOUT_MS);
+            }
+            @Override
+            public boolean processMessage(Message message) {
+                Log.d(TAG, "CellDunUnRequestedState.processMessage what=" + message.what);
+                boolean retValue = true;
+                switch (message.what) {
+                    case CMD_TETHER_MODE_REQUESTED:
+                    case CMD_TETHER_MODE_UNREQUESTED:
+                        deferMessage(message);
+                        break;
+                    case CMD_CELL_DUN_DISABLED:
+                        transitionTo(mInitialState);
+                        break;
+                    case CMD_CELL_DUN_TIMEOUT:
+                        // if we aren't using a sep apn, we won't get a disconnect broadcast..
+                        // just go back to initial after our short pause
+                        if (message.arg1 == mSequenceNumber) {
+                            transitionTo(mInitialState);
+                        }
+                        break;
+                    default:
+                        retValue = false;
+                        break;
+                }
+                return retValue;
+            }
+        }
+
+        class ErrorState extends HierarchicalState {
+            int mErrorNotification;
+            @Override
+            public boolean processMessage(Message message) {
+                boolean retValue = true;
+                switch (message.what) {
+                    case CMD_TETHER_MODE_REQUESTED:
+                        TetherInterfaceSM who = (TetherInterfaceSM)message.obj;
+                        who.sendMessage(who.obtainMessage(mErrorNotification));
+                        break;
+                    default:
+                       retValue = false;
+                }
+                return retValue;
+            }
+            void notify(int msgType) {
+                mErrorNotification = msgType;
+                for (Object o : mNotifyList) {
+                    TetherInterfaceSM sm = (TetherInterfaceSM)o;
+                    sm.sendMessage(sm.obtainMessage(msgType));
+                }
+            }
+
+        }
+        class CellDunErrorState extends ErrorState {
+            @Override
+            public void enter() {
+                Log.e(TAG, "Error trying to enable Cell DUN");
+                notify(TetherInterfaceSM.CMD_CELL_DUN_ERROR);
+            }
+        }
+
+        class SetIpForwardingEnabledErrorState extends ErrorState {
+            @Override
+            public void enter() {
+                Log.e(TAG, "Error in setIpForwardingEnabled");
+                notify(TetherInterfaceSM.CMD_IP_FORWARDING_ENABLE_ERROR);
+            }
+        }
+
+        class SetIpForwardingDisabledErrorState extends ErrorState {
+            @Override
+            public void enter() {
+                Log.e(TAG, "Error in setIpForwardingDisabled");
+                notify(TetherInterfaceSM.CMD_IP_FORWARDING_DISABLE_ERROR);
+            }
+        }
+
+        class StartTetheringErrorState extends ErrorState {
+            @Override
+            public void enter() {
+                Log.e(TAG, "Error in startTethering");
+                notify(TetherInterfaceSM.CMD_START_TETHERING_ERROR);
+                IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+                INetworkManagementService service =
+                        INetworkManagementService.Stub.asInterface(b);
+                try {
+                    service.setIpForwardingEnabled(false);
+                } catch (Exception e) {}
+            }
+        }
+
+        class StopTetheringErrorState extends ErrorState {
+            @Override
+            public void enter() {
+                Log.e(TAG, "Error in stopTethering");
+                notify(TetherInterfaceSM.CMD_STOP_TETHERING_ERROR);
+                IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+                INetworkManagementService service =
+                         INetworkManagementService.Stub.asInterface(b);
+                try {
+                    service.setIpForwardingEnabled(false);
+                } catch (Exception e) {}
+            }
+        }
+
+        class SetDnsForwardersErrorState extends ErrorState {
+            @Override
+            public void enter() {
+                Log.e(TAG, "Error in setDnsForwarders");
+                notify(TetherInterfaceSM.CMD_SET_DNS_FORWARDERS_ERROR);
+                IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+                INetworkManagementService service =
+                        INetworkManagementService.Stub.asInterface(b);
+                try {
+                    service.stopTethering();
+                } catch (Exception e) {}
+                try {
+                    service.setIpForwardingEnabled(false);
+                } catch (Exception e) {}
+            }
+        }
+    }
+
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) {
+            pw.println("Permission Denial: can't dump ConnectivityService.Tether " +
+                    "from from pid=" + Binder.getCallingPid() + ", uid=" +
+                    Binder.getCallingUid());
+                    return;
+        }
+
+        pw.println();
+        pw.println("Tether state:");
+        synchronized (mIfaces) {
+            for (Object o : mIfaces.values()) {
+                pw.println(" "+o.toString());
+            }
+        }
+        pw.println();
+        return;
     }
 }