Extract TetherInterfaceSM to its own class.

( cherry-pick of e3f93b02bdfde6fffd2bcbb2e1dc0785ce9f8d5a )

Attempt to keep all existing logic in place, except:

 + Marked a constructor as public, rather than default visible.
 + Added TAG, DBG, VDBG, and decoder ringer statics.
 + Moved static constants related to USB IPs into TetherInterfaceSM.

Bug: 28833951
Test: WiFi Tethering works on angler.

Change-Id: Id961220a9045832354cfe7381e5e9c0d8f54bf90
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index ac51a9e..b6eec77 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -71,6 +71,7 @@
 import com.android.internal.util.StateMachine;
 import com.android.server.IoThread;
 import com.android.server.connectivity.tethering.IControlsTethering;
+import com.android.server.connectivity.tethering.TetherInterfaceSM;
 import com.android.server.net.BaseNetworkObserver;
 
 import java.io.FileDescriptor;
@@ -135,9 +136,6 @@
     private static final ComponentName TETHER_SERVICE = ComponentName.unflattenFromString(Resources
             .getSystem().getString(com.android.internal.R.string.config_wifi_tether_enable));
 
-    private static final String USB_NEAR_IFACE_ADDR      = "192.168.42.129";
-    private static final int USB_PREFIX_LENGTH        = 24;
-
     // USB is  192.168.42.1 and 255.255.255.0
     // Wifi is 192.168.43.1 and 255.255.255.0
     // BT is limited to max default of 5 connections. 192.168.44.1 to 192.168.48.1
