blob: a2038c9321568b5bdce0a37cc50d7bb88fc84336 [file] [log] [blame]
/*
* 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.server;
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHealth;
import android.bluetooth.BluetoothInputDevice;
import android.bluetooth.BluetoothPan;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.Message;
import android.os.ParcelUuid;
import android.os.PowerManager;
import android.util.Log;
import java.util.HashMap;
import java.util.List;
/**
* @hide
*/
class BluetoothEventLoop {
private static final String TAG = "BluetoothEventLoop";
private static final boolean DBG = false;
private int mNativeData;
private Thread mThread;
private boolean mStarted;
private boolean mInterrupted;
private final HashMap<String, Integer> mPasskeyAgentRequestData;
private final HashMap<String, Integer> mAuthorizationAgentRequestData;
private final BluetoothService mBluetoothService;
private final BluetoothAdapter mAdapter;
private final BluetoothAdapterStateMachine mBluetoothState;
private BluetoothA2dp mA2dp;
private final Context mContext;
// The WakeLock is used for bringing up the LCD during a pairing request
// from remote device when Android is in Suspend state.
private PowerManager.WakeLock mWakeLock;
private static final int EVENT_PAIRING_CONSENT_DELAYED_ACCEPT = 1;
private static final int EVENT_AGENT_CANCEL = 2;
private static final int CREATE_DEVICE_ALREADY_EXISTS = 1;
private static final int CREATE_DEVICE_SUCCESS = 0;
private static final int CREATE_DEVICE_FAILED = -1;
private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
String address = null;
switch (msg.what) {
case EVENT_PAIRING_CONSENT_DELAYED_ACCEPT:
address = (String)msg.obj;
if (address != null) {
mBluetoothService.setPairingConfirmation(address, true);
}
break;
case EVENT_AGENT_CANCEL:
// Set the Bond State to BOND_NONE.
// We always have only 1 device in BONDING state.
String[] devices = mBluetoothService.listInState(BluetoothDevice.BOND_BONDING);
if (devices.length == 0) {
break;
} else if (devices.length > 1) {
Log.e(TAG, " There is more than one device in the Bonding State");
break;
}
address = devices[0];
mBluetoothService.setBondState(address,
BluetoothDevice.BOND_NONE,
BluetoothDevice.UNBOND_REASON_REMOTE_AUTH_CANCELED);
break;
}
}
};
static { classInitNative(); }
private static native void classInitNative();
/* package */ BluetoothEventLoop(Context context, BluetoothAdapter adapter,
BluetoothService bluetoothService,
BluetoothAdapterStateMachine bluetoothState) {
mBluetoothService = bluetoothService;
mContext = context;
mBluetoothState = bluetoothState;
mPasskeyAgentRequestData = new HashMap<String, Integer>();
mAuthorizationAgentRequestData = new HashMap<String, Integer>();
mAdapter = adapter;
//WakeLock instantiation in BluetoothEventLoop class
PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP
| PowerManager.ON_AFTER_RELEASE, TAG);
mWakeLock.setReferenceCounted(false);
initializeNativeDataNative();
}
/*package*/ void getProfileProxy() {
mAdapter.getProfileProxy(mContext, mProfileServiceListener, BluetoothProfile.A2DP);
mAdapter.getProfileProxy(mContext, mProfileServiceListener, BluetoothProfile.INPUT_DEVICE);
}
private BluetoothProfile.ServiceListener mProfileServiceListener =
new BluetoothProfile.ServiceListener() {
public void onServiceConnected(int profile, BluetoothProfile proxy) {
if (profile == BluetoothProfile.A2DP) {
mA2dp = (BluetoothA2dp) proxy;
}
}
public void onServiceDisconnected(int profile) {
if (profile == BluetoothProfile.A2DP) {
mA2dp = null;
}
}
};
protected void finalize() throws Throwable {
try {
cleanupNativeDataNative();
} finally {
super.finalize();
}
}
/* package */ HashMap<String, Integer> getPasskeyAgentRequestData() {
return mPasskeyAgentRequestData;
}
/* package */ HashMap<String, Integer> getAuthorizationAgentRequestData() {
return mAuthorizationAgentRequestData;
}
/* package */ void start() {
if (!isEventLoopRunningNative()) {
if (DBG) log("Starting Event Loop thread");
startEventLoopNative();
}
}
public void stop() {
if (isEventLoopRunningNative()) {
if (DBG) log("Stopping Event Loop thread");
stopEventLoopNative();
}
}
public boolean isEventLoopRunning() {
return isEventLoopRunningNative();
}
private void addDevice(String address, String[] properties) {
BluetoothDeviceProperties deviceProperties =
mBluetoothService.getDeviceProperties();
deviceProperties.addProperties(address, properties);
String rssi = deviceProperties.getProperty(address, "RSSI");
String classValue = deviceProperties.getProperty(address, "Class");
String name = deviceProperties.getProperty(address, "Name");
short rssiValue;
// For incoming connections, we don't get the RSSI value. Use a default of MIN_VALUE.
// If we accept the pairing, we will automatically show it at the top of the list.
if (rssi != null) {
rssiValue = (short)Integer.valueOf(rssi).intValue();
} else {
rssiValue = Short.MIN_VALUE;
}
if (classValue != null) {
Intent intent = new Intent(BluetoothDevice.ACTION_FOUND);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
intent.putExtra(BluetoothDevice.EXTRA_CLASS,
new BluetoothClass(Integer.valueOf(classValue)));
intent.putExtra(BluetoothDevice.EXTRA_RSSI, rssiValue);
intent.putExtra(BluetoothDevice.EXTRA_NAME, name);
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
} else {
log ("ClassValue: " + classValue + " for remote device: " + address + " is null");
}
}
/**
* Called by native code on a DeviceFound signal from org.bluez.Adapter.
*
* @param address the MAC address of the new device
* @param properties an array of property keys and value strings
*
* @see BluetoothDeviceProperties#addProperties(String, String[])
*/
private void onDeviceFound(String address, String[] properties) {
if (properties == null) {
Log.e(TAG, "ERROR: Remote device properties are null");
return;
}
addDevice(address, properties);
}
/**
* Called by native code on a DeviceDisappeared signal from
* org.bluez.Adapter.
*
* @param address the MAC address of the disappeared device
*/
private void onDeviceDisappeared(String address) {
Intent intent = new Intent(BluetoothDevice.ACTION_DISAPPEARED);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
}
/**
* Called by native code on a DisconnectRequested signal from
* org.bluez.Device.
*
* @param deviceObjectPath the object path for the disconnecting device
*/
private void onDeviceDisconnectRequested(String deviceObjectPath) {
String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
if (address == null) {
Log.e(TAG, "onDeviceDisconnectRequested: Address of the remote device in null");
return;
}
Intent intent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
}
/**
* Called by native code for the async response to a CreatePairedDevice
* method call to org.bluez.Adapter.
*
* @param address the MAC address of the device to pair
* @param result success or error result for the pairing operation
*/
private void onCreatePairedDeviceResult(String address, int result) {
address = address.toUpperCase();
mBluetoothService.onCreatePairedDeviceResult(address, result);
}
/**
* Called by native code on a DeviceCreated signal from org.bluez.Adapter.
*
* @param deviceObjectPath the object path for the created device
*/
private void onDeviceCreated(String deviceObjectPath) {
String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
if (!mBluetoothService.isRemoteDeviceInCache(address)) {
// Incoming connection, we haven't seen this device, add to cache.
String[] properties = mBluetoothService.getRemoteDeviceProperties(address);
if (properties != null) {
addDevice(address, properties);
}
}
return;
}
/**
* Called by native code on a DeviceRemoved signal from org.bluez.Adapter.
*
* @param deviceObjectPath the object path for the removed device
*/
private void onDeviceRemoved(String deviceObjectPath) {
String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
if (address != null) {
mBluetoothService.setBondState(address.toUpperCase(), BluetoothDevice.BOND_NONE,
BluetoothDevice.UNBOND_REASON_REMOVED);
mBluetoothService.setRemoteDeviceProperty(address, "UUIDs", null);
}
}
/**
* Called by native code on a PropertyChanged signal from
* org.bluez.Adapter. This method is also called from
* {@link BluetoothAdapterStateMachine} to set the "Pairable"
* property when Bluetooth is enabled.
*
* @param propValues a string array containing the key and one or more
* values.
*/
/*package*/ void onPropertyChanged(String[] propValues) {
BluetoothAdapterProperties adapterProperties =
mBluetoothService.getAdapterProperties();
if (adapterProperties.isEmpty()) {
// We have got a property change before
// we filled up our cache.
adapterProperties.getAllProperties();
}
log("Property Changed: " + propValues[0] + " : " + propValues[1]);
String name = propValues[0];
if (name.equals("Name")) {
adapterProperties.setProperty(name, propValues[1]);
Intent intent = new Intent(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED);
intent.putExtra(BluetoothAdapter.EXTRA_LOCAL_NAME, propValues[1]);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
} else if (name.equals("Pairable") || name.equals("Discoverable")) {
adapterProperties.setProperty(name, propValues[1]);
if (name.equals("Discoverable")) {
mBluetoothState.sendMessage(BluetoothAdapterStateMachine.SCAN_MODE_CHANGED);
}
String pairable = name.equals("Pairable") ? propValues[1] :
adapterProperties.getProperty("Pairable");
String discoverable = name.equals("Discoverable") ? propValues[1] :
adapterProperties.getProperty("Discoverable");
// This shouldn't happen, unless Adapter Properties are null.
if (pairable == null || discoverable == null)
return;
int mode = BluetoothService.bluezStringToScanMode(
pairable.equals("true"),
discoverable.equals("true"));
if (mode >= 0) {
Intent intent = new Intent(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
intent.putExtra(BluetoothAdapter.EXTRA_SCAN_MODE, mode);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
}
} else if (name.equals("Discovering")) {
Intent intent;
adapterProperties.setProperty(name, propValues[1]);
if (propValues[1].equals("true")) {
intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
} else {
// Stop the discovery.
mBluetoothService.cancelDiscovery();
intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
}
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
} else if (name.equals("Devices") || name.equals("UUIDs")) {
String value = null;
int len = Integer.valueOf(propValues[1]);
if (len > 0) {
StringBuilder str = new StringBuilder();
for (int i = 2; i < propValues.length; i++) {
str.append(propValues[i]);
str.append(",");
}
value = str.toString();
}
adapterProperties.setProperty(name, value);
if (name.equals("UUIDs")) {
mBluetoothService.updateBluetoothState(value);
}
} else if (name.equals("Powered")) {
mBluetoothState.sendMessage(BluetoothAdapterStateMachine.POWER_STATE_CHANGED,
propValues[1].equals("true") ? new Boolean(true) : new Boolean(false));
} else if (name.equals("DiscoverableTimeout")) {
adapterProperties.setProperty(name, propValues[1]);
}
}
/**
* Called by native code on a PropertyChanged signal from
* org.bluez.Device.
*
* @param deviceObjectPath the object path for the changed device
* @param propValues a string array containing the key and one or more
* values.
*/
private void onDevicePropertyChanged(String deviceObjectPath, String[] propValues) {
String name = propValues[0];
String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
if (address == null) {
Log.e(TAG, "onDevicePropertyChanged: Address of the remote device in null");
return;
}
log("Device property changed: " + address + " property: "
+ name + " value: " + propValues[1]);
BluetoothDevice device = mAdapter.getRemoteDevice(address);
if (name.equals("Name")) {
mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]);
Intent intent = new Intent(BluetoothDevice.ACTION_NAME_CHANGED);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
intent.putExtra(BluetoothDevice.EXTRA_NAME, propValues[1]);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
} else if (name.equals("Alias")) {
mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]);
} else if (name.equals("Class")) {
mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]);
Intent intent = new Intent(BluetoothDevice.ACTION_CLASS_CHANGED);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
intent.putExtra(BluetoothDevice.EXTRA_CLASS,
new BluetoothClass(Integer.valueOf(propValues[1])));
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
} else if (name.equals("Connected")) {
mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]);
Intent intent = null;
if (propValues[1].equals("true")) {
intent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED);
// Set the link timeout to 8000 slots (5 sec timeout)
// for bluetooth docks.
if (mBluetoothService.isBluetoothDock(address)) {
mBluetoothService.setLinkTimeout(address, 8000);
}
} else {
intent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECTED);
}
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
} else if (name.equals("UUIDs")) {
String uuid = null;
int len = Integer.valueOf(propValues[1]);
if (len > 0) {
StringBuilder str = new StringBuilder();
for (int i = 2; i < propValues.length; i++) {
str.append(propValues[i]);
str.append(",");
}
uuid = str.toString();
}
mBluetoothService.setRemoteDeviceProperty(address, name, uuid);
// UUIDs have changed, query remote service channel and update cache.
mBluetoothService.updateDeviceServiceChannelCache(address);
mBluetoothService.sendUuidIntent(address);
} else if (name.equals("Paired")) {
if (propValues[1].equals("true")) {
// If locally initiated pairing, we will
// not go to BOND_BONDED state until we have received a
// successful return value in onCreatePairedDeviceResult
if (null == mBluetoothService.getPendingOutgoingBonding()) {
mBluetoothService.setBondState(address, BluetoothDevice.BOND_BONDED);
}
} else {
mBluetoothService.setBondState(address, BluetoothDevice.BOND_NONE);
mBluetoothService.setRemoteDeviceProperty(address, "Trusted", "false");
}
} else if (name.equals("Trusted")) {
if (DBG)
log("set trust state succeeded, value is: " + propValues[1]);
mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]);
}
}
/**
* Called by native code on a PropertyChanged signal from
* org.bluez.Input.
*
* @param path the object path for the changed input device
* @param propValues a string array containing the key and one or more
* values.
*/
private void onInputDevicePropertyChanged(String path, String[] propValues) {
String address = mBluetoothService.getAddressFromObjectPath(path);
if (address == null) {
Log.e(TAG, "onInputDevicePropertyChanged: Address of the remote device is null");
return;
}
log("Input Device : Name of Property is: " + propValues[0]);
boolean state = false;
if (propValues[1].equals("true")) {
state = true;
}
mBluetoothService.handleInputDevicePropertyChange(address, state);
}
/**
* Called by native code on a PropertyChanged signal from
* org.bluez.Network.
*
* @param deviceObjectPath the object path for the changed PAN device
* @param propValues a string array containing the key and one or more
* values.
*/
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")) {
if (propValues[1].equals("false")) {
mBluetoothService.handlePanDeviceStateChange(device,
BluetoothPan.STATE_DISCONNECTED,
BluetoothPan.LOCAL_PANU_ROLE);
}
} else if (name.equals("Interface")) {
String iface = propValues[1];
if (!iface.equals("")) {
mBluetoothService.handlePanDeviceStateChange(device, iface,
BluetoothPan.STATE_CONNECTED,
BluetoothPan.LOCAL_PANU_ROLE);
}
}
}
private String checkPairingRequestAndGetAddress(String objectPath, int nativeData) {
String address = mBluetoothService.getAddressFromObjectPath(objectPath);
if (address == null) {
Log.e(TAG, "Unable to get device address in checkPairingRequestAndGetAddress, " +
"returning null");
return null;
}
address = address.toUpperCase();
mPasskeyAgentRequestData.put(address, new Integer(nativeData));
if (mBluetoothService.getBluetoothState() == BluetoothAdapter.STATE_TURNING_OFF) {
// shutdown path
mBluetoothService.cancelPairingUserInput(address);
return null;
}
// Set state to BONDING. For incoming connections it will be set here.
// For outgoing connections, it gets set when we call createBond.
// Also set it only when the state is not already Bonded, we can sometimes
// get an authorization request from the remote end if it doesn't have the link key
// while we still have it.
if (mBluetoothService.getBondState(address) != BluetoothDevice.BOND_BONDED)
mBluetoothService.setBondState(address, BluetoothDevice.BOND_BONDING);
return address;
}
/**
* Called by native code on a RequestPairingConsent method call to
* org.bluez.Agent.
*
* @param objectPath the path of the device to request pairing consent for
* @param nativeData a native pointer to the original D-Bus message
*/
private void onRequestPairingConsent(String objectPath, int nativeData) {
String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
if (address == null) return;
/* The link key will not be stored if the incoming request has MITM
* protection switched on. Unfortunately, some devices have MITM
* switched on even though their capabilities are NoInputNoOutput,
* so we may get this request many times. Also if we respond immediately,
* the other end is unable to handle it. Delay sending the message.
*/
if (mBluetoothService.getBondState(address) == BluetoothDevice.BOND_BONDED) {
Message message = mHandler.obtainMessage(EVENT_PAIRING_CONSENT_DELAYED_ACCEPT);
message.obj = address;
mHandler.sendMessageDelayed(message, 1500);
return;
}
// Acquire wakelock during PIN code request to bring up LCD display
mWakeLock.acquire();
Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
BluetoothDevice.PAIRING_VARIANT_CONSENT);
mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
// Release wakelock to allow the LCD to go off after the PIN popup notification.
mWakeLock.release();
return;
}
/**
* Called by native code on a RequestConfirmation method call to
* org.bluez.Agent.
*
* @param objectPath the path of the device to confirm the passkey for
* @param passkey an integer containing the 6-digit passkey to confirm
* @param nativeData a native pointer to the original D-Bus message
*/
private void onRequestPasskeyConfirmation(String objectPath, int passkey, int nativeData) {
String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
if (address == null) return;
// Acquire wakelock during PIN code request to bring up LCD display
mWakeLock.acquire();
Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
intent.putExtra(BluetoothDevice.EXTRA_PAIRING_KEY, passkey);
intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION);
mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
// Release wakelock to allow the LCD to go off after the PIN popup notification.
mWakeLock.release();
return;
}
/**
* Called by native code on a RequestPasskey method call to
* org.bluez.Agent.
*
* @param objectPath the path of the device requesting a passkey
* @param nativeData a native pointer to the original D-Bus message
*/
private void onRequestPasskey(String objectPath, int nativeData) {
String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
if (address == null) return;
// Acquire wakelock during PIN code request to bring up LCD display
mWakeLock.acquire();
Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
BluetoothDevice.PAIRING_VARIANT_PASSKEY);
mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
// Release wakelock to allow the LCD to go off after the PIN popup notification.
mWakeLock.release();
return;
}
/**
* Called by native code on a RequestPinCode method call to
* org.bluez.Agent.
*
* @param objectPath the path of the device requesting a PIN code
* @param nativeData a native pointer to the original D-Bus message
*/
private void onRequestPinCode(String objectPath, int nativeData) {
String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
if (address == null) return;
String pendingOutgoingAddress =
mBluetoothService.getPendingOutgoingBonding();
BluetoothClass btClass = new BluetoothClass(mBluetoothService.getRemoteClass(address));
int btDeviceClass = btClass.getDeviceClass();
if (address.equals(pendingOutgoingAddress)) {
// we initiated the bonding
// Check if its a dock
if (mBluetoothService.isBluetoothDock(address)) {
String pin = mBluetoothService.getDockPin();
mBluetoothService.setPin(address, BluetoothDevice.convertPinToBytes(pin));
return;
}
// try 0000 once if the device looks dumb
switch (btDeviceClass) {
case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET:
case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE:
case BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES:
case BluetoothClass.Device.AUDIO_VIDEO_PORTABLE_AUDIO:
case BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO:
if (mBluetoothService.attemptAutoPair(address)) return;
}
}
if (btDeviceClass == BluetoothClass.Device.PERIPHERAL_KEYBOARD ||
btDeviceClass == BluetoothClass.Device.PERIPHERAL_KEYBOARD_POINTING) {
// Its a keyboard. Follow the HID spec recommendation of creating the
// passkey and displaying it to the user. If the keyboard doesn't follow
// the spec recommendation, check if the keyboard has a fixed PIN zero
// and pair.
if (mBluetoothService.isFixedPinZerosAutoPairKeyboard(address)) {
mBluetoothService.setPin(address, BluetoothDevice.convertPinToBytes("0000"));
return;
}
// Generate a variable PIN. This is not truly random but good enough.
int pin = (int) Math.floor(Math.random() * 10000);
sendDisplayPinIntent(address, pin);
return;
}
// Acquire wakelock during PIN code request to bring up LCD display
mWakeLock.acquire();
Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.PAIRING_VARIANT_PIN);
mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
// Release wakelock to allow the LCD to go off after the PIN popup notification.
mWakeLock.release();
return;
}
/**
* Called by native code on a DisplayPasskey method call to
* org.bluez.Agent.
*
* @param objectPath the path of the device to display the passkey for
* @param passkey an integer containing the 6-digit passkey
* @param nativeData a native pointer to the original D-Bus message
*/
private void onDisplayPasskey(String objectPath, int passkey, int nativeData) {
String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
if (address == null) return;
// Acquire wakelock during PIN code request to bring up LCD display
mWakeLock.acquire();
Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
intent.putExtra(BluetoothDevice.EXTRA_PAIRING_KEY, passkey);
intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY);
mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
//Release wakelock to allow the LCD to go off after the PIN popup notification.
mWakeLock.release();
}
private void sendDisplayPinIntent(String address, int pin) {
// Acquire wakelock during PIN code request to bring up LCD display
mWakeLock.acquire();
Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
intent.putExtra(BluetoothDevice.EXTRA_PAIRING_KEY, pin);
intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN);
mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
//Release wakelock to allow the LCD to go off after the PIN popup notifcation.
mWakeLock.release();
}
/**
* Called by native code on a RequestOobData method call to
* org.bluez.Agent.
*
* @param objectPath the path of the device requesting OOB data
* @param nativeData a native pointer to the original D-Bus message
*/
private void onRequestOobData(String objectPath, int nativeData) {
String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
if (address == null) return;
Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT);
mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
}
/**
* Called by native code on an Authorize method call to org.bluez.Agent.
*
* @param objectPath the path of the device requesting to be authorized
* @param deviceUuid the UUID of the requesting device
* @param nativeData reference for native data
*/
private void onAgentAuthorize(String objectPath, String deviceUuid, int nativeData) {
if (!mBluetoothService.isEnabled()) return;
String address = mBluetoothService.getAddressFromObjectPath(objectPath);
if (address == null) {
Log.e(TAG, "Unable to get device address in onAuthAgentAuthorize");
return;
}
boolean authorized = false;
ParcelUuid uuid = ParcelUuid.fromString(deviceUuid);
BluetoothDevice device = mAdapter.getRemoteDevice(address);
mAuthorizationAgentRequestData.put(address, new Integer(nativeData));
// Bluez sends the UUID of the local service being accessed, _not_ the
// remote service
if (mA2dp != null &&
(BluetoothUuid.isAudioSource(uuid) || BluetoothUuid.isAvrcpTarget(uuid)
|| BluetoothUuid.isAdvAudioDist(uuid)) &&
!isOtherSinkInNonDisconnectedState(address)) {
authorized = mA2dp.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
if (authorized && !BluetoothUuid.isAvrcpTarget(uuid)) {
Log.i(TAG, "First check pass for incoming A2DP / AVRCP connection from " + address);
// Some headsets try to connect AVCTP before AVDTP - against the recommendation
// If AVCTP connection fails, we get stuck in IncomingA2DP state in the state
// machine. We don't handle AVCTP signals currently. We only send
// intents for AVDTP state changes. We need to handle both of them in
// some cases. For now, just don't move to incoming state in this case.
mBluetoothService.notifyIncomingA2dpConnection(address, false);
} else {
Log.i(TAG, "" + authorized +
"Incoming A2DP / AVRCP connection from " + address);
mA2dp.allowIncomingConnect(device, authorized);
mBluetoothService.notifyIncomingA2dpConnection(address, true);
}
} else if (BluetoothUuid.isInputDevice(uuid)) {
// We can have more than 1 input device connected.
authorized = mBluetoothService.getInputDevicePriority(device) >
BluetoothInputDevice.PRIORITY_OFF;
if (authorized) {
Log.i(TAG, "First check pass for incoming HID connection from " + address);
// notify profile state change
mBluetoothService.notifyIncomingHidConnection(address);
} else {
Log.i(TAG, "Rejecting incoming HID connection from " + address);
mBluetoothService.allowIncomingProfileConnect(device, authorized);
}
} else if (BluetoothUuid.isBnep(uuid)) {
// PAN doesn't go to the state machine, accept or reject from here
authorized = mBluetoothService.allowIncomingTethering();
mBluetoothService.allowIncomingProfileConnect(device, authorized);
} else {
Log.i(TAG, "Rejecting incoming " + deviceUuid + " connection from " + address);
mBluetoothService.allowIncomingProfileConnect(device, authorized);
}
log("onAgentAuthorize(" + objectPath + ", " + deviceUuid + ") = " + authorized);
}
private boolean onAgentOutOfBandDataAvailable(String objectPath) {
if (!mBluetoothService.isEnabled()) return false;
String address = mBluetoothService.getAddressFromObjectPath(objectPath);
if (address == null) return false;
if (mBluetoothService.getDeviceOutOfBandData(
mAdapter.getRemoteDevice(address)) != null) {
return true;
}
return false;
}
private boolean isOtherSinkInNonDisconnectedState(String address) {
List<BluetoothDevice> devices =
mA2dp.getDevicesMatchingConnectionStates(new int[] {BluetoothA2dp.STATE_CONNECTED,
BluetoothA2dp.STATE_CONNECTING,
BluetoothA2dp.STATE_DISCONNECTING});
if (devices.size() == 0) return false;
for (BluetoothDevice dev: devices) {
if (!dev.getAddress().equals(address)) return true;
}
return false;
}
/**
* Called by native code on a Cancel method call to org.bluez.Agent.
*/
private void onAgentCancel() {
Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_CANCEL);
mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_AGENT_CANCEL),
1500);
return;
}
/**
* Called by native code for the async response to a DiscoverServices
* method call to org.bluez.Adapter.
*
* @param deviceObjectPath the path for the specified device
* @param result true for success; false on error
*/
private void onDiscoverServicesResult(String deviceObjectPath, boolean result) {
String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
if (address == null) return;
// We don't parse the xml here, instead just query Bluez for the properties.
if (result) {
mBluetoothService.updateRemoteDevicePropertiesCache(address);
}
mBluetoothService.sendUuidIntent(address);
mBluetoothService.makeServiceChannelCallbacks(address);
}
/**
* Called by native code for the async response to a CreateDevice
* method call to org.bluez.Adapter.
*
* @param address the MAC address of the device to create
* @param result {@link #CREATE_DEVICE_SUCCESS},
* {@link #CREATE_DEVICE_ALREADY_EXISTS} or {@link #CREATE_DEVICE_FAILED}}
*/
private void onCreateDeviceResult(String address, int result) {
if (DBG) log("Result of onCreateDeviceResult:" + result);
switch (result) {
case CREATE_DEVICE_ALREADY_EXISTS:
String path = mBluetoothService.getObjectPathFromAddress(address);
if (path != null) {
mBluetoothService.discoverServicesNative(path, "");
break;
}
Log.w(TAG, "Device exists, but we don't have the bluez path, failing");
// fall-through
case CREATE_DEVICE_FAILED:
mBluetoothService.sendUuidIntent(address);
mBluetoothService.makeServiceChannelCallbacks(address);
break;
case CREATE_DEVICE_SUCCESS:
// nothing to do, UUID intent's will be sent via property changed
}
}
/**
* Called by native code for the async response to a Connect
* method call to org.bluez.Input.
*
* @param path the path of the specified input device
* @param result Result code of the operation.
*/
private void onInputDeviceConnectionResult(String path, int result) {
// Success case gets handled by Property Change signal
if (result != BluetoothInputDevice.INPUT_OPERATION_SUCCESS) {
String address = mBluetoothService.getAddressFromObjectPath(path);
if (address == null) return;
boolean connected = false;
BluetoothDevice device = mAdapter.getRemoteDevice(address);
int state = mBluetoothService.getInputDeviceConnectionState(device);
if (state == BluetoothInputDevice.STATE_CONNECTING) {
if (result == BluetoothInputDevice.INPUT_CONNECT_FAILED_ALREADY_CONNECTED) {
connected = true;
} else {
connected = false;
}
} else if (state == BluetoothInputDevice.STATE_DISCONNECTING) {
if (result == BluetoothInputDevice.INPUT_DISCONNECT_FAILED_NOT_CONNECTED) {
connected = false;
} else {
// There is no better way to handle this, this shouldn't happen
connected = true;
}
} else {
Log.e(TAG, "Error onInputDeviceConnectionResult. State is:" + state);
}
mBluetoothService.handleInputDevicePropertyChange(address, connected);
}
}
/**
* Called by native code for the async response to a Connect
* method call to org.bluez.Network.
*
* @param path the path of the specified PAN device
* @param result Result code of the operation.
*/
private void onPanDeviceConnectionResult(String path, int result) {
log ("onPanDeviceConnectionResult " + path + " " + result);
// Success case gets handled by Property Change signal
if (result != BluetoothPan.PAN_OPERATION_SUCCESS) {
String address = mBluetoothService.getAddressFromObjectPath(path);
if (address == null) return;
boolean connected = false;
BluetoothDevice device = mAdapter.getRemoteDevice(address);
int state = mBluetoothService.getPanDeviceConnectionState(device);
if (state == BluetoothPan.STATE_CONNECTING) {
if (result == BluetoothPan.PAN_CONNECT_FAILED_ALREADY_CONNECTED) {
connected = true;
} else {
connected = false;
}
} else if (state == BluetoothPan.STATE_DISCONNECTING) {
if (result == BluetoothPan.PAN_DISCONNECT_FAILED_NOT_CONNECTED) {
connected = false;
} else {
// There is no better way to handle this, this shouldn't happen
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,
BluetoothPan.LOCAL_PANU_ROLE);
}
}
/**
* Called by native code for the async response to a Connect
* method call to org.bluez.Health
*
* @param chanCode The internal id of the channel
* @param result Result code of the operation.
*/
private void onHealthDeviceConnectionResult(int chanCode, int result) {
log ("onHealthDeviceConnectionResult " + chanCode + " " + result);
// Success case gets handled by Property Change signal
if (result != BluetoothHealth.HEALTH_OPERATION_SUCCESS) {
mBluetoothService.onHealthDeviceChannelConnectionError(chanCode,
BluetoothHealth.STATE_CHANNEL_DISCONNECTED);
}
}
/**
* Called by native code on a DeviceDisconnected signal from
* org.bluez.NetworkServer.
*
* @param address the MAC address of the disconnected device
*/
private void onNetworkDeviceDisconnected(String address) {
BluetoothDevice device = mAdapter.getRemoteDevice(address);
mBluetoothService.handlePanDeviceStateChange(device, BluetoothPan.STATE_DISCONNECTED,
BluetoothPan.LOCAL_NAP_ROLE);
}
/**
* Called by native code on a DeviceConnected signal from
* org.bluez.NetworkServer.
*
* @param address the MAC address of the connected device
* @param iface interface of remote network
* @param destUuid unused UUID parameter
*/
private void onNetworkDeviceConnected(String address, String iface, int destUuid) {
BluetoothDevice device = mAdapter.getRemoteDevice(address);
mBluetoothService.handlePanDeviceStateChange(device, iface, BluetoothPan.STATE_CONNECTED,
BluetoothPan.LOCAL_NAP_ROLE);
}
/**
* Called by native code on a PropertyChanged signal from
* org.bluez.HealthDevice.
*
* @param devicePath the object path of the remote device
* @param propValues Properties (Name-Value) of the Health Device.
*/
private void onHealthDevicePropertyChanged(String devicePath, String[] propValues) {
log("Health Device : Name of Property is: " + propValues[0] + " Value:" + propValues[1]);
mBluetoothService.onHealthDevicePropertyChanged(devicePath, propValues[1]);
}
/**
* Called by native code on a ChannelCreated/Deleted signal from
* org.bluez.HealthDevice.
*
* @param devicePath the object path of the remote device
* @param channelPath the path of the health channel.
* @param exists Boolean to indicate if the channel was created or deleted.
*/
private void onHealthDeviceChannelChanged(String devicePath, String channelPath,
boolean exists) {
log("Health Device : devicePath: " + devicePath + ":channelPath:" + channelPath +
":exists" + exists);
mBluetoothService.onHealthDeviceChannelChanged(devicePath, channelPath, exists);
}
private static void log(String msg) {
Log.d(TAG, msg);
}
private native void initializeNativeDataNative();
private native void startEventLoopNative();
private native void stopEventLoopNative();
private native boolean isEventLoopRunningNative();
private native void cleanupNativeDataNative();
}