bluetooth tethering

Change-Id: Id6d5fb1922facc7013abc29214d3e1141995b767
diff --git a/core/java/android/bluetooth/BluetoothDevicePicker.java b/core/java/android/bluetooth/BluetoothDevicePicker.java
index 05eed0e..7415721 100644
--- a/core/java/android/bluetooth/BluetoothDevicePicker.java
+++ b/core/java/android/bluetooth/BluetoothDevicePicker.java
@@ -63,4 +63,9 @@
     public static final int FILTER_TYPE_AUDIO = 1;
     /** Ask device picker to show BT devices that support Object Transfer */
     public static final int FILTER_TYPE_TRANSFER = 2;
+    /** Ask device picker to show BT devices that support
+     * Personal Area Networking User (PANU) profile*/
+    public static final int FILTER_TYPE_PANU = 3;
+    /** Ask device picker to show BT devices that support Network Access Point (NAP) profile */
+    public static final int FILTER_TYPE_NAP = 4;
 }
diff --git a/core/java/android/bluetooth/BluetoothPan.java b/core/java/android/bluetooth/BluetoothPan.java
new file mode 100644
index 0000000..952765d
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothPan.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2008 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 android.bluetooth;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.content.Context;
+import android.os.ServiceManager;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.util.Log;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @hide
+ */
+public final class BluetoothPan {
+    private static final String TAG = "BluetoothPan";
+    private static final boolean DBG = false;
+
+    /** int extra for ACTION_PAN_STATE_CHANGED */
+    public static final String EXTRA_PAN_STATE =
+        "android.bluetooth.pan.extra.STATE";
+    /** int extra for ACTION_PAN_STATE_CHANGED */
+    public static final String EXTRA_PREVIOUS_PAN_STATE =
+        "android.bluetooth.pan.extra.PREVIOUS_STATE";
+
+    /** Indicates the state of an PAN device has changed.
+     * This intent will always contain EXTRA_DEVICE_STATE,
+     * EXTRA_PREVIOUS_DEVICE_STATE and BluetoothDevice.EXTRA_DEVICE
+     * extras.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_PAN_STATE_CHANGED =
+        "android.bluetooth.pan.action.STATE_CHANGED";
+
+    public static final String NAP_ROLE = "nap";
+    public static final String NAP_BRIDGE = "pan1";
+
+    public static final int MAX_CONNECTIONS = 7;
+
+    public static final int STATE_DISCONNECTED = 0;
+    public static final int STATE_CONNECTING   = 1;
+    public static final int STATE_CONNECTED    = 2;
+    public static final int STATE_DISCONNECTING = 3;
+
+    private final IBluetooth mService;
+    private final Context mContext;
+
+    /**
+     * Create a BluetoothPan proxy object for interacting with the local
+     * Bluetooth Pan service.
+     * @param c Context
+     */
+    public BluetoothPan(Context c) {
+        mContext = c;
+
+        IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_SERVICE);
+        if (b != null) {
+            mService = IBluetooth.Stub.asInterface(b);
+        } else {
+            Log.w(TAG, "Bluetooth Service not available!");
+
+            // Instead of throwing an exception which prevents people from going
+            // into Wireless settings in the emulator. Let it crash later
+            // when it is actually used.
+            mService = null;
+        }
+    }
+
+    /**
+     * Initiate a PAN connection.
+     *
+     * This function returns false on error and true if the connection
+     * attempt is being made.
+     *
+     * Listen for {@link #ACTION_PAN_STATE_CHANGED} to find out when the
+     * connection is completed.
+     *
+     * @param device Remote BT device.
+     * @return false on immediate error, true otherwise
+     * @hide
+     */
+    public boolean connect(BluetoothDevice device) {
+        if (DBG) log("connect(" + device + ")");
+        try {
+            return mService.connectPanDevice(device);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+            return false;
+        }
+    }
+
+    /**
+     * Initiate disconnect from PAN.
+     *
+     * This function return false on error and true if the disconnection
+     * attempt is being made.
+     *
+     * Listen for {@link #ACTION_PAN_STATE_CHANGED} to find out when
+     * disconnect is completed.
+     *
+     * @param device Remote BT device.
+     * @return false on immediate error, true otherwise
+     * @hide
+     */
+    public boolean disconnect(BluetoothDevice device) {
+        if (DBG) log("disconnect(" + device + ")");
+        try {
+            return mService.disconnectPanDevice(device);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+            return false;
+        }
+    }
+
+    /** Get the state of a PAN Device.
+    *
+    * This function returns an int representing the state of the PAN connection
+    *
+    *  @param device Remote BT device.
+    *  @return The current state of the PAN Device
+    *  @hide
+    */
+   public int getPanDeviceState(BluetoothDevice device) {
+       if (DBG) log("getPanDeviceState(" + device + ")");
+       try {
+           return mService.getPanDeviceState(device);
+       } catch (RemoteException e) {
+           Log.e(TAG, "", e);
+           return STATE_DISCONNECTED;
+       }
+   }
+
+   /** Returns a set of all the connected PAN Devices
+   *
+   * Does not include devices that are currently connecting or disconnecting
+   *
+   * @return a unmodifiable set of connected PAN Devices, or null on error.
+   * @hide
+   */
+   public Set<BluetoothDevice> getConnectedDevices() {
+      if (DBG) log("getConnectedDevices");
+      try {
+          return Collections.unmodifiableSet(
+                  new HashSet<BluetoothDevice>(
+                      Arrays.asList(mService.getConnectedPanDevices())));
+      } catch (RemoteException e) {
+          Log.e(TAG, "", e);
+          return null;
+      }
+   }
+
+    private static void log(String msg) {
+        Log.d(TAG, msg);
+    }
+
+    public void setBluetoothTethering(boolean value, String uuid, String bridge) {
+        try {
+            mService.setBluetoothTethering(value, uuid, bridge);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+    }
+
+    public boolean isTetheringOn() {
+        try {
+            return mService.isTetheringOn();
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+            return false;
+        }
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothUuid.java b/core/java/android/bluetooth/BluetoothUuid.java
index f1ee907..fb3dfe4 100644
--- a/core/java/android/bluetooth/BluetoothUuid.java
+++ b/core/java/android/bluetooth/BluetoothUuid.java
@@ -51,10 +51,14 @@
             ParcelUuid.fromString("00001105-0000-1000-8000-00805f9b34fb");
     public static final ParcelUuid Hid =
             ParcelUuid.fromString("00001124-0000-1000-8000-00805f9b34fb");
+    public static final ParcelUuid PANU =
+            ParcelUuid.fromString("00001115-0000-1000-8000-00805F9B34FB");
+    public static final ParcelUuid NAP =
+            ParcelUuid.fromString("00001116-0000-1000-8000-00805F9B34FB");
 
     public static final ParcelUuid[] RESERVED_UUIDS = {
         AudioSink, AudioSource, AdvAudioDist, HSP, Handsfree, AvrcpController, AvrcpTarget,
-        ObexObjectPush};
+        ObexObjectPush, PANU, NAP};
 
     public static boolean isAudioSource(ParcelUuid uuid) {
         return uuid.equals(AudioSource);
@@ -88,6 +92,13 @@
         return uuid.equals(Hid);
     }
 
+    public static boolean isPANU(ParcelUuid uuid) {
+        return uuid.equals(PANU);
+    }
+
+    public static boolean isNAP(ParcelUuid uuid) {
+        return uuid.equals(NAP);
+    }
     /**
      * Returns true if ParcelUuid is present in uuidArray
      *
diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl
index 75f093c..f8f678b 100644
--- a/core/java/android/bluetooth/IBluetooth.aidl
+++ b/core/java/android/bluetooth/IBluetooth.aidl
@@ -81,4 +81,11 @@
     int getInputDeviceState(in BluetoothDevice device);
     boolean setInputDevicePriority(in BluetoothDevice device, int priority);
     int getInputDevicePriority(in BluetoothDevice device);
+
+    boolean isTetheringOn();
+    void setBluetoothTethering(boolean value, String uuid, String bridge);
+    int getPanDeviceState(in BluetoothDevice device);
+    BluetoothDevice[] getConnectedPanDevices();
+    boolean connectPanDevice(in BluetoothDevice device);
+    boolean disconnectPanDevice(in BluetoothDevice device);
 }
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 6335296..8d1a04c 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -324,14 +324,14 @@
      * <p>
      * All applications that have background services that use the network
      * should listen to {@link #ACTION_BACKGROUND_DATA_SETTING_CHANGED}.
-     * 
+     *
      * @return Whether background data usage is allowed.
      */
     public boolean getBackgroundDataSetting() {
         try {
             return mService.getBackgroundDataSetting();
         } catch (RemoteException e) {
-            // Err on the side of safety 
+            // Err on the side of safety
             return false;
         }
     }
@@ -489,6 +489,17 @@
         }
     }
 
