summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/java/android/net/ConnectivityManager.java45
-rw-r--r--core/java/android/net/IConnectivityManager.aidl6
-rw-r--r--core/java/android/provider/Settings.java6
-rw-r--r--core/java/com/android/internal/app/TetherActivity.java62
-rw-r--r--core/java/com/android/internal/util/HierarchicalStateMachine.java2
-rw-r--r--core/res/res/values/config.xml16
-rw-r--r--services/java/com/android/server/ConnectivityService.java46
-rw-r--r--services/java/com/android/server/connectivity/Tethering.java1355
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;
}
}