diff options
-rw-r--r-- | core/java/android/net/ConnectivityManager.java | 45 | ||||
-rw-r--r-- | core/java/android/net/IConnectivityManager.aidl | 6 | ||||
-rw-r--r-- | core/java/android/provider/Settings.java | 6 | ||||
-rw-r--r-- | core/java/com/android/internal/app/TetherActivity.java | 62 | ||||
-rw-r--r-- | core/java/com/android/internal/util/HierarchicalStateMachine.java | 2 | ||||
-rw-r--r-- | core/res/res/values/config.xml | 16 | ||||
-rw-r--r-- | services/java/com/android/server/ConnectivityService.java | 46 | ||||
-rw-r--r-- | services/java/com/android/server/connectivity/Tethering.java | 1355 |
8 files changed, 1331 insertions, 207 deletions
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 @@ public class ConnectivityManager /** * @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 @@ public class ConnectivityManager 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..508e9c3 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -55,7 +55,13 @@ interface IConnectivityManager 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 final class Settings { 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.widget.Toast; 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 @@ public class TetherActivity extends AlertActivity implements } }; - private boolean mWantTethering; - @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -61,17 +62,18 @@ public class TetherActivity extends AlertActivity implements // 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 @@ public class TetherActivity extends AlertActivity implements * {@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 @@ public class TetherActivity extends AlertActivity implements } 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 @@ public class HierarchicalStateMachine { * @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 @@ public class ConnectivityService extends IConnectivityManager.Stub { "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 @@ public class ConnectivityService extends IConnectivityManager.Stub { pw.println(requester.toString()); } pw.println(); + + mTethering.dump(fd, pw, args); } // must be stateless - things change under us. @@ -1386,24 +1394,54 @@ public class ConnectivityService extends IConnectivityManager.Stub { // 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.ContentResolver; 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 @@ public class Tethering extends INetworkManagementEventObserver.Stub { 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 @@ public class Tethering extends INetworkManagementEventObserver.Stub { 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"); - return; + 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 (mAvailableIfaces.contains(iface)) { - Log.e(TAG, "available iface (" + iface + ") readded, ignoring"); + if (found == false) { + Log.d(TAG, iface + " is not a tetherable iface, ignoring"); return; } - mAvailableIfaces.add(iface); + 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(); + } 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)) { - Log.e(TAG, "Tried to Tether an unavailable iface :" + iface + ", ignoring"); - return false; + TetherInterfaceSM sm = null; + synchronized (mIfaces) { + sm = mIfaces.get(iface); } - if (mActiveIfaces.contains(iface)) { - Log.e(TAG, "Tried to Tether an already Tethered iface :" + iface + ", ignoring"); + if (sm == null) { + Log.e(TAG, "Tried to Tether an unknown 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) {} - } + if (sm.isErrored()) { + Log.e(TAG, "Tried to Tether to an errored iface :" + iface + ", ignoring"); 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) {} - } + if (!sm.isAvailable()) { + Log.e(TAG, "Tried to Tether an unavailable iface :" + iface + ", ignoring"); 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; } + sm.sendMessage(sm.obtainMessage(TetherInterfaceSM.CMD_TETHER_UNREQUESTED)); + return true; + } - 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); - } + private void sendTetherStateChangedBroadcast() { + IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE); + IConnectivityManager service = IConnectivityManager.Stub.asInterface(b); try { - service.untetherInterface(iface); - } catch (Exception e) { - Log.e(TAG, "Error untethering " + iface + ", :" + e); + if (!service.isTetheringSupported()) return; + } catch (RemoteException e) { + return; } - 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); + 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); + } + } } } - sendTetherStateChangedBroadcast(); - Log.d(TAG, "Untethered " + iface); - return true; - } - - private void sendTetherStateChangedBroadcast() { 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); - - // 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(); - } - } else if (mActiveIfaces.size() > 0) { - showTetheredNotification(); - } else { - clearNotification(); + broadcast.putStringArrayListExtra(ConnectivityManager.EXTRA_AVAILABLE_TETHER, + availableList); + broadcast.putStringArrayListExtra(ConnectivityManager.EXTRA_ACTIVE_TETHER, activeList); + broadcast.putStringArrayListExtra(ConnectivityManager.EXTRA_ERRORED_TETHER, + erroredList); + mContext.sendStickyBroadcast(broadcast); + + // 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; + } + } + } + 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 @@ public class Tethering extends INetworkManagementEventObserver.Stub { if (notificationManager == null) { return; } - Intent intent = new Intent(); intent.setClass(mContext, com.android.internal.app.TetherActivity.class); @@ -367,18 +417,32 @@ public class Tethering extends INetworkManagementEventObserver.Stub { -// 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 class Tethering extends INetworkManagementEventObserver.Stub { } } - 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); + public String[] getTetherableUsbRegexs() { + return mTetherableUsbRegexs; + } + + 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); + } + } + } + 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) {} + } } - return result; } - 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 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()); + } } - return result; + pw.println(); + return; } } |