+    /**
+     * {@hide}
+     */
+    public String[] getTetherableBluetoothRegexs() {
+        try {
+            return mService.getTetherableBluetoothRegexs();
+        } catch (RemoteException e) {
+            return new String[0];
+        }
+    }
+
     /** {@hide} */
     public static final int TETHER_ERROR_NO_ERROR           = 0;
     /** {@hide} */
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 5a14cc9..1d57019 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -1,16 +1,16 @@
 /**
  * Copyright (c) 2008, 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 
+ * 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 
+ *     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 
+ * 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.
  */
 
@@ -73,5 +73,7 @@
 
     String[] getTetherableWifiRegexs();
 
+    String[] getTetherableBluetoothRegexs();
+
     void requestNetworkTransitionWakelock(in String forWhom);
 }
diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java
index eb9b62b..9948060 100644
--- a/core/java/android/server/BluetoothEventLoop.java
+++ b/core/java/android/server/BluetoothEventLoop.java
@@ -21,6 +21,7 @@
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothInputDevice;
+import android.bluetooth.BluetoothPan;
 import android.bluetooth.BluetoothUuid;
 import android.content.Context;
 import android.content.Intent;
@@ -365,7 +366,8 @@
             return;
         }
         if (DBG) {
-            log("Device property changed:" + address + "property:" + name);
+            log("Device property changed: " + address + " property: "
+                    + name + " value: " + propValues[1]);
         }
         BluetoothDevice device = mAdapter.getRemoteDevice(address);
         if (name.equals("Name")) {
@@ -442,6 +444,25 @@
         mBluetoothService.handleInputDevicePropertyChange(address, state);
     }
 
+    private void onPanDevicePropertyChanged(String deviceObjectPath, String[] propValues) {
+        String name = propValues[0];
+        String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
+        if (address == null) {
+            Log.e(TAG, "onPanDevicePropertyChanged: Address of the remote device in null");
+            return;
+        }
+        if (DBG) {
+            log("Pan Device property changed: " + address + "  property: "
+                    + name + " value: "+ propValues[1]);
+        }
+        BluetoothDevice device = mAdapter.getRemoteDevice(address);
+        if (name.equals("Connected")) {
+            int state = propValues[1].equals("true") ? BluetoothInputDevice.STATE_CONNECTED :
+                BluetoothInputDevice.STATE_DISCONNECTED;
+            mBluetoothService.handlePanDeviceStateChange(device, state);
+        }
+    }
+
     private String checkPairingRequestAndGetAddress(String objectPath, int nativeData) {
         String address = mBluetoothService.getAddressFromObjectPath(objectPath);
         if (address == null) {
@@ -623,6 +644,8 @@
              } else {
                  Log.i(TAG, "Rejecting incoming HID connection from " + address);
              }
+        } else if (BluetoothUuid.isNAP(uuid)){
+            authorized = true;
         } else {
             Log.i(TAG, "Rejecting incoming " + deviceUuid + " connection from " + address);
         }
@@ -713,6 +736,30 @@
         }
     }
 
