diff options
-rw-r--r-- | core/java/android/net/ConnectivityManager.java | 62 | ||||
-rw-r--r-- | core/java/android/net/IConnectivityManager.aidl | 8 | ||||
-rw-r--r-- | core/java/android/os/INetworkManagementService.aidl | 3 | ||||
-rw-r--r-- | core/java/android/provider/Settings.java | 7 | ||||
-rw-r--r-- | core/java/com/android/internal/app/TetherActivity.java | 151 | ||||
-rw-r--r-- | core/res/AndroidManifest.xml | 4 | ||||
-rwxr-xr-x | core/res/res/drawable-hdpi/stat_sys_tether_active.png | bin | 0 -> 1191 bytes | |||
-rwxr-xr-x | core/res/res/drawable-hdpi/stat_sys_tether_usb.png | bin | 0 -> 1191 bytes | |||
-rw-r--r-- | core/res/res/drawable-mdpi/stat_sys_tether_active.png | bin | 0 -> 786 bytes | |||
-rw-r--r-- | core/res/res/drawable-mdpi/stat_sys_tether_usb.png | bin | 0 -> 786 bytes | |||
-rw-r--r-- | core/res/res/values/strings.xml | 44 | ||||
-rw-r--r-- | services/java/com/android/server/ConnectivityService.java | 38 | ||||
-rw-r--r-- | services/java/com/android/server/NetworkManagementService.java | 17 | ||||
-rw-r--r-- | services/java/com/android/server/connectivity/Tethering.java | 483 |
14 files changed, 807 insertions, 10 deletions
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 30799ec..d435df5 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -116,6 +116,24 @@ public class ConnectivityManager "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED"; /** + * Broadcast Action: A tetherable connection has come or gone + * TODO - finish the doc + * @hide + */ + public static final String ACTION_TETHER_STATE_CHANGED = + "android.net.conn.TETHER_STATE_CHANGED"; + + /** + * @hide + */ + public static final String EXTRA_AVAILABLE_TETHER_COUNT = "availableCount"; + + /** + * @hide + */ + public static final String EXTRA_ACTIVE_TETHER_COUNT = "activeCount"; + + /** * The Default Mobile data connection. When active, all data traffic * will use this connection by default. Should not coexist with other * default connections. @@ -338,4 +356,48 @@ public class ConnectivityManager } mService = service; } + + /** + * {@hide} + */ + public String[] getTetherableIfaces() { + try { + return mService.getTetherableIfaces(); + } catch (RemoteException e) { + return new String[0]; + } + } + + /** + * {@hide} + */ + public String[] getTetheredIfaces() { + try { + return mService.getTetheredIfaces(); + } catch (RemoteException e) { + return new String[0]; + } + } + + /** + * {@hide} + */ + public boolean tether(String iface) { + try { + return mService.tether(iface); + } catch (RemoteException e) { + return false; + } + } + + /** + * {@hide} + */ + public boolean untether(String iface) { + try { + return mService.untether(iface); + } catch (RemoteException e) { + return false; + } + } } diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index 9f59cce..caa3f2b 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -50,4 +50,12 @@ interface IConnectivityManager boolean getBackgroundDataSetting(); void setBackgroundDataSetting(boolean allowBackgroundData); + + boolean tether(String iface); + + boolean untether(String iface); + + String[] getTetherableIfaces(); + + String[] getTetheredIfaces(); } diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl index e4ec098..f48f45f 100644 --- a/core/java/android/os/INetworkManagementService.aidl +++ b/core/java/android/os/INetworkManagementService.aidl @@ -140,7 +140,8 @@ interface INetworkManagementService * Attaches a PPP server daemon to the specified TTY with the specified * local/remote addresses. */ - void attachPppd(String tty, String localAddr, String remoteAddr); + void attachPppd(String tty, String localAddr, String remoteAddr, String dns1Addr, + String dns2Addr); /** * Detaches a PPP server daemon from the specified TTY. diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 7128005..bacaf43 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -2944,6 +2944,13 @@ public final class Settings { public static final String MOUNT_UMS_NOTIFY_ENABLED = "mount_ums_notify_enabled"; /** + * Whether or not a notification is displayed when a Tetherable interface is detected. + * (0 = false, 1 = true) + * @hide + */ + public static final String TETHER_NOTIFY = "tether_notify"; + + /** * If nonzero, ANRs in invisible background processes bring up a dialog. * Otherwise, the process will be silently killed. * @hide diff --git a/core/java/com/android/internal/app/TetherActivity.java b/core/java/com/android/internal/app/TetherActivity.java new file mode 100644 index 0000000..2b93dbc --- /dev/null +++ b/core/java/com/android/internal/app/TetherActivity.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.app; + +import android.app.AlertDialog; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; +import android.os.Bundle; +import android.os.Handler; +import android.os.IMountService; +import android.os.Message; +import android.os.RemoteException; +import android.os.ServiceManager; +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. + */ +public class TetherActivity extends AlertActivity implements + DialogInterface.OnClickListener { + + private static final int POSITIVE_BUTTON = AlertDialog.BUTTON1; + + /* Used to detect when the USB cable is unplugged, so we can call finish() */ + private BroadcastReceiver mTetherReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction() == ConnectivityManager.ACTION_TETHER_STATE_CHANGED) { + handleTetherStateChanged(intent); + } + } + }; + + private boolean mWantTethering; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // 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 { + finish(); + return; + } + + // Set up the "dialog" + if (mWantTethering == true) { + 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); + mAlertParams.mPositiveButtonText = + getString(com.android.internal.R.string.tether_button); + mAlertParams.mPositiveButtonListener = this; + mAlertParams.mNegativeButtonText = + getString(com.android.internal.R.string.tether_button_cancel); + mAlertParams.mNegativeButtonListener = this; + } else { + mAlertParams.mIconId = com.android.internal.R.drawable.ic_dialog_usb; + mAlertParams.mTitle = getString(com.android.internal.R.string.tether_stop_title); + mAlertParams.mMessage = getString(com.android.internal.R.string.tether_stop_message); + mAlertParams.mPositiveButtonText = + getString(com.android.internal.R.string.tether_stop_button); + mAlertParams.mPositiveButtonListener = this; + mAlertParams.mNegativeButtonText = + getString(com.android.internal.R.string.tether_stop_button_cancel); + mAlertParams.mNegativeButtonListener = this; + } + setupAlert(); + } + + @Override + protected void onResume() { + super.onResume(); + + registerReceiver(mTetherReceiver, new IntentFilter( + ConnectivityManager.ACTION_TETHER_STATE_CHANGED)); + } + + @Override + protected void onPause() { + super.onPause(); + + unregisterReceiver(mTetherReceiver); + } + + /** + * {@inheritDoc} + */ + public void onClick(DialogInterface dialog, int which) { + + if (which == POSITIVE_BUTTON) { + ConnectivityManager connManager = + (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE); + // start/stop tethering + if (mWantTethering) { + if (!connManager.tether("ppp0")) { + showTetheringError(); + } + } else { + if (!connManager.untether("ppp0")) { + showUnTetheringError(); + } + } + } + // No matter what, finish the activity + finish(); + } + + private void handleTetherStateChanged(Intent intent) { + finish(); + } + + private void showTetheringError() { + Toast.makeText(this, com.android.internal.R.string.tether_error_message, + Toast.LENGTH_LONG).show(); + } + + private void showUnTetheringError() { + Toast.makeText(this, com.android.internal.R.string.tether_stop_error_message, + Toast.LENGTH_LONG).show(); + } + +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 665088a..1406b66 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1239,6 +1239,10 @@ <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> + <activity android:name="com.android.internal.app.TetherActivity" + android:theme="@style/Theme.Dialog.Alert" + android:excludeFromRecents="true"> + </activity> <activity android:name="com.android.internal.app.UsbStorageActivity" android:excludeFromRecents="true"> </activity> diff --git a/core/res/res/drawable-hdpi/stat_sys_tether_active.png b/core/res/res/drawable-hdpi/stat_sys_tether_active.png Binary files differnew file mode 100755 index 0000000..4c14c07 --- /dev/null +++ b/core/res/res/drawable-hdpi/stat_sys_tether_active.png diff --git a/core/res/res/drawable-hdpi/stat_sys_tether_usb.png b/core/res/res/drawable-hdpi/stat_sys_tether_usb.png Binary files differnew file mode 100755 index 0000000..4c14c07 --- /dev/null +++ b/core/res/res/drawable-hdpi/stat_sys_tether_usb.png diff --git a/core/res/res/drawable-mdpi/stat_sys_tether_active.png b/core/res/res/drawable-mdpi/stat_sys_tether_active.png Binary files differnew file mode 100644 index 0000000..2d0da4c --- /dev/null +++ b/core/res/res/drawable-mdpi/stat_sys_tether_active.png diff --git a/core/res/res/drawable-mdpi/stat_sys_tether_usb.png b/core/res/res/drawable-mdpi/stat_sys_tether_usb.png Binary files differnew file mode 100644 index 0000000..2d0da4c --- /dev/null +++ b/core/res/res/drawable-mdpi/stat_sys_tether_usb.png diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 30d0da7..d1bfc68 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1078,7 +1078,13 @@ <string name="permlab_changeNetworkState">change network connectivity</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permdesc_changeNetworkState">Allows an application to change - the state network connectivity.</string> + the state of network connectivity.</string> + + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permlab_changeTetherState">change tethered connectivity</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the applicaiton to do this. --> + <string name="permdesc_changeTetherState">Allows an application to change + the state of tethered network connectivity.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_changeBackgroundDataSetting">change background data usage setting</string> @@ -2200,4 +2206,40 @@ Used by AccessibilityService to announce the purpose of the view. --> <string name="description_star">favorite</string> + + + <!-- Strings for Tethering dialogs --> + <!-- This is the label for the activity, and should never be visible to the user. --> + <!-- See TETHERING. TETHERING_DIALOG: After the user selects the notification, a dialog is shown asking if he wants to Tether. This is the title. --> + <string name="tether_title">USB tethering available</string> + <!-- See TETHER. This is the message. --> + <string name="tether_message">Select \"Tether\" if you want to share your phone\'s data connection with your computer.</string> + <!-- See TETHER. This is the button text to Tether the computer with the phone. --> + <string name="tether_button">Tether</string> + <!-- See TETHER. This is the button text to ignore the plugging in of the phone.. --> + <string name="tether_button_cancel">Cancel</string> + <!-- See TETHER. If there was an error mounting, this is the text. --> + <string name="tether_error_message">There is a problem tethering.</string> + <!-- TETHER: When the user connects the phone to a computer, we show a notification asking if he wants to share his cellular network connection. This is the title --> + <string name="tether_available_notification_title">USB tethering available</string> + <!-- See USB_STORAGE. This is the message. --> + <string name="tether_available_notification_message">Select to tether your computer to your phone.</string> + <!-- TETHER_STOP: While TETHER is enabled, we show a notification dialog asking if he wants to stop. This is the title --> + <string name="tether_stop_notification_title">Untether</string> + <!-- See TETHER. This is the message. --> + <string name="tether_stop_notification_message">Select to untether your computer.</string> + + <!-- TETHER stop dialog strings --> + <!-- This is the label for the activity, and should never be visible to the user. --> + <!-- See TETHER_STOP. TETHER_STOP_DIALOG: After the user selects the notification, a dialog is shown asking if he wants to stop tethering. This is the title. --> + <string name="tether_stop_title">Disconnect tethering</string> + <!-- See TETHER_STOP. This is the message. --> + <string name="tether_stop_message">You have been sharing your phone\'s cellular data connection with your computer. Select \"Disconnect\" to disconnect USB tethering.</string> + <!-- See TETHER_STOP. This is the button text to disconnect tethering. --> + <string name="tether_stop_button">Disconnect</string> + <!-- See TETHER_STOP. This is the button text to cancel disconnecting the tether. --> + <string name="tether_stop_button_cancel">Cancel</string> + <!-- See TETHER_STOP_DIALOG. If there was an error disconnect, this is the text. --> + <string name="tether_stop_error_message">We\'ve encountered a problem turning off Tethering. Please try again.</string> + </resources> diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index aa4956f..4259016 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -43,6 +43,8 @@ import android.util.Log; import com.android.internal.telephony.Phone; +import com.android.server.connectivity.Tethering; + import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; @@ -62,6 +64,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { private static final String NETWORK_RESTORE_DELAY_PROP_NAME = "android.telephony.apn-restore"; + + private Tethering mTethering; + /** * Sometimes we want to refer to the individual network state * trackers separately, and sometimes we just want to treat them @@ -308,6 +313,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { continue; } } + + mTethering = new Tethering(mContext); } @@ -784,6 +791,13 @@ public class ConnectivityService extends IConnectivityManager.Stub { "ConnectivityService"); } + // TODO Make this a special check when it goes public + private void enforceTetherChangePermission() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.CHANGE_NETWORK_STATE, + "ConnectivityService"); + } + /** * Handle a {@code DISCONNECTED} event. If this pertains to the non-active * network, we ignore it. If it is for the active network, we send out a @@ -1368,4 +1382,28 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } } + + // javadoc from interface + public boolean tether(String iface) { + enforceTetherChangePermission(); + return mTethering.tether(iface); + } + + // javadoc from interface + public boolean untether(String iface) { + enforceTetherChangePermission(); + return mTethering.untether(iface); + } + + // TODO - move iface listing, queries, etc to new module + // javadoc from interface + public String[] getTetherableIfaces() { + enforceAccessPermission(); + return mTethering.getTetherableIfaces(); + } + + public String[] getTetheredIfaces() { + enforceAccessPermission(); + return mTethering.getTetheredIfaces(); + } } diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java index b34b50a..d41aacf 100644 --- a/services/java/com/android/server/NetworkManagementService.java +++ b/services/java/com/android/server/NetworkManagementService.java @@ -334,9 +334,9 @@ class NetworkManagementService extends INetworkManagementService.Stub { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); try { - String cmd = "tether dns set "; + String cmd = "tether dns set"; for (String s : dns) { - cmd += InetAddress.getByName(s).toString() + " "; + cmd += " " + InetAddress.getByName(s).getHostAddress(); } mConnector.doCommand(cmd); } catch (UnknownHostException e) { @@ -373,14 +373,16 @@ class NetworkManagementService extends INetworkManagementService.Stub { return mConnector.doListCommand("list_ttys", NetdResponseCode.TtyListResult); } - public void attachPppd(String tty, String localAddr, String remoteAddr) - throws IllegalStateException { + public void attachPppd(String tty, String localAddr, String remoteAddr, String dns1Addr, + String dns2Addr) throws IllegalStateException { try { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); - mConnector.doCommand(String.format("pppd attach %s %s %s", tty, - InetAddress.getByName(localAddr).toString(), - InetAddress.getByName(localAddr).toString())); + mConnector.doCommand(String.format("pppd attach %s %s %s %s %s", tty, + InetAddress.getByName(localAddr).getHostAddress(), + InetAddress.getByName(remoteAddr).getHostAddress(), + InetAddress.getByName(dns1Addr).getHostAddress(), + InetAddress.getByName(dns2Addr).getHostAddress())); } catch (UnknownHostException e) { throw new IllegalStateException("Error resolving addr", e); } @@ -392,4 +394,3 @@ class NetworkManagementService extends INetworkManagementService.Stub { mConnector.doCommand(String.format("pppd detach %s", tty)); } } - diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java new file mode 100644 index 0000000..f685383 --- /dev/null +++ b/services/java/com/android/server/connectivity/Tethering.java @@ -0,0 +1,483 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.connectivity; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.Resources; +import android.net.ConnectivityManager; +import android.net.INetworkManagementEventObserver; +import android.os.IBinder; +import android.os.INetworkManagementService; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.provider.Settings; +import android.util.Log; + +import java.util.ArrayList; +/** + * @hide + */ +public class Tethering extends INetworkManagementEventObserver.Stub { + + private Notification mTetheringNotification; + private Context mContext; + private final String TAG = "Tethering"; + + private boolean mPlaySounds = false; + + private ArrayList<String> mAvailableIfaces; + private ArrayList<String> mActiveIfaces; + + private ArrayList<String> mActiveTtys; + + private BroadcastReceiver mStateReceiver; + + public Tethering(Context context) { + Log.d(TAG, "Tethering starting"); + mContext = context; + + // register for notifications from NetworkManagement Service + IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); + INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); + try { + service.registerObserver(this); + } catch (RemoteException e) { + Log.e(TAG, "Error registering observer :" + e); + } + + mAvailableIfaces = new ArrayList<String>(); + mActiveIfaces = new ArrayList<String>(); + mActiveTtys = new ArrayList<String>(); + + // 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(); + mContext.registerReceiver(mStateReceiver, filter); + } + + public synchronized void interfaceLinkStatusChanged(String iface, boolean link) { + Log.d(TAG, "interfaceLinkStatusChanged " + iface + ", " + link); + } + + public synchronized void interfaceAdded(String iface) { + if (mActiveIfaces.contains(iface)) { + Log.e(TAG, "active iface (" + iface + ") reported as added, ignoring"); + return; + } + if (mAvailableIfaces.contains(iface)) { + Log.e(TAG, "available iface (" + iface + ") readded, ignoring"); + return; + } + mAvailableIfaces.add(iface); + Log.d(TAG, "interfaceAdded :" + iface); + sendTetherStateChangedBroadcast(); + } + + public synchronized void interfaceRemoved(String iface) { + if (mActiveIfaces.contains(iface)) { + Log.d(TAG, "removed an active iface (" + iface + ")"); + untether(iface); + } + if (mAvailableIfaces.contains(iface)) { + mAvailableIfaces.remove(iface); + Log.d(TAG, "interfaceRemoved " + iface); + sendTetherStateChangedBroadcast(); + } + } + + public synchronized 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; + } + if (mActiveIfaces.contains(iface)) { + Log.e(TAG, "Tried to Tether an already Tethered iface :" + iface + ", ignoring"); + return false; + } + + IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); + INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); + + if (mActiveIfaces.size() == 0) { + try { + service.setIpForwardingEnabled(true); + } catch (Exception e) { + Log.e(TAG, "Error in setIpForwardingEnabled(true) :" + e); + return false; + } + + try { + // TODO - don't hardcode this - though with non-conf values (un-routable) + // maybe it's not a big deal + service.startTethering("169.254.2.1", "169.254.2.64"); + } catch (Exception e) { + Log.e(TAG, "Error in startTethering :" + e); + try { + service.setIpForwardingEnabled(false); + } catch (Exception ee) {} + return false; + } + + try { + // TODO - maybe use the current connection's dns servers for this + String[] dns = new String[2]; + dns[0] = new String("8.8.8.8"); + dns[1] = new String("4.2.2.2"); + service.setDnsForwarders(dns); + } catch (Exception e) { + Log.e(TAG, "Error in setDnsForwarders :" + e); + try { + service.stopTethering(); + } catch (Exception ee) {} + try { + service.setIpForwardingEnabled(false); + } catch (Exception ee) {} + } + } + + try { + service.tetherInterface(iface); + } catch (Exception e) { + Log.e(TAG, "Error in tetherInterface :" + e); + if (mActiveIfaces.size() == 0) { + try { + service.stopTethering(); + } catch (Exception ee) {} + try { + service.setIpForwardingEnabled(false); + } catch (Exception ee) {} + } + return false; + } + + try { + // TODO - use the currently active external iface + service.enableNat (iface, "rmnet0"); + } catch (Exception e) { + Log.e(TAG, "Error in enableNat :" + e); + try { + service.untetherInterface(iface); + } catch (Exception ee) {} + if (mActiveIfaces.size() == 0) { + try { + service.stopTethering(); + } catch (Exception ee) {} + try { + service.setIpForwardingEnabled(false); + } catch (Exception ee) {} + } + return false; + } + mAvailableIfaces.remove(iface); + mActiveIfaces.add(iface); + Log.d(TAG, "Tethered " + iface); + sendTetherStateChangedBroadcast(); + return true; + } + + public synchronized boolean untether(String iface) { + Log.d(TAG, "Untethering " + iface); + + if (mAvailableIfaces.contains(iface)) { + Log.e(TAG, "Tried to Untether an available iface :" + iface); + return false; + } + if (!mActiveIfaces.contains(iface)) { + Log.e(TAG, "Tried to Untether an inactive iface :" + iface); + return false; + } + + IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); + INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); + + // none of these errors are recoverable - ie, multiple calls won't help + // and the user can't do anything. Basically a reboot is required and probably + // the device is misconfigured or something bad has happend. + // Because of this, we should try to unroll as much state as we can. + try { + service.disableNat(iface, "rmnet0"); + } catch (Exception e) { + Log.e(TAG, "Error in disableNat :" + e); + } + try { + service.untetherInterface(iface); + } catch (Exception e) { + Log.e(TAG, "Error untethering " + iface + ", :" + e); + } + mActiveIfaces.remove(iface); + mAvailableIfaces.add(iface); + + if (mActiveIfaces.size() == 0) { + Log.d(TAG, "no active tethers - turning down dhcp/ipforward"); + try { + service.stopTethering(); + } catch (Exception e) { + Log.e(TAG, "Error in stopTethering :" + e); + } + try { + service.setIpForwardingEnabled(false); + } catch (Exception e) { + Log.e(TAG, "Error in setIpForwardingEnabled(false) :" + e); + } + } + sendTetherStateChangedBroadcast(); + Log.d(TAG, "Untethered " + iface); + 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(); + } + } + + private void showTetherAvailableNotification() { + NotificationManager notificationManager = (NotificationManager)mContext. + getSystemService(Context.NOTIFICATION_SERVICE); + if (notificationManager == null) { + return; + } + + Intent intent = new Intent(); + intent.setClass(mContext, com.android.internal.app.TetherActivity.class); + + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0); + + Resources r = Resources.getSystem(); + CharSequence title = r.getText(com.android.internal.R.string. + tether_available_notification_title); + CharSequence message = r.getText(com.android.internal.R.string. + tether_available_notification_message); + + if(mTetheringNotification == null) { + mTetheringNotification = new Notification(); + mTetheringNotification.when = 0; + } + mTetheringNotification.icon = com.android.internal.R.drawable.stat_sys_tether_usb; + + boolean playSounds = false; + //playSounds = SystemProperties.get("persist.service.mount.playsnd", "1").equals("1"); + if (playSounds) { + mTetheringNotification.defaults |= Notification.DEFAULT_SOUND; + } else { + mTetheringNotification.defaults &= ~Notification.DEFAULT_SOUND; + } + + mTetheringNotification.flags = Notification.FLAG_ONGOING_EVENT; + mTetheringNotification.tickerText = title; + mTetheringNotification.setLatestEventInfo(mContext, title, message, pi); + + notificationManager.notify(mTetheringNotification.icon, mTetheringNotification); + + } + + private void showTetheredNotification() { + NotificationManager notificationManager = (NotificationManager)mContext. + getSystemService(Context.NOTIFICATION_SERVICE); + if (notificationManager == null) { + return; + } + + Intent intent = new Intent(); + intent.setClass(mContext, com.android.internal.app.TetherActivity.class); + + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0); + + Resources r = Resources.getSystem(); + CharSequence title = r.getText(com.android.internal.R.string. + tether_stop_notification_title); + CharSequence message = r.getText(com.android.internal.R.string. + tether_stop_notification_message); + + if(mTetheringNotification == null) { + mTetheringNotification = new Notification(); + mTetheringNotification.when = 0; + } + mTetheringNotification.icon = com.android.internal.R.drawable.stat_sys_tether_usb; + + boolean playSounds = false; + //playSounds = SystemProperties.get("persist.service.mount.playsnd", "1").equals("1"); + if (playSounds) { + mTetheringNotification.defaults |= Notification.DEFAULT_SOUND; + } else { + mTetheringNotification.defaults &= ~Notification.DEFAULT_SOUND; + } + + mTetheringNotification.flags = Notification.FLAG_ONGOING_EVENT; + mTetheringNotification.tickerText = title; + mTetheringNotification.setLatestEventInfo(mContext, title, message, pi); + + notificationManager.notify(mTetheringNotification.icon, mTetheringNotification); + } + + private void clearNotification() { + NotificationManager notificationManager = (NotificationManager)mContext. + getSystemService(Context.NOTIFICATION_SERVICE); + if (notificationManager != null && mTetheringNotification != null) { + notificationManager.cancel(mTetheringNotification.icon); + mTetheringNotification = null; + } + } + + + + +// TODO - remove this hack after we get proper USB detection + private class UMSStateReceiver extends BroadcastReceiver { + public void onReceive(Context content, Intent intent) { + if (intent.getAction().equals(Intent.ACTION_UMS_CONNECTED)) { + Tethering.this.handleTtyConnect(); + } else if (intent.getAction().equals(Intent.ACTION_UMS_DISCONNECTED)) { + Tethering.this.handleTtyDisconnect(); + } + } + } + + private synchronized void handleTtyConnect() { + Log.d(TAG, "handleTtyConnect"); + // for each of the available Tty not already supported by a ppp session, + // create a ppp session + // TODO - this should be data-driven rather than hard coded. + String[] allowedTtys = new String[1]; + allowedTtys[0] = new String("ttyGS0"); + + IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); + INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); + + String[] availableTtys; + try { + availableTtys = service.listTtys(); + } catch (RemoteException e) { + Log.e(TAG, "error listing Ttys :" + e); + return; + } + + for (String tty : availableTtys) { + for (String pattern : allowedTtys) { + if (tty.matches(pattern)) { + synchronized (this) { + if (!mActiveTtys.contains(tty)) { + // TODO - don't hardcode this + try { + // local, remote, dns + service.attachPppd(tty, "169.254.1.128", "169.254.1.1", + "169.254.1.128", "0.0.0.0"); + } catch (Exception e) { + Log.e(TAG, "error calling attachPppd: " + e); + return; + } + Log.d(TAG, "started Pppd on tty " + tty); + mActiveTtys.add(tty); + // TODO - remove this after we detect the new iface + interfaceAdded("ppp0"); + } + } + } + } + } + } + + private synchronized void handleTtyDisconnect() { + Log.d(TAG, "handleTtyDisconnect"); + + // TODO - this should be data-driven rather than hard coded. + String[] allowedTtys = new String[1]; + allowedTtys[0] = new String("ttyGS0"); + + IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); + INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); + + String[] availableTtys; + try { + availableTtys = service.listTtys(); + } catch (RemoteException e) { + Log.e(TAG, "error listing Ttys :" + e); + return; + } + + for (String tty : availableTtys) { + for (String pattern : allowedTtys) { + if (tty.matches(pattern)) { + synchronized (this) { + if (mActiveTtys.contains(tty)) { + try { + service.detachPppd(tty); + } catch (Exception e) { + Log.e(TAG, "error calling detachPppd on " + tty + " :" + e); + } + mActiveTtys.remove(tty); + // TODO - remove this after we detect the new iface + interfaceRemoved("ppp0"); + return; + } + } + } + } + } + } + + public synchronized String[] getTetheredIfaces() { + int size = mActiveIfaces.size(); + String[] result = new String[size]; + size -= 1; + for (int i=0; i< size; i++) { + result[i] = mActiveIfaces.get(i); + } + return result; + } + + public 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); + } + return result; + } +} |