| /* |
| * Copyright (C) 2011 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.server; |
| |
| import android.bluetooth.BluetoothAdapter; |
| import android.bluetooth.BluetoothDevice; |
| import android.bluetooth.BluetoothPan; |
| import android.bluetooth.BluetoothTetheringDataTracker; |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.res.Resources.NotFoundException; |
| import android.net.ConnectivityManager; |
| import android.net.InterfaceConfiguration; |
| import android.net.LinkAddress; |
| import android.net.NetworkUtils; |
| import android.os.IBinder; |
| import android.os.INetworkManagementService; |
| import android.os.ServiceManager; |
| import android.util.Log; |
| |
| import java.net.InetAddress; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| |
| /** |
| * This handles the PAN profile. All calls into this are made |
| * from Bluetooth Service. |
| */ |
| final class BluetoothPanProfileHandler { |
| private static final String TAG = "BluetoothPanProfileHandler"; |
| private static final boolean DBG = true; |
| |
| private ArrayList<String> mBluetoothIfaceAddresses; |
| private int mMaxPanDevices; |
| |
| private static final String BLUETOOTH_IFACE_ADDR_START= "192.168.44.1"; |
| private static final int BLUETOOTH_MAX_PAN_CONNECTIONS = 5; |
| private static final int BLUETOOTH_PREFIX_LENGTH = 24; |
| public static BluetoothPanProfileHandler sInstance; |
| private final HashMap<BluetoothDevice, BluetoothPanDevice> mPanDevices; |
| private boolean mTetheringOn; |
| private Context mContext; |
| private BluetoothService mBluetoothService; |
| |
| static final String NAP_ROLE = "nap"; |
| static final String NAP_BRIDGE = "pan1"; |
| |
| private BluetoothPanProfileHandler(Context context, BluetoothService service) { |
| mContext = context; |
| mPanDevices = new HashMap<BluetoothDevice, BluetoothPanDevice>(); |
| mBluetoothService = service; |
| mTetheringOn = false; |
| mBluetoothIfaceAddresses = new ArrayList<String>(); |
| try { |
| mMaxPanDevices = context.getResources().getInteger( |
| com.android.internal.R.integer.config_max_pan_devices); |
| } catch (NotFoundException e) { |
| mMaxPanDevices = BLUETOOTH_MAX_PAN_CONNECTIONS; |
| } |
| } |
| |
| static synchronized BluetoothPanProfileHandler getInstance(Context context, |
| BluetoothService service) { |
| if (sInstance == null) sInstance = new BluetoothPanProfileHandler(context, service); |
| return sInstance; |
| } |
| |
| synchronized boolean isTetheringOn() { |
| return mTetheringOn; |
| } |
| |
| synchronized boolean allowIncomingTethering() { |
| if (isTetheringOn() && getConnectedPanDevices().size() < mMaxPanDevices) |
| return true; |
| return false; |
| } |
| |
| private BroadcastReceiver mTetheringReceiver = null; |
| |
| synchronized void setBluetoothTethering(boolean value) { |
| if (!value) { |
| disconnectPanServerDevices(); |
| } |
| |
| if (mBluetoothService.getBluetoothState() != BluetoothAdapter.STATE_ON && value) { |
| IntentFilter filter = new IntentFilter(); |
| filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); |
| mTetheringReceiver = new BroadcastReceiver() { |
| @Override |
| public synchronized void onReceive(Context context, Intent intent) { |
| if (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF) |
| == BluetoothAdapter.STATE_ON) { |
| mTetheringOn = true; |
| mContext.unregisterReceiver(mTetheringReceiver); |
| } |
| } |
| }; |
| mContext.registerReceiver(mTetheringReceiver, filter); |
| } else { |
| mTetheringOn = value; |
| } |
| } |
| |
| synchronized int getPanDeviceConnectionState(BluetoothDevice device) { |
| BluetoothPanDevice panDevice = mPanDevices.get(device); |
| if (panDevice == null) { |
| return BluetoothPan.STATE_DISCONNECTED; |
| } |
| return panDevice.mState; |
| } |
| |
| synchronized boolean connectPanDevice(BluetoothDevice device) { |
| String objectPath = mBluetoothService.getObjectPathFromAddress(device.getAddress()); |
| if (DBG) Log.d(TAG, "connect PAN(" + objectPath + ")"); |
| if (getPanDeviceConnectionState(device) != BluetoothPan.STATE_DISCONNECTED) { |
| errorLog(device + " already connected to PAN"); |
| } |
| |
| int connectedCount = 0; |
| for (BluetoothDevice panDevice: mPanDevices.keySet()) { |
| if (getPanDeviceConnectionState(panDevice) == BluetoothPan.STATE_CONNECTED) { |
| connectedCount ++; |
| } |
| } |
| if (connectedCount > 8) { |
| debugLog(device + " could not connect to PAN because 8 other devices are" |
| + "already connected"); |
| return false; |
| } |
| |
| handlePanDeviceStateChange(device, BluetoothPan.STATE_CONNECTING, |
| BluetoothPan.LOCAL_PANU_ROLE); |
| if (mBluetoothService.connectPanDeviceNative(objectPath, "nap")) { |
| debugLog("connecting to PAN"); |
| return true; |
| } else { |
| handlePanDeviceStateChange(device, BluetoothPan.STATE_DISCONNECTED, |
| BluetoothPan.LOCAL_PANU_ROLE); |
| errorLog("could not connect to PAN"); |
| return false; |
| } |
| } |
| |
| private synchronized boolean disconnectPanServerDevices() { |
| debugLog("disconnect all PAN devices"); |
| |
| for (BluetoothDevice device: mPanDevices.keySet()) { |
| BluetoothPanDevice panDevice = mPanDevices.get(device); |
| int state = panDevice.mState; |
| if (state == BluetoothPan.STATE_CONNECTED && |
| panDevice.mLocalRole == BluetoothPan.LOCAL_NAP_ROLE) { |
| String objectPath = mBluetoothService.getObjectPathFromAddress(device.getAddress()); |
| |
| handlePanDeviceStateChange(device, BluetoothPan.STATE_DISCONNECTING, |
| panDevice.mLocalRole); |
| |
| if (!mBluetoothService.disconnectPanServerDeviceNative(objectPath, |
| device.getAddress(), |
| panDevice.mIfaceAddr)) { |
| errorLog("could not disconnect Pan Server Device "+device.getAddress()); |
| |
| // Restore prev state |
| handlePanDeviceStateChange(device, state, |
| panDevice.mLocalRole); |
| |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| synchronized List<BluetoothDevice> getConnectedPanDevices() { |
| List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>(); |
| |
| for (BluetoothDevice device: mPanDevices.keySet()) { |
| if (getPanDeviceConnectionState(device) == BluetoothPan.STATE_CONNECTED) { |
| devices.add(device); |
| } |
| } |
| return devices; |
| } |
| |
| synchronized List<BluetoothDevice> getPanDevicesMatchingConnectionStates(int[] states) { |
| List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>(); |
| |
| for (BluetoothDevice device: mPanDevices.keySet()) { |
| int panDeviceState = getPanDeviceConnectionState(device); |
| for (int state : states) { |
| if (state == panDeviceState) { |
| devices.add(device); |
| break; |
| } |
| } |
| } |
| return devices; |
| } |
| |
| synchronized boolean disconnectPanDevice(BluetoothDevice device) { |
| String objectPath = mBluetoothService.getObjectPathFromAddress(device.getAddress()); |
| debugLog("disconnect PAN(" + objectPath + ")"); |
| |
| int state = getPanDeviceConnectionState(device); |
| if (state != BluetoothPan.STATE_CONNECTED) { |
| debugLog(device + " already disconnected from PAN"); |
| return false; |
| } |
| |
| BluetoothPanDevice panDevice = mPanDevices.get(device); |
| |
| if (panDevice == null) { |
| errorLog("No record for this Pan device:" + device); |
| return false; |
| } |
| |
| handlePanDeviceStateChange(device, BluetoothPan.STATE_DISCONNECTING, |
| panDevice.mLocalRole); |
| if (panDevice.mLocalRole == BluetoothPan.LOCAL_NAP_ROLE) { |
| if (!mBluetoothService.disconnectPanServerDeviceNative(objectPath, device.getAddress(), |
| panDevice.mIface)) { |
| // Restore prev state, this shouldn't happen |
| handlePanDeviceStateChange(device, state, panDevice.mLocalRole); |
| return false; |
| } |
| } else { |
| if (!mBluetoothService.disconnectPanDeviceNative(objectPath)) { |
| // Restore prev state, this shouldn't happen |
| handlePanDeviceStateChange(device, state, panDevice.mLocalRole); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| synchronized void handlePanDeviceStateChange(BluetoothDevice device, |
| String iface, int state, int role) { |
| int prevState; |
| String ifaceAddr = null; |
| BluetoothPanDevice panDevice = mPanDevices.get(device); |
| |
| if (panDevice == null) { |
| prevState = BluetoothPan.STATE_DISCONNECTED; |
| } else { |
| prevState = panDevice.mState; |
| ifaceAddr = panDevice.mIfaceAddr; |
| } |
| if (prevState == state) return; |
| |
| if (role == BluetoothPan.LOCAL_NAP_ROLE) { |
| if (state == BluetoothPan.STATE_CONNECTED) { |
| ifaceAddr = enableTethering(iface); |
| if (ifaceAddr == null) Log.e(TAG, "Error seting up tether interface"); |
| } else if (state == BluetoothPan.STATE_DISCONNECTED) { |
| if (ifaceAddr != null) { |
| mBluetoothIfaceAddresses.remove(ifaceAddr); |
| ifaceAddr = null; |
| } |
| } |
| } else { |
| // PANU Role = reverse Tether |
| if (state == BluetoothPan.STATE_CONNECTED) { |
| BluetoothTetheringDataTracker.getInstance().startReverseTether(iface, device); |
| } else if (state == BluetoothPan.STATE_DISCONNECTED && |
| (prevState == BluetoothPan.STATE_CONNECTED || |
| prevState == BluetoothPan.STATE_DISCONNECTING)) { |
| BluetoothTetheringDataTracker.getInstance().stopReverseTether(panDevice.mIface); |
| } |
| } |
| |
| if (panDevice == null) { |
| panDevice = new BluetoothPanDevice(state, ifaceAddr, iface, role); |
| mPanDevices.put(device, panDevice); |
| } else { |
| panDevice.mState = state; |
| panDevice.mIfaceAddr = ifaceAddr; |
| panDevice.mLocalRole = role; |
| } |
| |
| Intent intent = new Intent(BluetoothPan.ACTION_CONNECTION_STATE_CHANGED); |
| intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); |
| intent.putExtra(BluetoothPan.EXTRA_PREVIOUS_STATE, prevState); |
| intent.putExtra(BluetoothPan.EXTRA_STATE, state); |
| intent.putExtra(BluetoothPan.EXTRA_LOCAL_ROLE, role); |
| mContext.sendBroadcast(intent, BluetoothService.BLUETOOTH_PERM); |
| |
| debugLog("Pan Device state : device: " + device + " State:" + prevState + "->" + state); |
| mBluetoothService.sendConnectionStateChange(device, state, prevState); |
| } |
| |
| synchronized void handlePanDeviceStateChange(BluetoothDevice device, |
| int state, int role) { |
| handlePanDeviceStateChange(device, null, state, role); |
| } |
| |
| private class BluetoothPanDevice { |
| private int mState; |
| private String mIfaceAddr; |
| private String mIface; |
| private int mLocalRole; // Which local role is this PAN device bound to |
| |
| BluetoothPanDevice(int state, String ifaceAddr, String iface, int localRole) { |
| mState = state; |
| mIfaceAddr = ifaceAddr; |
| mIface = iface; |
| mLocalRole = localRole; |
| } |
| } |
| |
| private String createNewTetheringAddressLocked() { |
| if (getConnectedPanDevices().size() == mMaxPanDevices) { |
| debugLog ("Max PAN device connections reached"); |
| return null; |
| } |
| String address = BLUETOOTH_IFACE_ADDR_START; |
| while (true) { |
| if (mBluetoothIfaceAddresses.contains(address)) { |
| String[] addr = address.split("\\."); |
| Integer newIp = Integer.parseInt(addr[2]) + 1; |
| address = address.replace(addr[2], newIp.toString()); |
| } else { |
| break; |
| } |
| } |
| mBluetoothIfaceAddresses.add(address); |
| return address; |
| } |
| |
| // configured when we start tethering |
| private synchronized String enableTethering(String iface) { |
| debugLog("updateTetherState:" + iface); |
| |
| 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[] currentIfaces = new String[0]; |
| try { |
| currentIfaces = service.listInterfaces(); |
| } catch (Exception e) { |
| Log.e(TAG, "Error listing Interfaces :" + e); |
| return null; |
| } |
| |
| boolean found = false; |
| for (String currIface: currentIfaces) { |
| if (currIface.equals(iface)) { |
| found = true; |
| break; |
| } |
| } |
| |
| if (!found) return null; |
| |
| String address = createNewTetheringAddressLocked(); |
| if (address == null) return null; |
| |
| InterfaceConfiguration ifcg = null; |
| try { |
| ifcg = service.getInterfaceConfig(iface); |
| if (ifcg != null) { |
| InetAddress addr = null; |
| if (ifcg.addr == null || (addr = ifcg.addr.getAddress()) == null || |
| addr.equals(NetworkUtils.numericToInetAddress("0.0.0.0")) || |
| addr.equals(NetworkUtils.numericToInetAddress("::0"))) { |
| addr = NetworkUtils.numericToInetAddress(address); |
| } |
| ifcg.interfaceFlags = ifcg.interfaceFlags.replace("down", "up"); |
| ifcg.addr = new LinkAddress(addr, BLUETOOTH_PREFIX_LENGTH); |
| ifcg.interfaceFlags = ifcg.interfaceFlags.replace("running", ""); |
| ifcg.interfaceFlags = ifcg.interfaceFlags.replace(" "," "); |
| service.setInterfaceConfig(iface, ifcg); |
| if (cm.tether(iface) != ConnectivityManager.TETHER_ERROR_NO_ERROR) { |
| Log.e(TAG, "Error tethering "+iface); |
| } |
| } |
| } catch (Exception e) { |
| Log.e(TAG, "Error configuring interface " + iface + ", :" + e); |
| return null; |
| } |
| return address; |
| } |
| |
| private static void debugLog(String msg) { |
| if (DBG) Log.d(TAG, msg); |
| } |
| |
| private static void errorLog(String msg) { |
| Log.e(TAG, msg); |
| } |
| } |