+    private void onPanDeviceConnectionResult(String path, boolean result) {
+        log ("onPanDeviceConnectionResult " + path + " " + result);
+        // Success case gets handled by Property Change signal
+        if (!result) {
+            String address = mBluetoothService.getAddressFromObjectPath(path);
+            if (address == null) return;
+
+            boolean connected = false;
+            BluetoothDevice device = mAdapter.getRemoteDevice(address);
+            int state = mBluetoothService.getPanDeviceState(device);
+            if (state == BluetoothPan.STATE_CONNECTING) {
+                connected = false;
+            } else if (state == BluetoothPan.STATE_DISCONNECTING) {
+                connected = true;
+            } else {
+                Log.e(TAG, "Error onPanDeviceConnectionResult. State is: "
+                        + state + " result: "+ result);
+            }
+            int newState = connected? BluetoothPan.STATE_CONNECTED :
+                BluetoothPan.STATE_DISCONNECTED;
+            mBluetoothService.handlePanDeviceStateChange(device, newState);
+        }
+    }
+
     private void onRestartRequired() {
         if (mBluetoothService.isEnabled()) {
             Log.e(TAG, "*** A serious error occured (did bluetoothd crash?) - " +
diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java
index acfc0d7..ab78aeb 100644
--- a/core/java/android/server/BluetoothService.java
+++ b/core/java/android/server/BluetoothService.java
@@ -24,28 +24,30 @@
 
 package android.server;
 
-import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothDeviceProfileState;
+import android.bluetooth.BluetoothPan;
 import android.bluetooth.BluetoothProfileState;
 import android.bluetooth.BluetoothInputDevice;
 import android.bluetooth.BluetoothSocket;
 import android.bluetooth.BluetoothUuid;
 import android.bluetooth.IBluetooth;
 import android.bluetooth.IBluetoothCallback;
-import android.bluetooth.IBluetoothHeadset;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.SharedPreferences;
+import android.net.ConnectivityManager;
+import android.net.InterfaceConfiguration;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.INetworkManagementService;
 import android.os.Message;
 import android.os.ParcelUuid;
 import android.os.RemoteException;
@@ -89,6 +91,7 @@
     private int mBluetoothState;
     private boolean mRestart = false;  // need to call enable() after disable()
     private boolean mIsDiscovering;
+    private boolean mTetheringOn;
 
     private BluetoothAdapter mAdapter;  // constant after init()
     private final BondState mBondState = new BondState();  // local cache of bondings
@@ -109,6 +112,10 @@
     private static final int MESSAGE_UUID_INTENT = 3;
     private static final int MESSAGE_DISCOVERABLE_TIMEOUT = 4;
 
+    private ArrayList<String> mBluetoothIfaceAddresses;
+    private static final String BLUETOOTH_NEAR_IFACE_ADDR_PREFIX= "192.168.44.";
+    private static final String BLUETOOTH_NETMASK        = "255.255.255.0";
+
     // The timeout used to sent the UUIDs Intent
     // This timeout should be greater than the page timeout
     private static final int UUID_INTENT_DELAY = 6000;
@@ -136,6 +143,7 @@
 
     private BluetoothA2dpService mA2dpService;
     private final HashMap<BluetoothDevice, Integer> mInputDevices;
+    private final HashMap<BluetoothDevice, Integer> mPanDevices;
 
     private static String mDockAddress;
     private String mDockPin;
@@ -187,6 +195,7 @@
 
         mBluetoothState = BluetoothAdapter.STATE_OFF;
         mIsDiscovering = false;
+        mTetheringOn = false;
         mAdapterProperties = new HashMap<String, String>();
         mDeviceProperties = new HashMap<String, Map<String,String>>();
 
@@ -199,6 +208,12 @@
         mHfpProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.HFP);
         mHidProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.HID);
 
+        // Can tether to up to 7 devices
+        mBluetoothIfaceAddresses = new ArrayList<String>(BluetoothPan.MAX_CONNECTIONS);
+        for (int i=1; i <= BluetoothPan.MAX_CONNECTIONS; i++) {
+            mBluetoothIfaceAddresses.add(BLUETOOTH_NEAR_IFACE_ADDR_PREFIX + i);
+        }
+
         mHfpProfileState.start();
         mA2dpProfileState.start();
         mHidProfileState.start();
@@ -209,6 +224,7 @@
         filter.addAction(Intent.ACTION_DOCK_EVENT);
         mContext.registerReceiver(mReceiver, filter);
         mInputDevices = new HashMap<BluetoothDevice, Integer>();
+        mPanDevices = new HashMap<BluetoothDevice, Integer>();
     }
 
     public static synchronized String readDockBluetoothAddress() {
@@ -336,6 +352,7 @@
         }
         setBluetoothState(BluetoothAdapter.STATE_TURNING_OFF);
         mHandler.removeMessages(MESSAGE_REGISTER_SDP_RECORDS);
+        setBluetoothTethering(false, BluetoothPan.NAP_ROLE, BluetoothPan.NAP_BRIDGE);
 
         // Allow 3 seconds for profiles to gracefully disconnect
         // TODO: Introduce a callback mechanism so that each profile can notify
@@ -1235,6 +1252,194 @@
         return sp.contains(SHARED_PREFERENCE_DOCK_ADDRESS + address);
     }
 