@@ -981,442 +979,6 @@
     }
 
     /**
-     * @hide
-     *
-     * Tracks the eligibility of a given network interface for tethering.
-     */
-    public static class TetherInterfaceSM extends StateMachine {
-        private static final int BASE_IFACE              = Protocol.BASE_TETHERING + 100;
-        // notification from the master SM that it's not in tether mode
-        static final int CMD_TETHER_MODE_DEAD            = BASE_IFACE + 1;
-        // request from the user that it wants to tether
-        static final int CMD_TETHER_REQUESTED            = BASE_IFACE + 2;
-        // request from the user that it wants to untether
-        static final int CMD_TETHER_UNREQUESTED          = BASE_IFACE + 3;
-        // notification that this interface is down
-        static final int CMD_INTERFACE_DOWN              = BASE_IFACE + 4;
-        // notification that this interface is up
-        static final int CMD_INTERFACE_UP                = BASE_IFACE + 5;
-        // notification from the master SM that it had an error turning on cellular dun
-        static final int CMD_CELL_DUN_ERROR              = BASE_IFACE + 6;
-        // notification from the master SM that it had trouble enabling IP Forwarding
-        static final int CMD_IP_FORWARDING_ENABLE_ERROR  = BASE_IFACE + 7;
-        // notification from the master SM that it had trouble disabling IP Forwarding
-        static final int CMD_IP_FORWARDING_DISABLE_ERROR = BASE_IFACE + 8;
-        // notification from the master SM that it had trouble starting tethering
-        static final int CMD_START_TETHERING_ERROR       = BASE_IFACE + 9;
-        // notification from the master SM that it had trouble stopping tethering
-        static final int CMD_STOP_TETHERING_ERROR        = BASE_IFACE + 10;
-        // notification from the master SM that it had trouble setting the DNS forwarders
-        static final int CMD_SET_DNS_FORWARDERS_ERROR    = BASE_IFACE + 11;
-        // the upstream connection has changed
-        static final int CMD_TETHER_CONNECTION_CHANGED   = BASE_IFACE + 12;
-
-        private final State mInitialState;
-        private final State mStartingState;
-        private final State mTetheredState;
-        private final State mUnavailableState;
-
-        private final INetworkManagementService mNMService;
-        private final INetworkStatsService mStatsService;
-        private final IControlsTethering mTetherController;
-
-        private final boolean mUsb;
-        private final String mIfaceName;
-
-        private final Object mMutex;  // Protects the fields below.
-        private boolean mAvailable;
-        private boolean mTethered;
-        private int mLastError;
-        private String mMyUpstreamIfaceName;  // may change over time
-
-        TetherInterfaceSM(String ifaceName, Looper looper, boolean usb, Object mutex,
-                        INetworkManagementService nMService, INetworkStatsService statsService,
-                        IControlsTethering tetherController) {
-            super(ifaceName, looper);
-            mNMService = nMService;
-            mStatsService = statsService;
-            mTetherController = tetherController;
-            mIfaceName = ifaceName;
-            mUsb = usb;
-            mMutex = mutex;
-            setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR);
-
-            mInitialState = new InitialState();
-            addState(mInitialState);
-            mStartingState = new StartingState();
-            addState(mStartingState);
-            mTetheredState = new TetheredState();
-            addState(mTetheredState);
-            mUnavailableState = new UnavailableState();
-            addState(mUnavailableState);
-
-            setInitialState(mInitialState);
-        }
-
-        @Override
-        public String toString() {
-            String res = new String();
-            res += mIfaceName + " - ";
-            IState current = getCurrentState();
-            if (current == mInitialState) res += "InitialState";
-            if (current == mStartingState) res += "StartingState";
-            if (current == mTetheredState) res += "TetheredState";
-            if (current == mUnavailableState) res += "UnavailableState";
-            if (isAvailable()) res += " - Available";
-            if (isTethered()) res += " - Tethered";
-            res += " - lastError =" + getLastError();
-            return res;
-        }
-
-        public int getLastError() {
-            synchronized (mMutex) {
-                return mLastError;
-            }
-        }
-
-        private void setLastError(int error) {
-            synchronized (mMutex) {
-                mLastError = error;
-
-                if (isErrored()) {
-                    if (mUsb) {
-                        // note everything's been unwound by this point so nothing to do on
-                        // further error..
-                        configureUsbIface(false, mIfaceName);
-                    }
-                }
-            }
-        }
-
-        public boolean isAvailable() {
-            synchronized (mMutex) {
-                return mAvailable;
-            }
-        }
-
-        private void setAvailable(boolean available) {
-            synchronized (mMutex) {
-                mAvailable = available;
-            }
-        }
-
-        public boolean isTethered() {
-            synchronized (mMutex) {
-                return mTethered;
-            }
-        }
-
-        private void setTethered(boolean tethered) {
-            synchronized (mMutex) {
-                mTethered = tethered;
-            }
-        }
-
-        public boolean isErrored() {
-            synchronized (mMutex) {
-                return (mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR);
-            }
-        }
-
-        // configured when we start tethering and unconfig'd on error or conclusion
-        private boolean configureUsbIface(boolean enabled, String iface) {
-            if (VDBG) Log.d(TAG, "configureUsbIface(" + enabled + ")");
-
-            InterfaceConfiguration ifcg = null;
-            try {
-                ifcg = mNMService.getInterfaceConfig(iface);
-                if (ifcg != null) {
-                    InetAddress addr = NetworkUtils.numericToInetAddress(USB_NEAR_IFACE_ADDR);
-                    ifcg.setLinkAddress(new LinkAddress(addr, USB_PREFIX_LENGTH));
-                    if (enabled) {
-                        ifcg.setInterfaceUp();
-                    } else {
-                        ifcg.setInterfaceDown();
-                    }
-                    ifcg.clearFlag("running");
-                    mNMService.setInterfaceConfig(iface, ifcg);
-                }
-            } catch (Exception e) {
-                Log.e(TAG, "Error configuring interface " + iface, e);
-                return false;
-            }
-
-            return true;
-        }
-
-        private void maybeLogMessage(State state, int what) {
-            if (DBG) {
-                Log.d(TAG, state.getName() + " got " +
-                        sMagicDecoderRing.get(what, Integer.toString(what)));
-            }
-        }
-
-        class InitialState extends State {
-            @Override
-            public void enter() {
-                setAvailable(true);
-                setTethered(false);
-                mTetherController.sendTetherStateChangedBroadcast();
-            }
-
-            @Override
-            public boolean processMessage(Message message) {
-                maybeLogMessage(this, message.what);
-                boolean retValue = true;
-                switch (message.what) {
-                    case CMD_TETHER_REQUESTED:
-                        setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR);
-                        mTetherController.notifyInterfaceTetheringReadiness(true, TetherInterfaceSM.this);
-                        transitionTo(mStartingState);
-                        break;
-                    case CMD_INTERFACE_DOWN:
-                        transitionTo(mUnavailableState);
-                        break;
-                    default:
-                        retValue = false;
-                        break;
-                }
-                return retValue;
-            }
-        }
-
-        class StartingState extends State {
-            @Override
-            public void enter() {
-                setAvailable(false);
-                if (mUsb) {
-                    if (!configureUsbIface(true, mIfaceName)) {
-                        mTetherController.notifyInterfaceTetheringReadiness(false, TetherInterfaceSM.this);
-                        setLastError(ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR);
-
-                        transitionTo(mInitialState);
-                        return;
-                    }
-                }
-                mTetherController.sendTetherStateChangedBroadcast();
-
-                // Skipping StartingState
-                transitionTo(mTetheredState);
-            }
-            @Override
-            public boolean processMessage(Message message) {
-                maybeLogMessage(this, message.what);
-                boolean retValue = true;
-                switch (message.what) {
-                    // maybe a parent class?
-                    case CMD_TETHER_UNREQUESTED:
-                        mTetherController.notifyInterfaceTetheringReadiness(false, TetherInterfaceSM.this);
-                        if (mUsb) {
-                            if (!configureUsbIface(false, mIfaceName)) {
-                                setLastErrorAndTransitionToInitialState(
-                                    ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR);
-                                break;
-                            }
-                        }
-                        transitionTo(mInitialState);
-                        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:
-                        setLastErrorAndTransitionToInitialState(
-                                ConnectivityManager.TETHER_ERROR_MASTER_ERROR);
-                        break;
-                    case CMD_INTERFACE_DOWN:
-                        mTetherController.notifyInterfaceTetheringReadiness(false, TetherInterfaceSM.this);
-                        transitionTo(mUnavailableState);
-                        break;
-                    default:
-                        retValue = false;
-                }
-                return retValue;
-            }
-        }
-
-        class TetheredState extends State {
-            @Override
-            public void enter() {
-                try {
-                    mNMService.tetherInterface(mIfaceName);
-                } catch (Exception e) {
-                    Log.e(TAG, "Error Tethering: " + e.toString());
-                    setLastError(ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR);
-
-                    try {
-                        mNMService.untetherInterface(mIfaceName);
-                    } catch (Exception ee) {
-                        Log.e(TAG, "Error untethering after failure!" + ee.toString());
-                    }
-                    transitionTo(mInitialState);
-                    return;
-                }
-                if (DBG) Log.d(TAG, "Tethered " + mIfaceName);
-                setAvailable(false);
-                setTethered(true);
-                mTetherController.sendTetherStateChangedBroadcast();
-            }
-
-            private void cleanupUpstream() {
-                if (mMyUpstreamIfaceName != null) {
-                    // note that we don't care about errors here.
-                    // sometimes interfaces are gone before we get
-                    // to remove their rules, which generates errors.
-                    // just do the best we can.
-                    try {
-                        // about to tear down NAT; gather remaining statistics
-                        mStatsService.forceUpdate();
-                    } catch (Exception e) {
-                        if (VDBG) Log.e(TAG, "Exception in forceUpdate: " + e.toString());
-                    }
-                    try {
-                        mNMService.stopInterfaceForwarding(mIfaceName, mMyUpstreamIfaceName);
-                    } catch (Exception e) {
-                        if (VDBG) Log.e(
-                                TAG, "Exception in removeInterfaceForward: " + e.toString());
-                    }
-                    try {
-                        mNMService.disableNat(mIfaceName, mMyUpstreamIfaceName);
-                    } catch (Exception e) {
-                        if (VDBG) Log.e(TAG, "Exception in disableNat: " + e.toString());
-                    }
-                    mMyUpstreamIfaceName = null;
-                }
-                return;
-            }
-
-            @Override
-            public boolean processMessage(Message message) {
-                maybeLogMessage(this, message.what);
-                boolean retValue = true;
-                boolean error = false;
-                switch (message.what) {
-                    case CMD_TETHER_UNREQUESTED:
-                    case CMD_INTERFACE_DOWN:
-                        cleanupUpstream();
-                        try {
-                            mNMService.untetherInterface(mIfaceName);
-                        } catch (Exception e) {
-                            setLastErrorAndTransitionToInitialState(
-                                    ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR);
-                            break;
-                        }
-                        mTetherController.notifyInterfaceTetheringReadiness(false, TetherInterfaceSM.this);
-                        if (message.what == CMD_TETHER_UNREQUESTED) {
-                            if (mUsb) {
-                                if (!configureUsbIface(false, mIfaceName)) {
-                                    setLastError(
-                                            ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR);
-                                }
-                            }
-                            transitionTo(mInitialState);
-                        } else if (message.what == CMD_INTERFACE_DOWN) {
-                            transitionTo(mUnavailableState);
-                        }
-                        if (DBG) Log.d(TAG, "Untethered " + mIfaceName);
-                        break;
-                    case CMD_TETHER_CONNECTION_CHANGED:
-                        String newUpstreamIfaceName = (String)(message.obj);
-                        if ((mMyUpstreamIfaceName == null && newUpstreamIfaceName == null) ||
-                                (mMyUpstreamIfaceName != null &&
-                                mMyUpstreamIfaceName.equals(newUpstreamIfaceName))) {
-                            if (VDBG) Log.d(TAG, "Connection changed noop - dropping");
-                            break;
-                        }
-                        cleanupUpstream();
-                        if (newUpstreamIfaceName != null) {
-                            try {
-                                mNMService.enableNat(mIfaceName, newUpstreamIfaceName);
-                                mNMService.startInterfaceForwarding(mIfaceName,
-                                        newUpstreamIfaceName);
-                            } catch (Exception e) {
-                                Log.e(TAG, "Exception enabling Nat: " + e.toString());
-                                try {
-                                    mNMService.disableNat(mIfaceName, newUpstreamIfaceName);
-                                } catch (Exception ee) {}
-                                try {
-                                    mNMService.untetherInterface(mIfaceName);
-                                } catch (Exception ee) {}
-
-                                setLastError(ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR);
-                                transitionTo(mInitialState);
-                                return true;
-                            }
-                        }
-                        mMyUpstreamIfaceName = newUpstreamIfaceName;
-                        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:
-                        cleanupUpstream();
-                        try {
-                            mNMService.untetherInterface(mIfaceName);
-                        } catch (Exception e) {
-                            setLastErrorAndTransitionToInitialState(
-                                    ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR);
-                            break;
-                        }
-                        if (error) {
-                            setLastErrorAndTransitionToInitialState(
-                                    ConnectivityManager.TETHER_ERROR_MASTER_ERROR);
-                            break;
-                        }
-                        if (DBG) Log.d(TAG, "Tether lost upstream connection " + mIfaceName);
-                        mTetherController.sendTetherStateChangedBroadcast();
-                        if (mUsb) {
-                            if (!configureUsbIface(false, mIfaceName)) {
-                                setLastError(ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR);
-                            }
-                        }
-                        transitionTo(mInitialState);
-                        break;
-                    default:
-                        retValue = false;
-                        break;
-                }
-                return retValue;
-            }
-        }
-
-        class UnavailableState extends State {
-            @Override
-            public void enter() {
-                setAvailable(false);
-                setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR);
-                setTethered(false);
-                mTetherController.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;
-            }
-        }
-
-        void setLastErrorAndTransitionToInitialState(int error) {
-            setLastError(error);
-            transitionTo(mInitialState);
-        }
-
-    }
-
-    /**
      * A NetworkCallback class that relays information of interest to the
      * tethering master state machine thread for subsequent processing.
      */
