blob: 8925856107e768d87bb0f7a8f41750b5ad4d0707 [file] [log] [blame]
/*
* 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);
}
}