+    public synchronized boolean isTetheringOn() {
+        return mTetheringOn;
+    }
+
+    public synchronized void setBluetoothTethering(boolean value, String uuid, String bridge) {
+        mTetheringOn = value;
+        if (!value) {
+            disconnectPan();
+        }
+        setBluetoothTetheringNative(value, uuid, bridge);
+    }
+
+    public synchronized int getPanDeviceState(BluetoothDevice device) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+
+        if (mPanDevices.get(device) == null) {
+            return BluetoothPan.STATE_DISCONNECTED;
+        }
+        return mPanDevices.get(device);
+    }
+
+    public synchronized boolean connectPanDevice(BluetoothDevice device) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+                                                "Need BLUETOOTH_ADMIN permission");
+
+        String objectPath = getObjectPathFromAddress(device.getAddress());
+        if (DBG) log("connect PAN(" + objectPath + ")");
+        if (getPanDeviceState(device) != BluetoothPan.STATE_DISCONNECTED) {
+            log (device + " already connected to PAN");
+        }
+
+        int connectedCount = 0;
+        for (BluetoothDevice BTdevice: mPanDevices.keySet()) {
+            if (getPanDeviceState(BTdevice) == BluetoothPan.STATE_CONNECTED) {
+                connectedCount ++;
+            }
+        }
+        if (connectedCount > 8) {
+            log (device + " could not connect to PAN because 8 other devices are already connected");
+            return false;
+        }
+
+        handlePanDeviceStateChange(device, BluetoothPan.STATE_CONNECTING);
+        if (connectPanDeviceNative(objectPath, "nap", "panu")) {
+            log ("connecting to PAN");
+            return true;
+        } else {
+            handlePanDeviceStateChange(device, BluetoothPan.STATE_DISCONNECTED);
+            log ("could not connect to PAN");
+            return false;
+        }
+    }
+
+    private synchronized boolean disconnectPan() {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        if (DBG) log("disconnect all PAN devices");
+
+        for (BluetoothDevice device: mPanDevices.keySet()) {
+            if (getPanDeviceState(device) == BluetoothPan.STATE_CONNECTED) {
+                if (!disconnectPanDevice(device)) {
+                    log ("could not disconnect Pan Device "+device.getAddress());
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    public synchronized BluetoothDevice[] getConnectedPanDevices() {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+
+        Set<BluetoothDevice> devices = new HashSet<BluetoothDevice>();
+        for (BluetoothDevice device: mPanDevices.keySet()) {
+            if (getPanDeviceState(device) == BluetoothPan.STATE_CONNECTED) {
+                devices.add(device);
+            }
+        }
+        return devices.toArray(new BluetoothDevice[devices.size()]);
+    }
+
+    public synchronized boolean disconnectPanDevice(BluetoothDevice device) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+                                                "Need BLUETOOTH_ADMIN permission");
+        String objectPath = getObjectPathFromAddress(device.getAddress());
+        if (DBG) log("disconnect PAN(" + objectPath + ")");
+        if (getPanDeviceState(device) != BluetoothPan.STATE_CONNECTED) {
+            log (device + " already disconnected from PAN");
+        }
+        handlePanDeviceStateChange(device, BluetoothPan.STATE_DISCONNECTING);
+        return disconnectPanDeviceNative(objectPath);
+    }
+
+    /*package*/ void handlePanDeviceStateChange(BluetoothDevice device, int state) {
+        int prevState;
+        if (mPanDevices.get(device) == null) {
+            prevState = BluetoothPan.STATE_DISCONNECTED;
+        } else {
+            prevState = mPanDevices.get(device);
+        }
+        if (prevState == state) return;
+
+        mPanDevices.put(device, state);
+
+        if (state == BluetoothPan.STATE_CONNECTED) {
+            updateTetherState(true);
+        }
+
+        Intent intent = new Intent(BluetoothPan.ACTION_PAN_STATE_CHANGED);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+        intent.putExtra(BluetoothPan.EXTRA_PREVIOUS_PAN_STATE, prevState);
+        intent.putExtra(BluetoothPan.EXTRA_PAN_STATE, state);
+        mContext.sendBroadcast(intent, BLUETOOTH_PERM);
+
+        if (DBG) log("Pan Device state : device: " + device + " State:" + prevState + "->" + state);
+
+    }
+
+    // configured when we start tethering and unconfig'd on error or conclusion
+    private boolean updateTetherState(boolean enabled) {
+        Log.d(TAG, "configureBluetoothIface(" + enabled + ")");
+
+        IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+        INetworkManagementService service = INetworkManagementService.Stub.asInterface(b);
+        ConnectivityManager cm =
+            (ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+        String[] bluetoothRegexs = cm.getTetherableBluetoothRegexs();
+
+        // bring toggle the interfaces
+        String[] ifaces = new String[0];
+        try {
+            ifaces = service.listInterfaces();
+        } catch (Exception e) {
+            Log.e(TAG, "Error listing Interfaces :" + e);
+            return false;
+        }
+
+        ArrayList<String> ifaceAddresses = (ArrayList<String>) mBluetoothIfaceAddresses.clone();
+        for (String iface : ifaces) {
+            for (String regex : bluetoothRegexs) {
+                if (iface.matches(regex)) {
+                    InterfaceConfiguration ifcg = null;
+                    try {
+                        ifcg = service.getInterfaceConfig(iface);
+                        if (ifcg != null) {
+                            if (enabled) {
+                                String[] addr = BLUETOOTH_NETMASK.split("\\.");
+                                ifcg.netmask = (Integer.parseInt(addr[0]) << 24) +
+                                        (Integer.parseInt(addr[1]) << 16) +
+                                        (Integer.parseInt(addr[2]) << 8) +
+                                        (Integer.parseInt(addr[3]));
+                                if (ifcg.ipAddr == 0 && !ifaceAddresses.isEmpty()) {
+                                    addr = ifaceAddresses.remove(0).split("\\.");
+                                    ifcg.ipAddr = (Integer.parseInt(addr[0]) << 24) +
+                                            (Integer.parseInt(addr[1]) << 16) +
+                                            (Integer.parseInt(addr[2]) << 8) +
+                                            (Integer.parseInt(addr[3]));
+                                } else {
+                                    String IfaceAddress =
+                                            String.valueOf(ifcg.ipAddr >>> 24) + "." +
+                                            String.valueOf((ifcg.ipAddr & 0x00FF0000) >>> 16) + "." +
+                                            String.valueOf((ifcg.ipAddr & 0x0000FF00) >>> 8) + "." +
+                                            String.valueOf(ifcg.ipAddr & 0x000000FF);
+                                    ifaceAddresses.remove(IfaceAddress);
+                                }
+                                ifcg.interfaceFlags = ifcg.interfaceFlags.replace("down", "up");
+                            } else {
+                                ifcg.interfaceFlags = ifcg.interfaceFlags.replace("up", "down");
+                            }
+                            ifcg.interfaceFlags = ifcg.interfaceFlags.replace("running", "");
+                            ifcg.interfaceFlags = ifcg.interfaceFlags.replace("  "," ");
+                            service.setInterfaceConfig(iface, ifcg);
+                            if (enabled) {
+                                if (cm.tether(iface) != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+                                    Log.e(TAG, "Error tethering "+ifaces);
+                                }
+                            }
+                        }
+                    } catch (Exception e) {
+                        Log.e(TAG, "Error configuring interface " + iface + ", :" + e);
+                        return false;
+                    }
+                }
+            }
+        }
+
+        return true;
+    }
+
     public synchronized boolean connectInputDevice(BluetoothDevice device) {
         mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
                                                 "Need BLUETOOTH_ADMIN permission");
@@ -1418,7 +1623,7 @@
             if (updateRemoteDevicePropertiesCache(address))
                 return getRemoteDeviceProperty(address, property);
         }
-        Log.e(TAG, "getRemoteDeviceProperty: " + property + "not present:" + address);
+        Log.e(TAG, "getRemoteDeviceProperty: " + property + " not present: " + address);
         return null;
     }
 
@@ -2281,4 +2486,8 @@
     private native boolean setLinkTimeoutNative(String path, int num_slots);
     private native boolean connectInputDeviceNative(String path);
     private native boolean disconnectInputDeviceNative(String path);
+
+    private native boolean setBluetoothTetheringNative(boolean value, String nap, String bridge);
+    private native boolean connectPanDeviceNative(String path, String srcRole, String dstRole);
+    private native boolean disconnectPanDeviceNative(String path);
 }
