/**
* BlueCove - Java library for Bluetooth
* Copyright (C) 2009 Vlad Skarzhevskyy
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*
* @author vlads
* @version $Id$
*/
package org.bluez.v3;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import javax.bluetooth.DiscoveryAgent;
import org.bluez.BlueZAPI;
import org.bluez.Error.Canceled;
import org.bluez.Error.Failed;
import org.bluez.Error.InvalidArguments;
import org.bluez.Error.NoSuchAdapter;
import org.bluez.Error.NotReady;
import org.bluez.Error.Rejected;
import org.freedesktop.DBus;
import org.freedesktop.dbus.DBusConnection;
import org.freedesktop.dbus.DBusSigHandler;
import org.freedesktop.dbus.DBusSignal;
import org.freedesktop.dbus.Path;
import org.freedesktop.dbus.UInt32;
import org.freedesktop.dbus.exceptions.DBusException;
import org.freedesktop.dbus.exceptions.DBusExecutionException;
import com.intel.bluetooth.BlueCoveImpl;
import com.intel.bluetooth.BluetoothConsts;
import com.intel.bluetooth.DebugLog;
/**
*
* Access BlueZ v3 over D-Bus
*
*/
public class BlueZAPIV3 implements BlueZAPI {
private DBusConnection dbusConn;
private Manager dbusManager;
private Adapter adapter;
private Path adapterPath;
private long lastDeviceDiscoveryTime = 0;
public BlueZAPIV3(DBusConnection dbusConn, Manager dbusManager) {
this.dbusConn = dbusConn;
this.dbusManager = dbusManager;
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#findAdapter(java.lang.String)
*/
public Path findAdapter(String pattern) throws InvalidArguments {
String path;
try {
path = dbusManager.FindAdapter(pattern);
} catch (NoSuchAdapter e) {
return null;
}
if (path == null) {
return null;
} else {
return new Path(path);
}
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#defaultAdapter()
*/
public Path defaultAdapter() throws InvalidArguments {
String path;
try {
path = dbusManager.DefaultAdapter();
} catch (NoSuchAdapter e) {
return null;
}
if (path == null) {
return null;
} else {
return new Path(path);
}
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#getAdapter(int)
*/
public Path getAdapter(int number) {
String[] adapters = dbusManager.ListAdapters();
if (adapters == null) {
throw null;
}
if ((number < 0) || (number >= adapters.length)) {
throw null;
}
return new Path(String.valueOf(adapters[number]));
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#listAdapters()
*/
public List<String> listAdapters() {
List<String> v = new Vector<String>();
String[] adapters = dbusManager.ListAdapters();
if (adapters != null) {
for (int i = 0; i < adapters.length; i++) {
String adapterId = String.valueOf(adapters[i]);
final String bluezPath = "/org/bluez/";
if (adapterId.startsWith(bluezPath)) {
adapterId = adapterId.substring(bluezPath.length());
}
v.add(adapterId);
}
}
return v;
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#selectAdapter(org.freedesktop.dbus.Path)
*/
public void selectAdapter(Path adapterPath) throws DBusException {
DebugLog.debug("selectAdapter", adapterPath.getPath());
adapter = dbusConn.getRemoteObject("org.bluez", adapterPath.getPath(), Adapter.class);
this.adapterPath = adapterPath;
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#getAdapterAddress()
*/
public String getAdapterAddress() {
return adapter.GetAddress();
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#getAdapterID()
*/
public String getAdapterID() {
final String bluezPath = "/org/bluez/";
if (adapterPath.getPath().startsWith(bluezPath)) {
return adapterPath.getPath().substring(bluezPath.length());
} else {
return adapterPath.getPath();
}
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#getAdapterDeviceClass()
*/
public int getAdapterDeviceClass() {
int record = 0;
String major = adapter.GetMajorClass();
if ("computer".equals(major)) {
record |= BluetoothConsts.DeviceClassConsts.MAJOR_COMPUTER;
} else {
DebugLog.debug("Unknown MajorClass", major);
}
String minor = adapter.GetMinorClass();
if (minor.equals("uncategorized")) {
record |= BluetoothConsts.DeviceClassConsts.COMPUTER_MINOR_UNCLASSIFIED;
} else if (minor.equals("desktop")) {
record |= BluetoothConsts.DeviceClassConsts.COMPUTER_MINOR_DESKTOP;
} else if (minor.equals("server")) {
record |= BluetoothConsts.DeviceClassConsts.COMPUTER_MINOR_SERVER;
} else if (minor.equals("laptop")) {
record |= BluetoothConsts.DeviceClassConsts.COMPUTER_MINOR_LAPTOP;
} else if (minor.equals("handheld")) {
record |= BluetoothConsts.DeviceClassConsts.COMPUTER_MINOR_HANDHELD;
} else if (minor.equals("palm")) {
record |= BluetoothConsts.DeviceClassConsts.COMPUTER_MINOR_PALM;
} else if (minor.equals("wearable")) {
record |= BluetoothConsts.DeviceClassConsts.COMPUTER_MINOR_WEARABLE;
} else {
DebugLog.debug("Unknown MinorClass", minor);
record |= BluetoothConsts.DeviceClassConsts.COMPUTER_MINOR_UNCLASSIFIED;
}
String[] srvc = adapter.GetServiceClasses();
if (srvc != null) {
for (int s = 0; s < srvc.length; s++) {
String serviceClass = srvc[s];
if (serviceClass.equals("positioning")) {
record |= BluetoothConsts.DeviceClassConsts.POSITIONING_SERVICE;
} else if (serviceClass.equals("networking")) {
record |= BluetoothConsts.DeviceClassConsts.NETWORKING_SERVICE;
} else if (serviceClass.equals("rendering")) {
record |= BluetoothConsts.DeviceClassConsts.RENDERING_SERVICE;
} else if (serviceClass.equals("capturing")) {
record |= BluetoothConsts.DeviceClassConsts.CAPTURING_SERVICE;
} else if (serviceClass.equals("object transfer")) {
record |= BluetoothConsts.DeviceClassConsts.OBJECT_TRANSFER_SERVICE;
} else if (serviceClass.equals("audio")) {
record |= BluetoothConsts.DeviceClassConsts.AUDIO_SERVICE;
} else if (serviceClass.equals("telephony")) {
record |= BluetoothConsts.DeviceClassConsts.TELEPHONY_SERVICE;
} else if (serviceClass.equals("information")) {
record |= BluetoothConsts.DeviceClassConsts.INFORMATION_SERVICE;
} else {
DebugLog.debug("Unknown ServiceClasses", serviceClass);
}
}
}
return record;
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#getAdapterName()
*/
public String getAdapterName() {
try {
return adapter.GetName();
} catch (NotReady e) {
return null;
} catch (Failed e) {
return null;
}
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#isAdapterDiscoverable()
*/
public boolean isAdapterDiscoverable() {
return adapter.IsDiscoverable();
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#getAdapterDiscoverableTimeout()
*/
public int getAdapterDiscoverableTimeout() {
return adapter.GetDiscoverableTimeout().intValue();
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#setAdapterDiscoverable(int)
*/
public boolean setAdapterDiscoverable(int mode) throws DBusException {
String modeStr;
switch (mode) {
case DiscoveryAgent.NOT_DISCOVERABLE:
modeStr = "connectable";
break;
case DiscoveryAgent.GIAC:
modeStr = "discoverable";
break;
case DiscoveryAgent.LIAC:
modeStr = "limited";
break;
default:
if ((0x9E8B00 <= mode) && (mode <= 0x9E8B3F)) {
// system does not support the access mode specified
return false;
}
throw new IllegalArgumentException("Invalid discoverable mode");
}
adapter.SetMode(modeStr);
return true;
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#getAdapterManufacturer()
*/
public String getAdapterManufacturer() {
return adapter.GetManufacturer();
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#getAdapterRevision()
*/
public String getAdapterRevision() {
return adapter.GetRevision();
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#getAdapterVersion()
*/
public String getAdapterVersion() {
return adapter.GetVersion();
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#isAdapterPowerOn()
*/
public boolean isAdapterPowerOn() {
return !"off".equals(adapter.GetMode());
}
private <T extends DBusSignal> void quietRemoveSigHandler(Class<T> type, DBusSigHandler<T> handler) {
try {
dbusConn.removeSigHandler(type, handler);
} catch (DBusException ignore) {
}
}
private boolean hasBonding(String deviceAddress) {
try {
return adapter.HasBonding(deviceAddress);
} catch (Throwable e) {
return false;
}
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#deviceInquiry(org.bluez.BlueZAPI.DeviceInquiryListener )
*/
public void deviceInquiry(final DeviceInquiryListener listener) throws DBusException, InterruptedException {
final Object discoveryCompletedEvent = new Object();
DBusSigHandler<Adapter.DiscoveryCompleted> discoveryCompleted = new DBusSigHandler<Adapter.DiscoveryCompleted>() {
public void handle(Adapter.DiscoveryCompleted s) {
DebugLog.debug("discoveryCompleted.handle()");
synchronized (discoveryCompletedEvent) {
discoveryCompletedEvent.notifyAll();
}
}
};
DBusSigHandler<Adapter.DiscoveryStarted> discoveryStarted = new DBusSigHandler<Adapter.DiscoveryStarted>() {
public void handle(Adapter.DiscoveryStarted s) {
DebugLog.debug("device discovery procedure has been started.");
//TODO
}
};
DBusSigHandler<Adapter.RemoteDeviceFound> remoteDeviceFound = new DBusSigHandler<Adapter.RemoteDeviceFound>() {
public void handle(Adapter.RemoteDeviceFound s) {
listener.deviceDiscovered(s.getDeviceAddress(), null, s.getDeviceClass().intValue(), hasBonding(s.getDeviceAddress()));
}
};
DBusSigHandler<Adapter.RemoteNameUpdated> remoteNameUpdated = new DBusSigHandler<Adapter.RemoteNameUpdated>() {
public void handle(Adapter.RemoteNameUpdated s) {
listener.deviceDiscovered(s.getDeviceAddress(), s.getDeviceName(), -1, false);
}
};
DBusSigHandler<Adapter.RemoteClassUpdated> remoteClassUpdated = new DBusSigHandler<Adapter.RemoteClassUpdated>() {
public void handle(Adapter.RemoteClassUpdated s) {
listener.deviceDiscovered(s.getDeviceAddress(), null, s.getDeviceClass().intValue(), hasBonding(s.getDeviceAddress()));
}
};
try {
dbusConn.addSigHandler(Adapter.DiscoveryCompleted.class, discoveryCompleted);
dbusConn.addSigHandler(Adapter.DiscoveryStarted.class, discoveryStarted);
dbusConn.addSigHandler(Adapter.RemoteDeviceFound.class, remoteDeviceFound);
dbusConn.addSigHandler(Adapter.RemoteNameUpdated.class, remoteNameUpdated);
dbusConn.addSigHandler(Adapter.RemoteClassUpdated.class, remoteClassUpdated);
// Inquiries are throttled if they are called too quickly in succession.
// e.g. JSR-82 TCK
long sinceDiscoveryLast = System.currentTimeMillis() - lastDeviceDiscoveryTime;
long acceptableInterval = 5 * 1000;
if (sinceDiscoveryLast < acceptableInterval) {
Thread.sleep(acceptableInterval - sinceDiscoveryLast);
}
synchronized (discoveryCompletedEvent) {
adapter.DiscoverDevices();
listener.deviceInquiryStarted();
DebugLog.debug("wait for device inquiry to complete...");
discoveryCompletedEvent.wait();
//adapter.CancelDiscovery();
}
} finally {
quietRemoveSigHandler(Adapter.RemoteClassUpdated.class, remoteClassUpdated);
quietRemoveSigHandler(Adapter.RemoteNameUpdated.class, remoteNameUpdated);
quietRemoveSigHandler(Adapter.RemoteDeviceFound.class, remoteDeviceFound);
quietRemoveSigHandler(Adapter.DiscoveryStarted.class, discoveryStarted);
quietRemoveSigHandler(Adapter.DiscoveryCompleted.class, discoveryCompleted);
}
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#deviceInquiryCancel()
*/
public void deviceInquiryCancel() throws DBusException {
adapter.CancelDiscovery();
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#getRemoteDeviceFriendlyName(java.lang.String)
*/
public String getRemoteDeviceFriendlyName(final String deviceAddress) throws DBusException, IOException {
final Object discoveryCompletedEvent = new Object();
final Vector<String> namesFound = new Vector<String>();
DBusSigHandler<Adapter.DiscoveryCompleted> discoveryCompleted = new DBusSigHandler<Adapter.DiscoveryCompleted>() {
public void handle(Adapter.DiscoveryCompleted s) {
DebugLog.debug("discoveryCompleted.handle()");
synchronized (discoveryCompletedEvent) {
discoveryCompletedEvent.notifyAll();
}
}
};
DBusSigHandler<Adapter.RemoteNameUpdated> remoteNameUpdated = new DBusSigHandler<Adapter.RemoteNameUpdated>() {
public void handle(Adapter.RemoteNameUpdated s) {
if (deviceAddress.equals(s.getDeviceAddress())) {
if (s.getDeviceName() != null) {
namesFound.add(s.getDeviceName());
synchronized (discoveryCompletedEvent) {
discoveryCompletedEvent.notifyAll();
}
} else {
DebugLog.debug("device name is null");
}
} else {
DebugLog.debug("ignore device name " + s.getDeviceAddress() + " " + s.getDeviceName());
}
}
};
try {
dbusConn.addSigHandler(Adapter.DiscoveryCompleted.class, discoveryCompleted);
dbusConn.addSigHandler(Adapter.RemoteNameUpdated.class, remoteNameUpdated);
synchronized (discoveryCompletedEvent) {
adapter.DiscoverDevices();
DebugLog.debug("wait for device inquiry to complete...");
try {
discoveryCompletedEvent.wait();
DebugLog.debug(namesFound.size() + " device name(s) found");
if (namesFound.size() == 0) {
throw new IOException("Can't retrive device name");
}
// return the last name found
return namesFound.get(namesFound.size() - 1);
} catch (InterruptedException e) {
throw new InterruptedIOException();
}
}
} finally {
quietRemoveSigHandler(Adapter.RemoteNameUpdated.class, remoteNameUpdated);
quietRemoveSigHandler(Adapter.DiscoveryCompleted.class, discoveryCompleted);
}
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#retrieveDevices(boolean)
*/
public List<String> retrieveDevices(boolean preKnown) {
if (!preKnown) {
return null;
}
List<String> addresses = new Vector<String>();
String[] bonded = adapter.ListBondings();
if (bonded != null) {
for (int i = 0; i < bonded.length; i++) {
addresses.add(bonded[i]);
}
}
String[] trusted = adapter.ListTrusts();
if (trusted != null) {
for (int i = 0; i < trusted.length; i++) {
addresses.add(trusted[i]);
}
}
return addresses;
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#isRemoteDeviceConnected(java.lang.String)
*/
public boolean isRemoteDeviceConnected(String deviceAddress) throws DBusException {
return adapter.IsConnected(deviceAddress);
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#isRemoteDeviceTrusted(java.lang.String)
*/
public Boolean isRemoteDeviceTrusted(String deviceAddress) throws DBusException {
return Boolean.valueOf(adapter.HasBonding(deviceAddress));
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#authenticateRemoteDevice(java.lang.String)
*/
public void authenticateRemoteDevice(String deviceAddress) throws DBusException {
adapter.CreateBonding(deviceAddress);
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#authenticateRemoteDevice(java.lang.String,
* java.lang.String)
*/
public boolean authenticateRemoteDevice(final String deviceAddress, final String passkey) throws DBusException {
if (passkey == null) {
authenticateRemoteDevice(deviceAddress);
return true;
} else {
PasskeyAgent passkeyAgent = new PasskeyAgent() {
public String Request(String path, String address) throws Rejected, Canceled {
if (deviceAddress.equals(address)) {
DebugLog.debug("PasskeyAgent.Request");
return passkey;
} else {
return "";
}
}
public boolean isRemote() {
return false;
}
public void Cancel(String path, String address) {
}
public void Release() {
}
};
// final Object completedEvent = new Object();
// DBusSigHandler<Adapter.BondingCreated> bondingCreated = new DBusSigHandler<Adapter.BondingCreated>() {
// public void handle(Adapter.BondingCreated s) {
// DebugLog.debug("BondingCreated.handle");
// synchronized (completedEvent) {
// completedEvent.notifyAll();
// }
// }
// };
DebugLog.debug("get security on path", adapterPath.getPath());
Security security = dbusConn.getRemoteObject("org.bluez", adapterPath.getPath(), Security.class);
String passkeyAgentPath = "/org/bluecove/authenticate/" + getAdapterID() + "/" + deviceAddress.replace(':', '_');
DebugLog.debug("export passkeyAgent", passkeyAgentPath);
dbusConn.exportObject(passkeyAgentPath, passkeyAgent);
// see http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=501222
final boolean useDefaultPasskeyAgentBug = BlueCoveImpl.getConfigProperty("bluecove.bluez.registerDefaultPasskeyAgent", false);
try {
if (useDefaultPasskeyAgentBug) {
security.RegisterDefaultPasskeyAgent(passkeyAgentPath);
} else {
security.RegisterPasskeyAgent(passkeyAgentPath, deviceAddress);
}
adapter.CreateBonding(deviceAddress);
return true;
} finally {
//quietRemoveSigHandler(Adapter.BondingCreated.class, bondingCreated);
try {
if (useDefaultPasskeyAgentBug) {
security.UnregisterDefaultPasskeyAgent(passkeyAgentPath);
} else {
security.UnregisterPasskeyAgent(passkeyAgentPath, deviceAddress);
}
} catch (DBusExecutionException ignore) {
}
dbusConn.unExportObject(passkeyAgentPath);
}
}
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#removeAuthenticationWithRemoteDevice(java.lang.String)
*/
public void removeAuthenticationWithRemoteDevice(String deviceAddress) throws DBusException {
adapter.RemoveBonding(deviceAddress);
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#getRemoteDeviceServices(java.lang.String)
*/
public Map<Integer, String> getRemoteDeviceServices(String deviceAddress) throws DBusException {
String match = "";
UInt32[] serviceHandles;
try {
serviceHandles = adapter.GetRemoteServiceHandles(deviceAddress, match);
} catch (DBus.Error.NoReply e) {
return null;
}
if (serviceHandles == null) {
throw new DBusException("Recived no records");
}
Map<Integer, String> xmlRecords = new HashMap<Integer, String>();
for (int i = 0; i < serviceHandles.length; ++i) {
xmlRecords.put(serviceHandles[i].intValue(), adapter.GetRemoteServiceRecordAsXML(deviceAddress, serviceHandles[i]));
}
return xmlRecords;
}
private Database getSDPService() throws DBusException {
//return dbusConn.getRemoteObject("org.bluez", adapterPath.getPath(), Database.class);
return dbusConn.getRemoteObject("org.bluez", "/org/bluez", Database.class);
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#registerSDPRecord(java.lang.String)
*/
public long registerSDPRecord(String sdpXML) throws DBusException {
DebugLog.debug("AddServiceRecordFromXML", sdpXML);
UInt32 handle = getSDPService().AddServiceRecordFromXML(sdpXML);
return handle.longValue();
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#updateSDPRecord(long, java.lang.String)
*/
public void updateSDPRecord(long handle, String sdpXML) throws DBusException {
DebugLog.debug("UpdateServiceRecordFromXML", sdpXML);
getSDPService().UpdateServiceRecordFromXML(new UInt32(handle), sdpXML);
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#unregisterSDPRecord(long)
*/
public void unregisterSDPRecord(long handle) throws DBusException {
DebugLog.debug("RemoveServiceRecord", handle);
getSDPService().RemoveServiceRecord(new UInt32(handle));
}
}