diff --git a/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java b/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java
index 5eed26c..9f4efff 100644
--- a/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java
+++ b/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java
@@ -16,8 +16,6 @@
 
 package com.android.server.connectivity.tethering;
 
-import com.android.server.connectivity.Tethering;
-
 /**
  * @hide
  *
@@ -25,5 +23,5 @@
  */
 public interface IControlsTethering {
     void sendTetherStateChangedBroadcast();
-    void notifyInterfaceTetheringReadiness(boolean isReady, Tethering.TetherInterfaceSM who);
+    void notifyInterfaceTetheringReadiness(boolean isReady, TetherInterfaceSM who);
 }
\ No newline at end of file
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceSM.java b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceSM.java
new file mode 100644
index 0000000..e7f32a9
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceSM.java
@@ -0,0 +1,483 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.tethering;
+
+import android.net.ConnectivityManager;
+import android.net.INetworkStatsService;
+import android.net.InterfaceConfiguration;
+import android.net.LinkAddress;
+import android.net.NetworkUtils;
+import android.os.INetworkManagementService;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.util.IState;
+import com.android.internal.util.MessageUtils;
+import com.android.internal.util.Protocol;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+
+import java.net.InetAddress;
+
+/**
+ * @hide
+ *
+ * Tracks the eligibility of a given network interface for tethering.
+ */
+public class TetherInterfaceSM extends StateMachine {
+    private static final String USB_NEAR_IFACE_ADDR = "192.168.42.129";
+    private static final int USB_PREFIX_LENGTH = 24;
+
+    private final static String TAG = "TetherInterfaceSM";
+    private final static boolean DBG = false;
+    private final static boolean VDBG = false;
+    private static final Class[] messageClasses = {
+            TetherInterfaceSM.class
+    };
+    private static final SparseArray<String> sMagicDecoderRing =
+            MessageUtils.findMessageNames(messageClasses);
+
+    private static final int BASE_IFACE              = Protocol.BASE_TETHERING + 100;
+    // notification from the master SM that it's not in tether mode
+    public static final int CMD_TETHER_MODE_DEAD            = BASE_IFACE + 1;
+    // request from the user that it wants to tether
+    public static final int CMD_TETHER_REQUESTED            = BASE_IFACE + 2;
+    // request from the user that it wants to untether
+    public static final int CMD_TETHER_UNREQUESTED          = BASE_IFACE + 3;
+    // notification that this interface is down
+    public static final int CMD_INTERFACE_DOWN              = BASE_IFACE + 4;
+    // notification that this interface is up
+    public static final int CMD_INTERFACE_UP                = BASE_IFACE + 5;
+    // notification from the master SM that it had an error turning on cellular dun
+    public static final int CMD_CELL_DUN_ERROR              = BASE_IFACE + 6;
+    // notification from the master SM that it had trouble enabling IP Forwarding
+    public static final int CMD_IP_FORWARDING_ENABLE_ERROR  = BASE_IFACE + 7;
+    // notification from the master SM that it had trouble disabling IP Forwarding
+    public static final int CMD_IP_FORWARDING_DISABLE_ERROR = BASE_IFACE + 8;
+    // notification from the master SM that it had trouble starting tethering
+    public static final int CMD_START_TETHERING_ERROR       = BASE_IFACE + 9;
+    // notification from the master SM that it had trouble stopping tethering
+    public static final int CMD_STOP_TETHERING_ERROR        = BASE_IFACE + 10;
+    // notification from the master SM that it had trouble setting the DNS forwarders
+    public static final int CMD_SET_DNS_FORWARDERS_ERROR    = BASE_IFACE + 11;
+    // the upstream connection has changed
+    public static final int CMD_TETHER_CONNECTION_CHANGED   = BASE_IFACE + 12;
+
+    private final State mInitialState;
+    private final State mStartingState;
+    private final State mTetheredState;
+    private final State mUnavailableState;
+
+    private final INetworkManagementService mNMService;
+    private final INetworkStatsService mStatsService;
+    private final IControlsTethering mTetherController;
+
+    private final boolean mUsb;
+    private final String mIfaceName;
+
+    private final Object mMutex;  // Protects the fields below.
+    private boolean mAvailable;
+    private boolean mTethered;
+    private int mLastError;
+    private String mMyUpstreamIfaceName;  // may change over time
+
+    public TetherInterfaceSM(String ifaceName, Looper looper, boolean usb, Object mutex,
+                    INetworkManagementService nMService, INetworkStatsService statsService,
+                    IControlsTethering tetherController) {
+        super(ifaceName, looper);
+        mNMService = nMService;
+        mStatsService = statsService;
+        mTetherController = tetherController;
+        mIfaceName = ifaceName;
+        mUsb = usb;
+        mMutex = mutex;
+        setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR);
+
+        mInitialState = new InitialState();
+        addState(mInitialState);
+        mStartingState = new StartingState();
+        addState(mStartingState);
+        mTetheredState = new TetheredState();
+        addState(mTetheredState);
+        mUnavailableState = new UnavailableState();
+        addState(mUnavailableState);
+
+        setInitialState(mInitialState);
+    }
+
+    @Override
+    public String toString() {
+        String res = new String();
+        res += mIfaceName + " - ";
+        IState current = getCurrentState();
+        if (current == mInitialState) res += "InitialState";
+        if (current == mStartingState) res += "StartingState";
+        if (current == mTetheredState) res += "TetheredState";
+        if (current == mUnavailableState) res += "UnavailableState";
+        if (isAvailable()) res += " - Available";
+        if (isTethered()) res += " - Tethered";
+        res += " - lastError =" + getLastError();
+        return res;
+    }
+
+    public int getLastError() {
+        synchronized (mMutex) {
+            return mLastError;
+        }
+    }
+
+    private void setLastError(int error) {
+        synchronized (mMutex) {
+            mLastError = error;
+
+            if (isErrored()) {
+                if (mUsb) {
+                    // note everything's been unwound by this point so nothing to do on
+                    // further error..
+                    configureUsbIface(false, mIfaceName);
+                }
+            }
+        }
+    }
+
+    public boolean isAvailable() {
+        synchronized (mMutex) {
+            return mAvailable;
+        }
+    }
+
+    private void setAvailable(boolean available) {
+        synchronized (mMutex) {
+            mAvailable = available;
+        }
+    }
+
+    public boolean isTethered() {
+        synchronized (mMutex) {
+            return mTethered;
+        }
+    }
+
+    private void setTethered(boolean tethered) {
+        synchronized (mMutex) {
+            mTethered = tethered;
+        }
+    }
+
+    public boolean isErrored() {
+        synchronized (mMutex) {
+            return (mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR);
+        }
+    }
+
+    // configured when we start tethering and unconfig'd on error or conclusion
+    private boolean configureUsbIface(boolean enabled, String iface) {
+        if (VDBG) Log.d(TAG, "configureUsbIface(" + enabled + ")");
+
+        InterfaceConfiguration ifcg = null;
+        try {
+            ifcg = mNMService.getInterfaceConfig(iface);
+            if (ifcg != null) {
+                InetAddress addr = NetworkUtils.numericToInetAddress(USB_NEAR_IFACE_ADDR);
+                ifcg.setLinkAddress(new LinkAddress(addr, USB_PREFIX_LENGTH));
+                if (enabled) {
+                    ifcg.setInterfaceUp();
+                } else {
+                    ifcg.setInterfaceDown();
+                }
+                ifcg.clearFlag("running");
+                mNMService.setInterfaceConfig(iface, ifcg);
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Error configuring interface " + iface, e);
+            return false;
+        }
+
+        return true;
+    }
+
+    private void maybeLogMessage(State state, int what) {
+        if (DBG) {
+            Log.d(TAG, state.getName() + " got " +
+                    sMagicDecoderRing.get(what, Integer.toString(what)));
+        }
+    }
+
+    class InitialState extends State {
+        @Override
+        public void enter() {
+            setAvailable(true);
+            setTethered(false);
+            mTetherController.sendTetherStateChangedBroadcast();
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            maybeLogMessage(this, message.what);
+            boolean retValue = true;
+            switch (message.what) {
+                case CMD_TETHER_REQUESTED:
+                    setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR);
+                    mTetherController.notifyInterfaceTetheringReadiness(true, TetherInterfaceSM.this);
+                    transitionTo(mStartingState);
+                    break;
+                case CMD_INTERFACE_DOWN:
+                    transitionTo(mUnavailableState);
+                    break;
+                default:
+                    retValue = false;
+                    break;
+            }
+            return retValue;
+        }
+    }
+
+    class StartingState extends State {
+        @Override
+        public void enter() {
+            setAvailable(false);
+            if (mUsb) {
+                if (!configureUsbIface(true, mIfaceName)) {
+                    mTetherController.notifyInterfaceTetheringReadiness(false, TetherInterfaceSM.this);
+                    setLastError(ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR);
+
+                    transitionTo(mInitialState);
+                    return;
+                }
+            }
+            mTetherController.sendTetherStateChangedBroadcast();
+
+            // Skipping StartingState
+            transitionTo(mTetheredState);
+        }
+        @Override
+        public boolean processMessage(Message message) {
+            maybeLogMessage(this, message.what);
+            boolean retValue = true;
+            switch (message.what) {
+                // maybe a parent class?
+                case CMD_TETHER_UNREQUESTED:
+                    mTetherController.notifyInterfaceTetheringReadiness(false, TetherInterfaceSM.this);
+                    if (mUsb) {
+                        if (!configureUsbIface(false, mIfaceName)) {
+                            setLastErrorAndTransitionToInitialState(
+                                ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR);
+                            break;
+                        }
+                    }
+                    transitionTo(mInitialState);
+                    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:
+                    setLastErrorAndTransitionToInitialState(
+                            ConnectivityManager.TETHER_ERROR_MASTER_ERROR);
+                    break;
+                case CMD_INTERFACE_DOWN:
+                    mTetherController.notifyInterfaceTetheringReadiness(false, TetherInterfaceSM.this);
+                    transitionTo(mUnavailableState);
+                    break;
+                default:
+                    retValue = false;
+            }
+            return retValue;
+        }
+    }
+
+    class TetheredState extends State {
+        @Override
+        public void enter() {
+            try {
+                mNMService.tetherInterface(mIfaceName);
+            } catch (Exception e) {
+                Log.e(TAG, "Error Tethering: " + e.toString());
+                setLastError(ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR);
+
+                try {
+                    mNMService.untetherInterface(mIfaceName);
+                } catch (Exception ee) {
+                    Log.e(TAG, "Error untethering after failure!" + ee.toString());
+                }
+                transitionTo(mInitialState);
+                return;
+            }
+            if (DBG) Log.d(TAG, "Tethered " + mIfaceName);
+            setAvailable(false);
+            setTethered(true);
+            mTetherController.sendTetherStateChangedBroadcast();
+        }
+
+        private void cleanupUpstream() {
+            if (mMyUpstreamIfaceName != null) {
+                // note that we don't care about errors here.
+                // sometimes interfaces are gone before we get
+                // to remove their rules, which generates errors.
+                // just do the best we can.
+                try {
+                    // about to tear down NAT; gather remaining statistics
+                    mStatsService.forceUpdate();
+                } catch (Exception e) {
+                    if (VDBG) Log.e(TAG, "Exception in forceUpdate: " + e.toString());
+                }
+                try {
+                    mNMService.stopInterfaceForwarding(mIfaceName, mMyUpstreamIfaceName);
+                } catch (Exception e) {
+                    if (VDBG) Log.e(
+                            TAG, "Exception in removeInterfaceForward: " + e.toString());
+                }
+                try {
+                    mNMService.disableNat(mIfaceName, mMyUpstreamIfaceName);
+                } catch (Exception e) {
+                    if (VDBG) Log.e(TAG, "Exception in disableNat: " + e.toString());
+                }
+                mMyUpstreamIfaceName = null;
+            }
+            return;
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            maybeLogMessage(this, message.what);
+            boolean retValue = true;
+            boolean error = false;
+            switch (message.what) {
+                case CMD_TETHER_UNREQUESTED:
+                case CMD_INTERFACE_DOWN:
+                    cleanupUpstream();
+                    try {
+                        mNMService.untetherInterface(mIfaceName);
+                    } catch (Exception e) {
+                        setLastErrorAndTransitionToInitialState(
+                                ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR);
+                        break;
+                    }
+                    mTetherController.notifyInterfaceTetheringReadiness(false, TetherInterfaceSM.this);
+                    if (message.what == CMD_TETHER_UNREQUESTED) {
+                        if (mUsb) {
+                            if (!configureUsbIface(false, mIfaceName)) {
+                                setLastError(
+                                        ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR);
+                            }
+                        }
+                        transitionTo(mInitialState);
+                    } else if (message.what == CMD_INTERFACE_DOWN) {
+                        transitionTo(mUnavailableState);
+                    }
+                    if (DBG) Log.d(TAG, "Untethered " + mIfaceName);
+                    break;
+                case CMD_TETHER_CONNECTION_CHANGED:
+                    String newUpstreamIfaceName = (String)(message.obj);
+                    if ((mMyUpstreamIfaceName == null && newUpstreamIfaceName == null) ||
+                            (mMyUpstreamIfaceName != null &&
+                            mMyUpstreamIfaceName.equals(newUpstreamIfaceName))) {
+                        if (VDBG) Log.d(TAG, "Connection changed noop - dropping");
+                        break;
+                    }
+                    cleanupUpstream();
+                    if (newUpstreamIfaceName != null) {
+                        try {
+                            mNMService.enableNat(mIfaceName, newUpstreamIfaceName);
+                            mNMService.startInterfaceForwarding(mIfaceName,
+                                    newUpstreamIfaceName);
+                        } catch (Exception e) {
+                            Log.e(TAG, "Exception enabling Nat: " + e.toString());
+                            try {
+                                mNMService.disableNat(mIfaceName, newUpstreamIfaceName);
+                            } catch (Exception ee) {}
+                            try {
+                                mNMService.untetherInterface(mIfaceName);
+                            } catch (Exception ee) {}
+
+                            setLastError(ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR);
+                            transitionTo(mInitialState);
+                            return true;
+                        }
+                    }
+                    mMyUpstreamIfaceName = newUpstreamIfaceName;
+                    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:
+                    cleanupUpstream();
+                    try {
+                        mNMService.untetherInterface(mIfaceName);
+                    } catch (Exception e) {
+                        setLastErrorAndTransitionToInitialState(
+                                ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR);
+                        break;
+                    }
+                    if (error) {
+                        setLastErrorAndTransitionToInitialState(
+                                ConnectivityManager.TETHER_ERROR_MASTER_ERROR);
+                        break;
+                    }
+                    if (DBG) Log.d(TAG, "Tether lost upstream connection " + mIfaceName);
+                    mTetherController.sendTetherStateChangedBroadcast();
+                    if (mUsb) {
+                        if (!configureUsbIface(false, mIfaceName)) {
+                            setLastError(ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR);
+                        }
+                    }
+                    transitionTo(mInitialState);
+                    break;
+                default:
+                    retValue = false;
+                    break;
+            }
+            return retValue;
+        }
+    }
+
+    class UnavailableState extends State {
+        @Override
+        public void enter() {
+            setAvailable(false);
+            setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR);
+            setTethered(false);
+            mTetherController.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;
+        }
+    }
+
+    void setLastErrorAndTransitionToInitialState(int error) {
+        setLastError(error);
+        transitionTo(mInitialState);
+    }
+}