diff --git a/core/jni/android_bluetooth_common.cpp b/core/jni/android_bluetooth_common.cpp
index a38d3b2..aae0f21 100644
--- a/core/jni/android_bluetooth_common.cpp
+++ b/core/jni/android_bluetooth_common.cpp
@@ -73,6 +73,12 @@
     {"Connected", DBUS_TYPE_BOOLEAN},
 };
 
+static Properties pan_properties[] = {
+    {"Connected", DBUS_TYPE_BOOLEAN},
+    {"Interface", DBUS_TYPE_STRING},
+    {"UUID", DBUS_TYPE_STRING},
+};
+
 typedef union {
     char *str_val;
     int int_val;
@@ -191,6 +197,7 @@
     dbus_bool_t ret;
     va_list lst;
     va_start(lst, first_arg_type);
+
     ret = dbus_func_args_async_valist(env, conn,
                                       timeout_ms,
                                       reply, user, nat,
@@ -708,6 +715,11 @@
                     sizeof(input_properties) / sizeof(Properties));
 }
 
+jobjectArray parse_pan_property_change(JNIEnv *env, DBusMessage *msg) {
+    return parse_property_change(env, msg, (Properties *) &pan_properties,
+                    sizeof(pan_properties) / sizeof(Properties));
+}
+
 jobjectArray parse_adapter_properties(JNIEnv *env, DBusMessageIter *iter) {
     return parse_properties(env, iter, (Properties *) &adapter_properties,
                             sizeof(adapter_properties) / sizeof(Properties));
diff --git a/core/jni/android_bluetooth_common.h b/core/jni/android_bluetooth_common.h
index 27a00ae..9222e1a 100644
--- a/core/jni/android_bluetooth_common.h
+++ b/core/jni/android_bluetooth_common.h
@@ -164,6 +164,7 @@
 jobjectArray parse_adapter_property_change(JNIEnv *env, DBusMessage *msg);
 jobjectArray parse_input_properties(JNIEnv *env, DBusMessageIter *iter);
 jobjectArray parse_input_property_change(JNIEnv *env, DBusMessage *msg);
+jobjectArray parse_pan_property_change(JNIEnv *env, DBusMessage *msg);
 void append_variant(DBusMessageIter *iter, int type, void *val);
 int get_bdaddr(const char *str, bdaddr_t *ba);
 void get_bdaddr_as_string(const bdaddr_t *ba, char *str);
diff --git a/core/jni/android_server_BluetoothEventLoop.cpp b/core/jni/android_server_BluetoothEventLoop.cpp
index 4dc012c..6ef154e 100644
--- a/core/jni/android_server_BluetoothEventLoop.cpp
+++ b/core/jni/android_server_BluetoothEventLoop.cpp
@@ -66,6 +66,8 @@
 
 static jmethodID method_onInputDevicePropertyChanged;
 static jmethodID method_onInputDeviceConnectionResult;
+static jmethodID method_onPanDevicePropertyChanged;
+static jmethodID method_onPanDeviceConnectionResult;
 
 typedef event_loop_native_data_t native_data_t;
 
@@ -120,9 +122,13 @@
     method_onDisplayPasskey = env->GetMethodID(clazz, "onDisplayPasskey",
                                                "(Ljava/lang/String;II)V");
     method_onInputDevicePropertyChanged = env->GetMethodID(clazz, "onInputDevicePropertyChanged",
-                                                      "(Ljava/lang/String;[Ljava/lang/String;)V");
+                                               "(Ljava/lang/String;[Ljava/lang/String;)V");
     method_onInputDeviceConnectionResult = env->GetMethodID(clazz, "onInputDeviceConnectionResult",
-                                                      "(Ljava/lang/String;Z)V");
+                                               "(Ljava/lang/String;Z)V");
+    method_onPanDevicePropertyChanged = env->GetMethodID(clazz, "onPanDevicePropertyChanged",
+                                               "(Ljava/lang/String;[Ljava/lang/String;)V");
+    method_onPanDeviceConnectionResult = env->GetMethodID(clazz, "onPanDeviceConnectionResult",
+                                               "(Ljava/lang/String;Z)V");
 
     field_mNativeData = env->GetFieldID(clazz, "mNativeData", "I");
 #endif
