/**
* 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.v4;
import java.io.IOException;
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.DoesNotExist;
import org.bluez.Error.InvalidArguments;
import org.bluez.Error.NoSuchAdapter;
import org.bluez.Error.Rejected;
import org.bluez.dbus.DBusProperties;
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.Variant;
import org.freedesktop.dbus.exceptions.DBusException;
import com.intel.bluetooth.BluetoothConsts;
import com.intel.bluetooth.DebugLog;
/**
* Access BlueZ v4 over D-Bus
*/
public class BlueZAPIV4 implements BlueZAPI {
private DBusConnection dbusConn;
private Manager dbusManager;
private Adapter adapter;
private Path adapterPath;
public BlueZAPIV4(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 {
try {
return dbusManager.FindAdapter(pattern);
} catch (NoSuchAdapter e) {
return null;
}
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#defaultAdapter()
*/
public Path defaultAdapter() throws InvalidArguments {
try {
return dbusManager.DefaultAdapter();
} catch (NoSuchAdapter e) {
return null;
}
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#getAdapter(int)
*/
public Path getAdapter(int number) {
Path[] adapters = dbusManager.ListAdapters();
if (adapters == null) {
throw null;
}
if ((number < 0) || (number >= adapters.length)) {
throw null;
}
return adapters[number];
}
private String hciID(String adapterPath) {
final String bluezPath = "/org/bluez/";
String path;
if (adapterPath.startsWith(bluezPath)) {
path = adapterPath.substring(bluezPath.length());
} else {
path = adapterPath;
}
int lastpart = path.lastIndexOf('/');
if ((lastpart != -1) && (lastpart != path.length() -1)) {
return path.substring(lastpart + 1);
} else {
return path;
}
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#listAdapters()
*/
public List<String> listAdapters() {
List<String> v = new Vector<String>();
Path[] adapters = dbusManager.ListAdapters();
if (adapters != null) {
for (int i = 0; i < adapters.length; i++) {
v.add(hciID(adapters[i].getPath()));
}
}
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 DBusProperties.getStringValue(adapter, Adapter.Properties.Address);
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#getAdapterID()
*/
public String getAdapterID() {
return hciID(adapterPath.getPath());
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#getAdapterDeviceClass()
*/
public int getAdapterDeviceClass() {
// Since BlueZ 4.34
Integer deviceClass = DBusProperties.getIntValue(adapter, Adapter.Properties.Class);
if (deviceClass == null) {
return BluetoothConsts.DeviceClassConsts.MAJOR_COMPUTER;
} else {
return deviceClass.intValue();
}
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#getAdapterName()
*/
public String getAdapterName() {
return DBusProperties.getStringValue(adapter, Adapter.Properties.Name);
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#isAdapterDiscoverable()
*/
public boolean isAdapterDiscoverable() {
return DBusProperties.getBooleanValue(adapter, Adapter.Properties.Discoverable);
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#getAdapterDiscoverableTimeout()
*/
public int getAdapterDiscoverableTimeout() {
return DBusProperties.getIntValue(adapter, Adapter.Properties.DiscoverableTimeout);
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#setAdapterDiscoverable(int)
*/
public boolean setAdapterDiscoverable(int mode) throws DBusException {
switch (mode) {
case DiscoveryAgent.NOT_DISCOVERABLE:
adapter.SetProperty(DBusProperties.getPropertyName(Adapter.Properties.Discoverable), new Variant<Boolean>(Boolean.FALSE));
break;
case DiscoveryAgent.GIAC:
adapter.SetProperty(DBusProperties.getPropertyName(Adapter.Properties.DiscoverableTimeout), new Variant<UInt32>(new UInt32(0)));
adapter.SetProperty(DBusProperties.getPropertyName(Adapter.Properties.Discoverable), new Variant<Boolean>(Boolean.TRUE));
break;
case DiscoveryAgent.LIAC:
adapter.SetProperty(DBusProperties.getPropertyName(Adapter.Properties.DiscoverableTimeout), new Variant<UInt32>(new UInt32(180)));
adapter.SetProperty(DBusProperties.getPropertyName(Adapter.Properties.Discoverable), new Variant<Boolean>(Boolean.TRUE));
break;
default:
if ((0x9E8B00 <= mode) && (mode <= 0x9E8B3F)) {
// system does not support the access mode specified
return false;
}
throw new IllegalArgumentException("Invalid discoverable mode");
}
return true;
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#getAdapterManufacturer()
*/
public String getAdapterManufacturer() {
// TODO How do I get this in BlueZ 4?
return null;
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#getAdapterRevision()
*/
public String getAdapterRevision() {
// TODO How do I get this in BlueZ 4?
return null;
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#getAdapterVersion()
*/
public String getAdapterVersion() {
// TODO How do I get this in BlueZ 4?
return null;
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#isAdapterPowerOn()
*/
public boolean isAdapterPowerOn() {
return DBusProperties.getBooleanValue(adapter, Adapter.Properties.Powered);
}
private <T extends DBusSignal> void quietRemoveSigHandler(Class<T> type, DBusSigHandler<T> handler) {
try {
dbusConn.removeSigHandler(type, handler);
} catch (DBusException ignore) {
}
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#deviceInquiry(org.bluez.BlueZAPI.DeviceInquiryListener)
*/
public void deviceInquiry(final DeviceInquiryListener listener) throws DBusException, InterruptedException {
DBusSigHandler<Adapter.DeviceFound> remoteDeviceFound = new DBusSigHandler<Adapter.DeviceFound>() {
public void handle(Adapter.DeviceFound s) {
String deviceName = null;
int deviceClass = -1;
boolean paired = false;
Map<String, Variant<?>> properties = s.getDeviceProperties();
if (properties != null) {
deviceName = DBusProperties.getStringValue(properties, Device.Properties.Name);
deviceClass = DBusProperties.getIntValue(properties, Device.Properties.Class);
//TODO verify that this ever present
paired = DBusProperties.getBooleanValue(properties, Device.Properties.Paired, false);
}
listener.deviceDiscovered(s.getDeviceAddress(), deviceName, deviceClass, paired);
}
};
try {
dbusConn.addSigHandler(Adapter.DeviceFound.class, remoteDeviceFound);
adapter.StartDiscovery();
// Verify that discovery actually started to avoid race condition
int tick = 0;
boolean discovering = false;
while ((tick < 5) && !(discovering = DBusProperties.getBooleanValue(adapter, Adapter.Properties.Discovering))) {
Thread.sleep(200);
tick ++;
}
if (!discovering) {
throw new org.bluez.Error.Failed("Unable to confirm discovering state");
}
listener.deviceInquiryStarted();
while (DBusProperties.getBooleanValue(adapter, Adapter.Properties.Discovering)) {
Thread.sleep(200);
}
adapter.StopDiscovery();
} finally {
quietRemoveSigHandler(Adapter.DeviceFound.class, remoteDeviceFound);
}
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#deviceInquiryCancel()
*/
public void deviceInquiryCancel() throws DBusException {
adapter.StopDiscovery();
}
private Device getDevice(String deviceAddress) throws DBusException {
Path devicePath;
try {
devicePath = adapter.FindDevice(deviceAddress);
} catch (DoesNotExist e) {
DebugLog.debug("can't get device", e);
devicePath = adapter.CreateDevice(deviceAddress);
}
return dbusConn.getRemoteObject("org.bluez", devicePath.getPath(), Device.class);
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#getRemoteDeviceFriendlyName(java.lang.String)
*/
public String getRemoteDeviceFriendlyName(String deviceAddress) throws DBusException, IOException {
return DBusProperties.getStringValue(getDevice(deviceAddress), Device.Properties.Name);
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#retrieveDevices(boolean)
*/
public List<String> retrieveDevices(boolean preKnown) {
Path[] devices = adapter.ListDevices();
List<String> addresses = new Vector<String>();
if (devices != null) {
for (Path devicePath : devices) {
try {
Device device = dbusConn.getRemoteObject("org.bluez", devicePath.getPath(), Device.class);
Map<String, Variant<?>> properties = device.GetProperties();
if (properties != null) {
String address = DBusProperties.getStringValue(properties, Device.Properties.Address);
boolean paired = DBusProperties.getBooleanValue(properties, Device.Properties.Paired, false);
boolean trusted = DBusProperties.getBooleanValue(properties, Device.Properties.Trusted, false);
if ((!preKnown) || paired || trusted) {
addresses.add(address);
}
}
} catch (DBusException e) {
DebugLog.debug("can't get device " + devicePath, e);
}
}
}
return addresses;
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#isRemoteDeviceConnected(java.lang.String)
*/
public boolean isRemoteDeviceConnected(String deviceAddress) throws DBusException {
return DBusProperties.getBooleanValue(getDevice(deviceAddress), Device.Properties.Connected);
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#isRemoteDeviceTrusted(java.lang.String)
*/
public Boolean isRemoteDeviceTrusted(String deviceAddress) throws DBusException {
return DBusProperties.getBooleanValue(getDevice(deviceAddress), Device.Properties.Paired);
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#authenticateRemoteDevice(java.lang.String)
*/
public void authenticateRemoteDevice(String deviceAddress) throws DBusException {
throw new DBusException("TODO: How to implement this using Agent?");
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#authenticateRemoteDevice(java.lang.String,
* java.lang.String)
*/
public boolean authenticateRemoteDevice(String deviceAddress, final String passkey) throws DBusException {
if (passkey == null) {
authenticateRemoteDevice(deviceAddress);
return true;
}
Agent agent = new Agent() {
public void Authorize(Path device, String uuid) throws Rejected, Canceled {
}
public void ConfirmModeChange(String mode) throws Rejected, Canceled {
}
public void DisplayPasskey(Path device, UInt32 passkey, byte entered) {
}
public void RequestConfirmation(Path device, UInt32 passkey) throws Rejected, Canceled {
}
public UInt32 RequestPasskey(Path device) throws Rejected, Canceled {
return null;
}
public String RequestPinCode(Path device) throws Rejected, Canceled {
return passkey;
}
public void Cancel() {
}
public void Release() {
}
public boolean isRemote() {
return false;
}
};
String agentPath = "/org/bluecove/authenticate/" + getAdapterID() + "/" + deviceAddress.replace(':', '_');
DebugLog.debug("export Agent", agentPath);
dbusConn.exportObject(agentPath, agent);
try {
adapter.CreatePairedDevice(deviceAddress, new Path(agentPath), "");
return true;
} finally {
dbusConn.unExportObject(agentPath);
}
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#removeAuthenticationWithRemoteDevice(java.lang.String)
*/
public void removeAuthenticationWithRemoteDevice(String deviceAddress) throws DBusException {
Path devicePath = adapter.FindDevice(deviceAddress);
adapter.RemoveDevice(devicePath);
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#getRemoteDeviceServices(java.lang.String)
*/
public Map<Integer, String> getRemoteDeviceServices(String deviceAddress) throws DBusException {
Path devicePath;
try {
devicePath = adapter.FindDevice(deviceAddress);
} catch (DoesNotExist e) {
devicePath = adapter.CreateDevice(deviceAddress);
}
Device device = dbusConn.getRemoteObject("org.bluez", devicePath.getPath(), Device.class);
Map<UInt32, String> xmlMap = device.DiscoverServices("");
Map<Integer, String> xmlRecords = new HashMap<Integer, String>();
for (Map.Entry<UInt32, String> record : xmlMap.entrySet()) {
xmlRecords.put(record.getKey().intValue(), record.getValue());
}
return xmlRecords;
}
private Service getSDPService() throws DBusException {
return dbusConn.getRemoteObject("org.bluez", adapterPath.getPath(), Service.class);
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#registerSDPRecord(java.lang.String)
*/
public long registerSDPRecord(String sdpXML) throws DBusException {
DebugLog.debug("AddRecord", sdpXML);
UInt32 handle = getSDPService().AddRecord(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("UpdateRecord", sdpXML);
getSDPService().UpdateRecord(new UInt32(handle), sdpXML);
}
/*
* (non-Javadoc)
*
* @see org.bluez.BlueZAPI#unregisterSDPRecord(long)
*/
public void unregisterSDPRecord(long handle) throws DBusException {
DebugLog.debug("RemoveRecord", handle);
getSDPService().RemoveRecord(new UInt32(handle));
}
}