blob: aa5a1fd312ab5be05ca67f476ee2aaf6ad6ec85c [file] [log] [blame]
/*
* Copyright (C) 2012 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.bluetooth.pan;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothPan;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.IBluetoothPan;
import android.content.Context;
import android.content.Intent;
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.Handler;
import android.os.IBinder;
import android.os.INetworkManagementService;
import android.os.Message;
import android.os.ServiceManager;
import android.os.UserManager;
import android.util.Log;
import com.android.bluetooth.BluetoothMetricsProto;
import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.MetricsLogger;
import com.android.bluetooth.btservice.ProfileService;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* Provides Bluetooth Pan Device profile, as a service in
* the Bluetooth application.
* @hide
*/
public class PanService extends ProfileService {
private static final String TAG = "PanService";
private static final boolean DBG = false;
private static PanService sPanService;
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;
private HashMap<BluetoothDevice, BluetoothPanDevice> mPanDevices;
private ArrayList<String> mBluetoothIfaceAddresses;
private int mMaxPanDevices;
private String mPanIfName;
private String mNapIfaceAddr;
private boolean mNativeAvailable;
private static final int MESSAGE_CONNECT = 1;
private static final int MESSAGE_DISCONNECT = 2;
private static final int MESSAGE_CONNECT_STATE_CHANGED = 11;
private boolean mTetherOn = false;
private BluetoothTetheringNetworkFactory mNetworkFactory;
static {
classInitNative();
}
@Override
public IProfileServiceBinder initBinder() {
return new BluetoothPanBinder(this);
}
public static synchronized PanService getPanService() {
if (sPanService == null) {
Log.w(TAG, "getPanService(): service is null");
return null;
}
if (!sPanService.isAvailable()) {
Log.w(TAG, "getPanService(): service is not available ");
return null;
}
return sPanService;
}
private static synchronized void setPanService(PanService instance) {
if (DBG) {
Log.d(TAG, "setPanService(): set to: " + instance);
}
sPanService = instance;
}
@Override
protected boolean start() {
mPanDevices = new HashMap<BluetoothDevice, BluetoothPanDevice>();
mBluetoothIfaceAddresses = new ArrayList<String>();
try {
mMaxPanDevices = getResources().getInteger(
com.android.internal.R.integer.config_max_pan_devices);
} catch (NotFoundException e) {
mMaxPanDevices = BLUETOOTH_MAX_PAN_CONNECTIONS;
}
initializeNative();
mNativeAvailable = true;
mNetworkFactory =
new BluetoothTetheringNetworkFactory(getBaseContext(), getMainLooper(), this);
setPanService(this);
return true;
}
@Override
protected boolean stop() {
mHandler.removeCallbacksAndMessages(null);
return true;
}
@Override
protected void cleanup() {
// TODO(b/72948646): this should be moved to stop()
setPanService(null);
if (mNativeAvailable) {
cleanupNative();
mNativeAvailable = false;
}
if (mPanDevices != null) {
int[] desiredStates = {BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.STATE_DISCONNECTING};
List<BluetoothDevice> devList =
getDevicesMatchingConnectionStates(desiredStates);
for (BluetoothDevice device : devList) {
BluetoothPanDevice panDevice = mPanDevices.get(device);
Log.d(TAG, "panDevice: " + panDevice + " device address: " + device);
if (panDevice != null) {
handlePanDeviceStateChange(device, mPanIfName,
BluetoothProfile.STATE_DISCONNECTED,
panDevice.mLocalRole, panDevice.mRemoteRole);
}
}
mPanDevices.clear();
}
}
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_CONNECT: {
BluetoothDevice device = (BluetoothDevice) msg.obj;
if (!connectPanNative(Utils.getByteAddress(device),
BluetoothPan.LOCAL_PANU_ROLE, BluetoothPan.REMOTE_NAP_ROLE)) {
handlePanDeviceStateChange(device, null, BluetoothProfile.STATE_CONNECTING,
BluetoothPan.LOCAL_PANU_ROLE, BluetoothPan.REMOTE_NAP_ROLE);
handlePanDeviceStateChange(device, null,
BluetoothProfile.STATE_DISCONNECTED, BluetoothPan.LOCAL_PANU_ROLE,
BluetoothPan.REMOTE_NAP_ROLE);
break;
}
}
break;
case MESSAGE_DISCONNECT: {
BluetoothDevice device = (BluetoothDevice) msg.obj;
if (!disconnectPanNative(Utils.getByteAddress(device))) {
handlePanDeviceStateChange(device, mPanIfName,
BluetoothProfile.STATE_DISCONNECTING, BluetoothPan.LOCAL_PANU_ROLE,
BluetoothPan.REMOTE_NAP_ROLE);
handlePanDeviceStateChange(device, mPanIfName,
BluetoothProfile.STATE_DISCONNECTED, BluetoothPan.LOCAL_PANU_ROLE,
BluetoothPan.REMOTE_NAP_ROLE);
break;
}
}
break;
case MESSAGE_CONNECT_STATE_CHANGED: {
ConnectState cs = (ConnectState) msg.obj;
BluetoothDevice device = getDevice(cs.addr);
// TBD get iface from the msg
if (DBG) {
Log.d(TAG,
"MESSAGE_CONNECT_STATE_CHANGED: " + device + " state: " + cs.state);
}
handlePanDeviceStateChange(device, mPanIfName /* iface */,
convertHalState(cs.state), cs.local_role, cs.remote_role);
}
break;
}
}
};
/**
* Handlers for incoming service calls
*/
private static class BluetoothPanBinder extends IBluetoothPan.Stub
implements IProfileServiceBinder {
private PanService mService;
BluetoothPanBinder(PanService svc) {
mService = svc;
}
@Override
public void cleanup() {
mService = null;
}
private PanService getService() {
if (!Utils.checkCaller()) {
Log.w(TAG, "Pan call not allowed for non-active user");
return null;
}
if (mService != null && mService.isAvailable()) {
return mService;
}
return null;
}
@Override
public boolean connect(BluetoothDevice device) {
PanService service = getService();
if (service == null) {
return false;
}
return service.connect(device);
}
@Override
public boolean disconnect(BluetoothDevice device) {
PanService service = getService();
if (service == null) {
return false;
}
return service.disconnect(device);
}
@Override
public int getConnectionState(BluetoothDevice device) {
PanService service = getService();
if (service == null) {
return BluetoothPan.STATE_DISCONNECTED;
}
return service.getConnectionState(device);
}
private boolean isPanNapOn() {
PanService service = getService();
if (service == null) {
return false;
}
return service.isPanNapOn();
}
private boolean isPanUOn() {
if (DBG) {
Log.d(TAG, "isTetheringOn call getPanLocalRoleNative");
}
PanService service = getService();
if (service == null) {
return false;
}
return service.isPanUOn();
}
@Override
public boolean isTetheringOn() {
// TODO(BT) have a variable marking the on/off state
PanService service = getService();
if (service == null) {
return false;
}
return service.isTetheringOn();
}
@Override
public void setBluetoothTethering(boolean value, String pkgName) {
PanService service = getService();
if (service == null) {
return;
}
Log.d(TAG, "setBluetoothTethering: " + value + ", pkgName: " + pkgName
+ ", mTetherOn: " + service.mTetherOn);
service.setBluetoothTethering(value, pkgName);
}
@Override
public List<BluetoothDevice> getConnectedDevices() {
PanService service = getService();
if (service == null) {
return new ArrayList<BluetoothDevice>(0);
}
return service.getConnectedDevices();
}
@Override
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
PanService service = getService();
if (service == null) {
return new ArrayList<BluetoothDevice>(0);
}
return service.getDevicesMatchingConnectionStates(states);
}
}
;
public boolean connect(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
if (getConnectionState(device) != BluetoothProfile.STATE_DISCONNECTED) {
Log.e(TAG, "Pan Device not disconnected: " + device);
return false;
}
Message msg = mHandler.obtainMessage(MESSAGE_CONNECT, device);
mHandler.sendMessage(msg);
return true;
}
public boolean disconnect(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
Message msg = mHandler.obtainMessage(MESSAGE_DISCONNECT, device);
mHandler.sendMessage(msg);
return true;
}
public int getConnectionState(BluetoothDevice device) {
BluetoothPanDevice panDevice = mPanDevices.get(device);
if (panDevice == null) {
return BluetoothPan.STATE_DISCONNECTED;
}
return panDevice.mState;
}
boolean isPanNapOn() {
if (DBG) {
Log.d(TAG, "isTetheringOn call getPanLocalRoleNative");
}
return (getPanLocalRoleNative() & BluetoothPan.LOCAL_NAP_ROLE) != 0;
}
boolean isPanUOn() {
if (DBG) {
Log.d(TAG, "isTetheringOn call getPanLocalRoleNative");
}
return (getPanLocalRoleNative() & BluetoothPan.LOCAL_PANU_ROLE) != 0;
}
public boolean isTetheringOn() {
// TODO(BT) have a variable marking the on/off state
return mTetherOn;
}
void setBluetoothTethering(boolean value, final String pkgName) {
if (DBG) {
Log.d(TAG, "setBluetoothTethering: " + value + ", mTetherOn: " + mTetherOn);
}
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
final Context context = getBaseContext();
ConnectivityManager.enforceTetherChangePermission(context, pkgName);
UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
if (um.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING) && value) {
throw new SecurityException("DISALLOW_CONFIG_TETHERING is enabled for this user.");
}
if (mTetherOn != value) {
//drop any existing panu or pan-nap connection when changing the tethering state
mTetherOn = value;
List<BluetoothDevice> devList = getConnectedDevices();
for (BluetoothDevice dev : devList) {
disconnect(dev);
}
}
}
public boolean setPriority(BluetoothDevice device, int priority) {
if (device == null) {
throw new IllegalArgumentException("Null device");
}
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
if (DBG) {
Log.d(TAG, "Saved priority " + device + " = " + priority);
}
AdapterService.getAdapterService().getDatabase()
.setProfilePriority(device, BluetoothProfile.PAN, priority);
return true;
}
public int getPriority(BluetoothDevice device) {
if (device == null) {
throw new IllegalArgumentException("Null device");
}
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
return AdapterService.getAdapterService().getDatabase()
.getProfilePriority(device, BluetoothProfile.PAN);
}
public List<BluetoothDevice> getConnectedDevices() {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
List<BluetoothDevice> devices =
getDevicesMatchingConnectionStates(new int[]{BluetoothProfile.STATE_CONNECTED});
return devices;
}
List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
List<BluetoothDevice> panDevices = new ArrayList<BluetoothDevice>();
for (BluetoothDevice device : mPanDevices.keySet()) {
int panDeviceState = getConnectionState(device);
for (int state : states) {
if (state == panDeviceState) {
panDevices.add(device);
break;
}
}
}
return panDevices;
}
protected static class ConnectState {
public ConnectState(byte[] address, int state, int error, int localRole, int remoteRole) {
this.addr = address;
this.state = state;
this.error = error;
this.local_role = localRole;
this.remote_role = remoteRole;
}
public byte[] addr;
public int state;
public int error;
public int local_role;
public int remote_role;
}
;
private void onConnectStateChanged(byte[] address, int state, int error, int localRole,
int remoteRole) {
if (DBG) {
Log.d(TAG, "onConnectStateChanged: " + state + ", local role:" + localRole
+ ", remoteRole: " + remoteRole);
}
Message msg = mHandler.obtainMessage(MESSAGE_CONNECT_STATE_CHANGED);
msg.obj = new ConnectState(address, state, error, localRole, remoteRole);
mHandler.sendMessage(msg);
}
private void onControlStateChanged(int localRole, int state, int error, String ifname) {
if (DBG) {
Log.d(TAG, "onControlStateChanged: " + state + ", error: " + error + ", ifname: "
+ ifname);
}
if (error == 0) {
mPanIfName = ifname;
}
}
private static int convertHalState(int halState) {
switch (halState) {
case CONN_STATE_CONNECTED:
return BluetoothProfile.STATE_CONNECTED;
case CONN_STATE_CONNECTING:
return BluetoothProfile.STATE_CONNECTING;
case CONN_STATE_DISCONNECTED:
return BluetoothProfile.STATE_DISCONNECTED;
case CONN_STATE_DISCONNECTING:
return BluetoothProfile.STATE_DISCONNECTING;
default:
Log.e(TAG, "bad pan connection state: " + halState);
return BluetoothProfile.STATE_DISCONNECTED;
}
}
void handlePanDeviceStateChange(BluetoothDevice device, String iface, int state, int localRole,
int remoteRole) {
if (DBG) {
Log.d(TAG, "handlePanDeviceStateChange: device: " + device + ", iface: " + iface
+ ", state: " + state + ", localRole:" + localRole + ", remoteRole:"
+ remoteRole);
}
int prevState;
BluetoothPanDevice panDevice = mPanDevices.get(device);
if (panDevice == null) {
Log.i(TAG, "state " + state + " Num of connected pan devices: " + mPanDevices.size());
prevState = BluetoothProfile.STATE_DISCONNECTED;
panDevice = new BluetoothPanDevice(state, iface, localRole, remoteRole);
mPanDevices.put(device, panDevice);
} else {
prevState = panDevice.mState;
panDevice.mState = state;
panDevice.mLocalRole = localRole;
panDevice.mRemoteRole = remoteRole;
panDevice.mIface = iface;
}
// Avoid race condition that gets this class stuck in STATE_DISCONNECTING. While we
// are in STATE_CONNECTING, if a BluetoothPan#disconnect call comes in, the original
// connect call will put us in STATE_DISCONNECTED. Then, the disconnect completes and
// changes the state to STATE_DISCONNECTING. All future calls to BluetoothPan#connect
// will fail until the caller explicitly calls BluetoothPan#disconnect.
if (prevState == BluetoothProfile.STATE_DISCONNECTED
&& state == BluetoothProfile.STATE_DISCONNECTING) {
Log.d(TAG, "Ignoring state change from " + prevState + " to " + state);
mPanDevices.remove(device);
return;
}
Log.d(TAG, "handlePanDeviceStateChange preState: " + prevState + " state: " + state);
if (prevState == state) {
return;
}
if (remoteRole == BluetoothPan.LOCAL_PANU_ROLE) {
if (state == BluetoothProfile.STATE_CONNECTED) {
if ((!mTetherOn) || (localRole == BluetoothPan.LOCAL_PANU_ROLE)) {
Log.d(TAG, "handlePanDeviceStateChange BT tethering is off/Local role"
+ " is PANU drop the connection");
mPanDevices.remove(device);
disconnectPanNative(Utils.getByteAddress(device));
return;
}
Log.d(TAG, "handlePanDeviceStateChange LOCAL_NAP_ROLE:REMOTE_PANU_ROLE");
if (mNapIfaceAddr == null) {
mNapIfaceAddr = startTethering(iface);
if (mNapIfaceAddr == null) {
Log.e(TAG, "Error seting up tether interface");
mPanDevices.remove(device);
disconnectPanNative(Utils.getByteAddress(device));
return;
}
}
} else if (state == BluetoothProfile.STATE_DISCONNECTED) {
mPanDevices.remove(device);
Log.i(TAG, "remote(PANU) is disconnected, Remaining connected PANU devices: "
+ mPanDevices.size());
if (mNapIfaceAddr != null && mPanDevices.size() == 0) {
stopTethering(iface);
mNapIfaceAddr = null;
}
}
} else if (mNetworkFactory != null) {
// PANU Role = reverse Tether
Log.d(TAG, "handlePanDeviceStateChange LOCAL_PANU_ROLE:REMOTE_NAP_ROLE state = " + state
+ ", prevState = " + prevState);
if (state == BluetoothProfile.STATE_CONNECTED) {
mNetworkFactory.startReverseTether(iface);
} else if (state == BluetoothProfile.STATE_DISCONNECTED) {
mNetworkFactory.stopReverseTether();
mPanDevices.remove(device);
}
}
if (state == BluetoothProfile.STATE_CONNECTED) {
MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.PAN);
}
/* Notifying the connection state change of the profile before sending the intent for
connection state change, as it was causing a race condition, with the UI not being
updated with the correct connection state. */
Log.d(TAG, "Pan Device state : device: " + device + " State:" + prevState + "->" + state);
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, localRole);
sendBroadcast(intent, BLUETOOTH_PERM);
}
private String startTethering(String iface) {
return configureBtIface(true, iface);
}
private String stopTethering(String iface) {
return configureBtIface(false, iface);
}
private String configureBtIface(boolean enable, String iface) {
Log.i(TAG, "configureBtIface: " + iface + " enable: " + enable);
IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
INetworkManagementService service = INetworkManagementService.Stub.asInterface(b);
ConnectivityManager cm =
(ConnectivityManager) 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;
}
InterfaceConfiguration ifcg = null;
String address = null;
try {
ifcg = service.getInterfaceConfig(iface);
if (ifcg != null) {
InetAddress addr = null;
LinkAddress linkAddr = ifcg.getLinkAddress();
if (linkAddr == null || (addr = linkAddr.getAddress()) == null || addr.equals(
NetworkUtils.numericToInetAddress("0.0.0.0")) || addr.equals(
NetworkUtils.numericToInetAddress("::0"))) {
address = BLUETOOTH_IFACE_ADDR_START;
addr = NetworkUtils.numericToInetAddress(address);
}
ifcg.setLinkAddress(new LinkAddress(addr, BLUETOOTH_PREFIX_LENGTH));
if (enable) {
ifcg.setInterfaceUp();
} else {
ifcg.setInterfaceDown();
}
ifcg.clearFlag("running");
service.setInterfaceConfig(iface, ifcg);
if (enable) {
int tetherStatus = cm.tether(iface);
if (tetherStatus != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
Log.e(TAG, "Error tethering " + iface + " tetherStatus: " + tetherStatus);
return null;
}
} else {
int untetherStatus = cm.untether(iface);
Log.i(TAG, "Untethered: " + iface + " untetherStatus: " + untetherStatus);
}
}
} catch (Exception e) {
Log.e(TAG, "Error configuring interface " + iface + ", :" + e);
return null;
}
return address;
}
private List<BluetoothDevice> getConnectedPanDevices() {
List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
for (BluetoothDevice device : mPanDevices.keySet()) {
if (getPanDeviceConnectionState(device) == BluetoothProfile.STATE_CONNECTED) {
devices.add(device);
}
}
return devices;
}
private int getPanDeviceConnectionState(BluetoothDevice device) {
BluetoothPanDevice panDevice = mPanDevices.get(device);
if (panDevice == null) {
return BluetoothProfile.STATE_DISCONNECTED;
}
return panDevice.mState;
}
@Override
public void dump(StringBuilder sb) {
super.dump(sb);
println(sb, "mMaxPanDevices: " + mMaxPanDevices);
println(sb, "mPanIfName: " + mPanIfName);
println(sb, "mTetherOn: " + mTetherOn);
println(sb, "mPanDevices:");
for (BluetoothDevice device : mPanDevices.keySet()) {
println(sb, " " + device + " : " + mPanDevices.get(device));
}
}
private class BluetoothPanDevice {
private int mState;
private String mIface;
private int mLocalRole; // Which local role is this PAN device bound to
private int mRemoteRole; // Which remote role is this PAN device bound to
BluetoothPanDevice(int state, String iface, int localRole, int remoteRole) {
mState = state;
mIface = iface;
mLocalRole = localRole;
mRemoteRole = remoteRole;
}
}
// Constants matching Hal header file bt_hh.h
// bthh_connection_state_t
private static final int CONN_STATE_CONNECTED = 0;
private static final int CONN_STATE_CONNECTING = 1;
private static final int CONN_STATE_DISCONNECTED = 2;
private static final int CONN_STATE_DISCONNECTING = 3;
private static native void classInitNative();
private native void initializeNative();
private native void cleanupNative();
private native boolean connectPanNative(byte[] btAddress, int localRole, int remoteRole);
private native boolean disconnectPanNative(byte[] btAddress);
private native boolean enablePanNative(int localRole);
private native int getPanLocalRoleNative();
}