@@ -890,7 +896,23 @@
             LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg);
         }
         goto success;
-    }
+    } else if (dbus_message_is_signal(msg,
+                                     "org.bluez.Network",
+                                     "PropertyChanged")) {
+
+       jobjectArray str_array =
+                   parse_pan_property_change(env, msg);
+       if (str_array != NULL) {
+           const char *c_path = dbus_message_get_path(msg);
+           env->CallVoidMethod(nat->me,
+                               method_onPanDevicePropertyChanged,
+                               env->NewStringUTF(c_path),
+                               str_array);
+       } else {
+           LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg);
+       }
+       goto success;
+   }
 
     ret = a2dp_event_filter(msg, env);
     env->PopLocalFrame(NULL);
@@ -1273,6 +1295,31 @@
     free(user);
 }
 
+void onPanDeviceConnectionResult(DBusMessage *msg, void *user, void *n) {
+    LOGV(__FUNCTION__);
+
+    native_data_t *nat = (native_data_t *)n;
+    const char *path = (const char *)user;
+    DBusError err;
+    dbus_error_init(&err);
+    JNIEnv *env;
+    nat->vm->GetEnv((void**)&env, nat->envVer);
+
+    bool result = JNI_TRUE;
+    if (dbus_set_error_from_message(&err, msg)) {
+        LOG_AND_FREE_DBUS_ERROR(&err);
+        result = JNI_FALSE;
+    }
+    LOGV("... Pan Device Path = %s, result = %d", path, result);
+    jstring jPath = env->NewStringUTF(path);
+    env->CallVoidMethod(nat->me,
+                        method_onPanDeviceConnectionResult,
+                        jPath,
+                        result);
+    env->DeleteLocalRef(jPath);
+    free(user);
+}
+
 #endif
 
 static JNINativeMethod sMethods[] = {
diff --git a/core/jni/android_server_BluetoothService.cpp b/core/jni/android_server_BluetoothService.cpp
index 69a8003..74127cf 100644
--- a/core/jni/android_server_BluetoothService.cpp
+++ b/core/jni/android_server_BluetoothService.cpp
@@ -17,6 +17,9 @@
 #define DBUS_ADAPTER_IFACE BLUEZ_DBUS_BASE_IFC ".Adapter"
 #define DBUS_DEVICE_IFACE BLUEZ_DBUS_BASE_IFC ".Device"
 #define DBUS_INPUT_IFACE BLUEZ_DBUS_BASE_IFC ".Input"
+#define DBUS_NETWORK_IFACE BLUEZ_DBUS_BASE_IFC ".Network"
+#define DBUS_NETWORKSERVER_IFACE BLUEZ_DBUS_BASE_IFC ".NetworkServer"
+
 
 #define LOG_TAG "BluetoothService.cpp"
 
@@ -71,6 +74,8 @@
 void onDiscoverServicesResult(DBusMessage *msg, void *user, void *nat);
 void onCreateDeviceResult(DBusMessage *msg, void *user, void *nat);
 void onInputDeviceConnectionResult(DBusMessage *msg, void *user, void *nat);
+void onConnectPanResult(DBusMessage *msg, void *user, void *n);
+void onPanDeviceConnectionResult(DBusMessage *msg, void *user, void *nat);
 
 
 /** Get native data stored in the opaque (Java code maintained) pointer mNativeData
@@ -939,6 +944,105 @@
     return JNI_FALSE;
 }
 
+static jboolean setBluetoothTetheringNative(JNIEnv *env, jobject object, jboolean value,
+                                            jstring src_role, jstring bridge) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = get_native_data(env, object);
+    if (nat) {
+        DBusMessage *reply;
+        const char *c_role = env->GetStringUTFChars(src_role, NULL);
+        const char *c_bridge = env->GetStringUTFChars(bridge, NULL);
+        if (value) {
+            LOGE("setBluetoothTetheringNative true");
+            reply = dbus_func_args(env, nat->conn,
+                                  get_adapter_path(env, object),
+                                  DBUS_NETWORKSERVER_IFACE,
+                                  "Register",
+                                  DBUS_TYPE_STRING, &c_role,
+                                  DBUS_TYPE_STRING, &c_bridge,
+                                  DBUS_TYPE_INVALID);
+        } else {
+            LOGE("setBluetoothTetheringNative false");
+            reply = dbus_func_args(env, nat->conn,
+                                  get_adapter_path(env, object),
+                                  DBUS_NETWORKSERVER_IFACE,
+                                  "Unregister",
+                                  DBUS_TYPE_STRING, &c_role,
+                                  DBUS_TYPE_INVALID);
+        }
+        env->ReleaseStringUTFChars(src_role, c_role);
+        env->ReleaseStringUTFChars(bridge, c_bridge);
+        return reply ? JNI_TRUE : JNI_FALSE;
+    }
+#endif
+    return JNI_FALSE;
+}
+
+static jboolean connectPanDeviceNative(JNIEnv *env, jobject object, jstring path,
+                                       jstring srcRole, jstring dstRole) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    LOGE("connectPanDeviceNative");
+    native_data_t *nat = get_native_data(env, object);
+    jobject eventLoop = env->GetObjectField(object, field_mEventLoop);
+    struct event_loop_native_data_t *eventLoopNat =
+            get_EventLoop_native_data(env, eventLoop);
+
+    if (nat && eventLoopNat) {
+        const char *c_path = env->GetStringUTFChars(path, NULL);
+        const char *src = env->GetStringUTFChars(srcRole, NULL);
+        const char *dst = env->GetStringUTFChars(dstRole, NULL);
+
+        int len = env->GetStringLength(path) + 1;
+        char *context_path = (char *)calloc(len, sizeof(char));
+        strlcpy(context_path, c_path, len);  // for callback
+
+        bool ret = dbus_func_args_async(env, nat->conn, -1,onPanDeviceConnectionResult,
+                                    context_path, eventLoopNat, c_path,
+                                    DBUS_NETWORK_IFACE, "Connect",
+                                    DBUS_TYPE_STRING, &src,
+                                    DBUS_TYPE_STRING, &dst,
+                                    DBUS_TYPE_INVALID);
+
+        env->ReleaseStringUTFChars(path, c_path);
+        env->ReleaseStringUTFChars(srcRole, src);
+        env->ReleaseStringUTFChars(dstRole, dst);
+        return ret ? JNI_TRUE : JNI_FALSE;
+    }
+#endif
+    return JNI_FALSE;
+}
+
+static jboolean disconnectPanDeviceNative(JNIEnv *env, jobject object,
+                                     jstring path) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    LOGE("disconnectPanDeviceNative");
+    native_data_t *nat = get_native_data(env, object);
+    jobject eventLoop = env->GetObjectField(object, field_mEventLoop);
+    struct event_loop_native_data_t *eventLoopNat =
+            get_EventLoop_native_data(env, eventLoop);
+
+    if (nat && eventLoopNat) {
+        const char *c_path = env->GetStringUTFChars(path, NULL);
+
+        int len = env->GetStringLength(path) + 1;
+        char *context_path = (char *)calloc(len, sizeof(char));
+        strlcpy(context_path, c_path, len);  // for callback
+
+        bool ret = dbus_func_args_async(env, nat->conn, -1,onPanDeviceConnectionResult,
+                                        context_path, eventLoopNat, c_path,
+                                        DBUS_NETWORK_IFACE, "Disconnect",
+                                        DBUS_TYPE_INVALID);
+
+        env->ReleaseStringUTFChars(path, c_path);
+        return ret ? JNI_TRUE : JNI_FALSE;
+    }
+#endif
+    return JNI_FALSE;
+}
+
 static JNINativeMethod sMethods[] = {
      /* name, signature, funcPtr */
     {"classInitNative", "()V", (void*)classInitNative},
@@ -987,8 +1091,15 @@
     // HID functions
     {"connectInputDeviceNative", "(Ljava/lang/String;)Z", (void *)connectInputDeviceNative},
     {"disconnectInputDeviceNative", "(Ljava/lang/String;)Z", (void *)disconnectInputDeviceNative},
+
+    {"setBluetoothTetheringNative", "(ZLjava/lang/String;Ljava/lang/String;)Z",
+              (void *)setBluetoothTetheringNative},
+    {"connectPanDeviceNative", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z",
+              (void *)connectPanDeviceNative},
+    {"disconnectPanDeviceNative", "(Ljava/lang/String;)Z", (void *)disconnectPanDeviceNative},
 };
 
+
 int register_android_server_BluetoothService(JNIEnv *env) {
     return AndroidRuntime::registerNativeMethods(env,
                 "android/server/BluetoothService", sMethods, NELEM(sMethods));
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 4430ea0..3e3f47c 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -121,6 +121,12 @@
     <string-array translatable="false" name="config_tether_wifi_regexs">
     </string-array>
 
+    <!-- List of regexpressions describing the interface (if any) that represent tetherable
+         bluetooth interfaces.  If the device doesn't want to support tethering over bluetooth this
+         should be empty. -->
+    <string-array translatable="false" name="config_tether_bluetooth_regexs">
+    </string-array>
+
     <!-- Dhcp range (min, max) to use for tethering purposes -->
     <string-array translatable="false" name="config_tether_dhcp_range">
     </string-array>
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 859353a..4da0bac 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -344,7 +344,8 @@
         mTetheringConfigValid = (((mNetTrackers[ConnectivityManager.TYPE_MOBILE_DUN] != null) ||
                                   !mTethering.isDunRequired()) &&
                                  (mTethering.getTetherableUsbRegexs().length != 0 ||
-                                  mTethering.getTetherableWifiRegexs().length != 0) &&
+                                  mTethering.getTetherableWifiRegexs().length != 0 ||
+                                  mTethering.getTetherableBluetoothRegexs().length != 0) &&
                                  mTethering.getUpstreamIfaceRegexs().length != 0);
 
     }
@@ -1676,6 +1677,15 @@
         }
     }
 
+    public String[] getTetherableBluetoothRegexs() {
+        enforceTetherAccessPermission();
+        if (isTetheringSupported()) {
+            return mTethering.getTetherableBluetoothRegexs();
+        } else {
+            return new String[0];
+        }
+    }
+
     // TODO - move iface listing, queries, etc to new module
     // javadoc from interface
     public String[] getTetherableIfaces() {
diff --git a/services/java/com/android/server/NativeDaemonConnector.java b/services/java/com/android/server/NativeDaemonConnector.java
index c452590..249c32b 100644
--- a/services/java/com/android/server/NativeDaemonConnector.java
+++ b/services/java/com/android/server/NativeDaemonConnector.java
@@ -128,11 +128,12 @@
                                     Slog.e(TAG, String.format(
                                             "Error handling '%s'", event), ex);
                                 }
-                            }
-                            try {
-                                mResponseQueue.put(event);
-                            } catch (InterruptedException ex) {
-                                Slog.e(TAG, "Failed to put response onto queue", ex);
+                            } else {
+                                try {
+                                    mResponseQueue.put(event);
+                                } catch (InterruptedException ex) {
+                                    Slog.e(TAG, "Failed to put response onto queue", ex);
+                                }
                             }
                         } catch (NumberFormatException nfe) {
                             Slog.w(TAG, String.format("Bad msg (%s)", event));
@@ -209,6 +210,7 @@
      */
     public synchronized ArrayList<String> doCommand(String cmd)
             throws NativeDaemonConnectorException  {
+        mResponseQueue.clear();
         sendCommand(cmd);
 
         ArrayList<String> response = new ArrayList<String>();
diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java
index acedd26..949b874 100644
--- a/services/java/com/android/server/connectivity/Tethering.java
+++ b/services/java/com/android/server/connectivity/Tethering.java
@@ -19,8 +19,8 @@
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.bluetooth.BluetoothPan;
 import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -32,7 +32,6 @@
 import android.net.IConnectivityManager;
 import android.net.INetworkManagementEventObserver;
 import android.net.NetworkInfo;
-import android.net.NetworkUtils;
 import android.os.Binder;
 import android.os.Environment;
 import android.os.HandlerThread;
@@ -53,6 +52,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.LinkedList;
 import java.util.Set;
 /**
  * @hide
@@ -75,6 +75,7 @@
     // TODO - remove both of these - should be part of interface inspection/selection stuff
     private String[] mTetherableUsbRegexs;
     private String[] mTetherableWifiRegexs;
+    private String[] mTetherableBluetoothRegexs;
     private String[] mUpstreamIfaceRegexs;
 
     private Looper mLooper;
@@ -94,6 +95,8 @@
     private static final String DHCP_DEFAULT_RANGE1_STOP  = "192.168.42.254";
     private static final String DHCP_DEFAULT_RANGE2_START = "192.168.43.2";
     private static final String DHCP_DEFAULT_RANGE2_STOP  = "192.168.43.254";
+    private static final String DHCP_DEFAULT_RANGE3_START = "192.168.44.9";
+    private static final String DHCP_DEFAULT_RANGE3_STOP  = "192.168.44.254";
 
     private String[] mDnsServers;
     private static final String DNS_DEFAULT_SERVER1 = "8.8.8.8";
@@ -153,11 +156,13 @@
         mDhcpRange = context.getResources().getStringArray(
                 com.android.internal.R.array.config_tether_dhcp_range);
         if ((mDhcpRange.length == 0) || (mDhcpRange.length % 2 ==1)) {
-            mDhcpRange = new String[4];
+            mDhcpRange = new String[6];
             mDhcpRange[0] = DHCP_DEFAULT_RANGE1_START;
             mDhcpRange[1] = DHCP_DEFAULT_RANGE1_STOP;
             mDhcpRange[2] = DHCP_DEFAULT_RANGE2_START;
             mDhcpRange[3] = DHCP_DEFAULT_RANGE2_STOP;
+            mDhcpRange[4] = DHCP_DEFAULT_RANGE3_START;
+            mDhcpRange[5] = DHCP_DEFAULT_RANGE3_STOP;
         }
         mDunRequired = false; // resample when we turn on
 
@@ -165,6 +170,8 @@
                 com.android.internal.R.array.config_tether_usb_regexs);
         mTetherableWifiRegexs = context.getResources().getStringArray(
                 com.android.internal.R.array.config_tether_wifi_regexs);
+        mTetherableBluetoothRegexs = context.getResources().getStringArray(
+                com.android.internal.R.array.config_tether_bluetooth_regexs);
         mUpstreamIfaceRegexs = context.getResources().getStringArray(
                 com.android.internal.R.array.config_tether_upstream_regexs);
 
@@ -183,6 +190,8 @@
         } else if (isUsb(iface)) {
             found = true;
             usb = true;
+        } else if (isBluetooth(iface)) {
+            found = true;
         }
         if (found == false) return;
 
@@ -217,6 +226,12 @@
         return false;
     }
 
+    public boolean isBluetooth(String iface) {
+        for (String regex : mTetherableBluetoothRegexs) {
+            if (iface.matches(regex)) return true;
+        }
+        return false;
+    }
     public void interfaceAdded(String iface) {
         IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
         INetworkManagementService service = INetworkManagementService.Stub.asInterface(b);
@@ -229,6 +244,9 @@
             found = true;
             usb = true;
         }
+        if (isBluetooth(iface)) {
+            found = true;
+        }
         if (found == false) {
             if (DEBUG) Log.d(TAG, iface + " is not a tetherable iface, ignoring");
             return;
@@ -324,13 +342,14 @@
 
         boolean wifiTethered = false;
         boolean usbTethered = false;
+        boolean bluetoothTethered = false;
 
         synchronized (mIfaces) {
             Set ifaces = mIfaces.keySet();
             for (Object iface : ifaces) {
                 TetherInterfaceSM sm = mIfaces.get(iface);
                 if (sm != null) {
-                    if(sm.isErrored()) {
+                    if (sm.isErrored()) {
                         erroredList.add((String)iface);
                     } else if (sm.isAvailable()) {
                         availableList.add((String)iface);
@@ -339,6 +358,8 @@
                             usbTethered = true;
                         } else if (isWifi((String)iface)) {
                             wifiTethered = true;
+                      } else if (isBluetooth((String)iface)) {
+                            bluetoothTethered = true;
                         }
                         activeList.add((String)iface);
                     }
@@ -359,13 +380,19 @@
         }
 
         if (usbTethered) {
-            if (wifiTethered) {
+            if (wifiTethered || bluetoothTethered) {
                 showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_general);
             } else {
                 showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_usb);
             }
         } else if (wifiTethered) {
-            showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_wifi);
+            if (bluetoothTethered) {
+                showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_general);
+            } else {
+                showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_wifi);
+            }
+        } else if (bluetoothTethered) {
+            showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_bluetooth);
         } else {
             clearTetheredNotification();
         }
@@ -396,7 +423,7 @@
         CharSequence message = r.getText(com.android.internal.R.string.
                 tethered_notification_message);
 
-        if(mTetheredNotification == null) {
+        if (mTetheredNotification == null) {
             mTetheredNotification = new Notification();
             mTetheredNotification.when = 0;
         }
@@ -471,7 +498,7 @@
         }
     }
 
-    // toggled when we enter/leave the fully teathered state
+    // toggled when we enter/leave the fully tethered state
     private boolean enableUsbRndis(boolean enabled) {
         if (DEBUG) Log.d(TAG, "enableUsbRndis(" + enabled + ")");
         IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
@@ -554,6 +581,10 @@
         return mTetherableWifiRegexs;
     }
 
+    public String[] getTetherableBluetoothRegexs() {
+        return mTetherableBluetoothRegexs;
+    }
+
     public String[] getUpstreamIfaceRegexs() {
         return mUpstreamIfaceRegexs;
     }