/** * BlueCove - Java library for Bluetooth * Copyright (C) 2006-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 net.sf.bluecove; import java.io.IOException; import java.util.Enumeration; import java.util.Vector; import javax.bluetooth.BluetoothStateException; import javax.bluetooth.DeviceClass; import javax.bluetooth.DiscoveryAgent; import javax.bluetooth.DiscoveryListener; import javax.bluetooth.LocalDevice; import javax.bluetooth.RemoteDevice; import javax.bluetooth.ServiceRecord; import javax.bluetooth.UUID; import org.bluecove.tester.log.Logger; import org.bluecove.tester.util.RuntimeDetect; import org.bluecove.tester.util.StringUtils; import org.bluecove.tester.util.TimeUtils; import net.sf.bluecove.util.BluetoothTypesInfo; public class TestClientBluetoothInquirer implements DiscoveryListener { private final TestClientConfig config; boolean stoped = false; boolean inquiring; boolean inquiringDevice; boolean searchingServices; boolean deviceDiscoveryError; Vector devices = new Vector(); Vector serverURLs = new Vector(); public int[] attrIDs; public final UUID L2CAP = new UUID(0x0100); public final UUID RFCOMM = new UUID(0x0003); private UUID searchUuidSet[]; private UUID searchUuidSet2[]; DiscoveryAgent discoveryAgent; int servicesSearchTransID; private String servicesOnDeviceName = null; private String servicesOnDeviceAddress = null; private boolean servicesFound = false; boolean anyServicesFound = false; private int anyServicesFoundCount; public TestClientBluetoothInquirer(TestClientConfig config) { this.config = config; inquiringDevice = false; inquiring = false; if (this.config.searchOnlyBluecoveUuid) { if (Configuration.useServiceClassExtUUID.booleanValue()) { searchUuidSet = new UUID[] { L2CAP, RFCOMM, Configuration.blueCoveUUID(), Consts.uuidSrvClassExt }; } else { searchUuidSet = new UUID[] { L2CAP, RFCOMM, Configuration.blueCoveUUID() }; } if ((Configuration.supportL2CAP) && (Configuration.testL2CAP.booleanValue())) { if (Configuration.useServiceClassExtUUID.booleanValue()) { searchUuidSet2 = new UUID[] { L2CAP, Configuration.blueCoveL2CAPUUID(), Consts.uuidSrvClassExt }; } else { searchUuidSet2 = new UUID[] { L2CAP, Configuration.blueCoveL2CAPUUID() }; } } } else { searchUuidSet = new UUID[] { Configuration.discoveryUUID }; } if (!Configuration.testServiceAttributes.booleanValue()) { attrIDs = null; } else if (Configuration.testAllServiceAttributes.booleanValue()) { int allSize = ServiceRecordTester.allTestServiceAttributesSize(); attrIDs = new int[allSize + 1]; attrIDs[0] = Consts.TEST_SERVICE_ATTRIBUTE_INT_ID; for (int i = 0; i < allSize; i++) { attrIDs[1 + i] = Consts.SERVICE_ATTRIBUTE_ALL_START + i; } } else if (Configuration.testIgnoreNotWorkingServiceAttributes.booleanValue()) { attrIDs = new int[] { Consts.TEST_SERVICE_ATTRIBUTE_INT_ID, Consts.TEST_SERVICE_ATTRIBUTE_URL_ID, Consts.TEST_SERVICE_ATTRIBUTE_BYTES_ID, Consts.VARIABLE_SERVICE_ATTRIBUTE_BYTES_ID, Consts.SERVICE_ATTRIBUTE_BYTES_SERVER_INFO }; } else { attrIDs = new int[] { 0x0009, // BluetoothProfileDescriptorList 0x0100, // Service name Consts.TEST_SERVICE_ATTRIBUTE_INT_ID, Consts.TEST_SERVICE_ATTRIBUTE_STR_ID, Consts.TEST_SERVICE_ATTRIBUTE_URL_ID, Consts.TEST_SERVICE_ATTRIBUTE_LONG_ID, Consts.TEST_SERVICE_ATTRIBUTE_BYTES_ID, Consts.VARIABLE_SERVICE_ATTRIBUTE_BYTES_ID, Consts.SERVICE_ATTRIBUTE_BYTES_SERVER_INFO, 0x0303, // SupportedFormatList }; } } public boolean hasServers() { return ((serverURLs != null) && (serverURLs.size() >= 1)); } public void shutdown() { stoped = true; if (inquiring && (discoveryAgent != null)) { cancelInquiry(); cancelServiceSearch(); } } private void cancelInquiry() { try { if (discoveryAgent != null) { if (discoveryAgent.cancelInquiry(this)) { Logger.debug("Device inquiry was canceled"); } else if (inquiringDevice) { Logger.debug("Device inquiry was not canceled"); } } } catch (Throwable e) { Logger.error("Cannot cancel Device inquiry", e); } } private void cancelServiceSearch() { try { if ((servicesSearchTransID != 0) && (discoveryAgent != null)) { discoveryAgent.cancelServiceSearch(servicesSearchTransID); servicesSearchTransID = 0; } } catch (Throwable e) { } } public boolean runDeviceInquiry() { boolean needToFindDevice = Configuration.clientContinuousDiscoveryDevices || ((devices.size() == 0) && (serverURLs.size() == 0)); try { if (this.config.useDiscoveredDevices) { copyDiscoveredDevices(); this.config.useDiscoveredDevices = false; } else if (needToFindDevice) { Logger.debug("Starting Device inquiry"); deviceDiscoveryError = false; devices.removeAllElements(); long start = System.currentTimeMillis(); inquiring = true; inquiringDevice = true; try { discoveryAgent = LocalDevice.getLocalDevice().getDiscoveryAgent(); boolean started = discoveryAgent.startInquiry(DiscoveryAgent.GIAC, this); if (!started) { Logger.error("Inquiry was not started (may be because the accessCode is not supported)"); return false; } } catch (BluetoothStateException e) { Logger.error("Cannot start Device inquiry", e); return false; } // By this time inquiryCompleted maybe already been called, // because we are too fast while (inquiringDevice) { synchronized (this) { try { wait(); } catch (InterruptedException e) { return false; } } } inquiringDevice = false; if (this.stoped) { return true; } cancelInquiry(); Logger.debug(" Device inquiry took " + TimeUtils.secSince(start)); RemoteDeviceInfo.discoveryInquiryFinished(TimeUtils.since(start)); if (deviceDiscoveryError && (devices.size() == 0)) { return false; } } if (Configuration.clientContinuousServicesSearch || serverURLs.size() == 0) { serverURLs.removeAllElements(); try { return startServicesSearch(); } finally { cancelServiceSearch(); } } else { return true; } } finally { inquiring = false; inquiringDevice = false; } } private void copyDiscoveredDevices() { if (RemoteDeviceInfo.devices.size() == 0) { Logger.warn("No device in history, run Discovery"); } for (Enumeration iter = RemoteDeviceInfo.devices.elements(); iter.hasMoreElements();) { RemoteDeviceInfo dev = (RemoteDeviceInfo) iter.nextElement(); devices.addElement(dev.remoteDevice); } if (devices.size() == 0) { if (Configuration.storage == null) { Logger.warn("no storage"); return; } String lastURL = Configuration.getLastServerURL(); if (StringUtils.isStringSet(lastURL)) { Logger.info("Will used device from recent Connections"); devices.addElement(new RemoteDeviceIheritance(BluetoothTypesInfo.extractBluetoothAddress(lastURL))); } else { Logger.warn("no recent Connections"); } } } public void deviceDiscovered(RemoteDevice remoteDevice, DeviceClass cod) { if (this.stoped) { return; } if (Configuration.listedDevicesOnly.booleanValue() && !Configuration.isWhiteDevice(remoteDevice.getBluetoothAddress())) { Logger.debug("ignore device " + TestResponderClient.niceDeviceName(remoteDevice.getBluetoothAddress()) + " " + BluetoothTypesInfo.toString(cod) + " (not listed)"); return; } if (Configuration.useMajorDeviceClass(cod.getMajorDeviceClass())) { devices.addElement(remoteDevice); } else { Logger.debug("ignore device " + TestResponderClient.niceDeviceName(remoteDevice.getBluetoothAddress()) + " " + BluetoothTypesInfo.toString(cod) + " (by code)"); return; } String name = ""; try { if ((Configuration.discoveryGetDeviceFriendlyName.booleanValue()) || RuntimeDetect.isBlueCove) { name = " [" + remoteDevice.getFriendlyName(false) + "]"; } } catch (IOException e) { Logger.debug("er.getFriendlyName," + remoteDevice.getBluetoothAddress(), e); } if (remoteDevice.isTrustedDevice()) { name += " Trusted"; } RemoteDeviceInfo.deviceFound(remoteDevice); Logger.debug("deviceDiscovered " + TestResponderClient.niceDeviceName(remoteDevice.getBluetoothAddress()) + name + " " + remoteDevice.getBluetoothAddress() + " " + BluetoothTypesInfo.toString(cod)); } private boolean startServicesSearch() { if (devices.size() == 0) { return true; } Logger.debug(this.config.logID + "Starting Services search " + TimeUtils.timeNowToString()); long inquiryStart = System.currentTimeMillis(); nextDevice: for (Enumeration iter = devices.elements(); iter.hasMoreElements();) { if (this.stoped) { break; } servicesFound = false; anyServicesFound = false; anyServicesFoundCount = 0; long start = System.currentTimeMillis(); RemoteDevice remoteDevice = (RemoteDevice) iter.nextElement(); String name = ""; if ((Configuration.discoveryGetDeviceFriendlyName.booleanValue()) || RuntimeDetect.isBlueCove) { try { name = remoteDevice.getFriendlyName(false); if ((name != null) && (name.length() > 0)) { TestResponderClient.recentDeviceNames.put(remoteDevice.getBluetoothAddress().toUpperCase(), name); } } catch (Throwable e) { Logger.error(this.config.logID + "er.getFriendlyName," + remoteDevice.getBluetoothAddress(), e); } } servicesOnDeviceAddress = remoteDevice.getBluetoothAddress(); servicesOnDeviceName = TestResponderClient.niceDeviceName(servicesOnDeviceAddress); if (servicesOnDeviceName.equals(name)) { name = ""; } Logger.debug(this.config.logID + "Search Services on " + servicesOnDeviceAddress + " " + servicesOnDeviceName + " " + name); int transID = -1; for (int uuidType = 1; uuidType <= 2; uuidType++) { UUID[] uuidSet = searchUuidSet; if (uuidType == 2) { if (searchUuidSet2 != null) { uuidSet = searchUuidSet2; } else { break; } } try { discoveryAgent = LocalDevice.getLocalDevice().getDiscoveryAgent(); int[] shortAttrSet; if ((TestResponderClient.sdAttrRetrievableMax != 0) && (attrIDs != null) && (TestResponderClient.sdAttrRetrievableMax < attrIDs.length)) { shortAttrSet = new int[TestResponderClient.sdAttrRetrievableMax]; for (int i = 0; i < TestResponderClient.sdAttrRetrievableMax; i++) { shortAttrSet[i] = attrIDs[i]; } Logger.debug(this.config.logID + "search attr first " + shortAttrSet.length + " of " + attrIDs.length); } else { shortAttrSet = attrIDs; } searchingServices = true; servicesSearchTransID = discoveryAgent.searchServices(shortAttrSet, uuidSet, remoteDevice, this); transID = servicesSearchTransID; if (transID <= 0) { Logger.warn(this.config.logID + "servicesSearch TransID mast be positive, " + transID); } } catch (BluetoothStateException e) { Logger.error(this.config.logID + "Cannot start searchServices on " + servicesOnDeviceName, e); if (!this.config.searchServiceRetry) { this.stoped = true; return false; } continue nextDevice; } // By this time serviceSearchCompleted maybe already been // called, because we are too fast while (searchingServices) { synchronized (this) { try { wait(); } catch (InterruptedException e) { break; } } } cancelServiceSearch(); } RemoteDeviceInfo.searchServices(remoteDevice, servicesFound, TimeUtils.since(start)); String msg = (anyServicesFound) ? "; " + anyServicesFoundCount + " service(s) found" : "; no services"; Logger.debug(this.config.logID + "Services Search " + transID + " took " + TimeUtils.secSince(start) + msg); } String msg = ""; if (serverURLs.size() > 0) { msg = "; BC Srv(s) " + serverURLs.size(); } Logger.debug(this.config.logID + "Services search completed " + TimeUtils.secSince(inquiryStart) + msg); return true; } void populateAllservicesAttributes(ServiceRecord servRecord) { int lastId = 0xffff; for (int j = 0; j <= lastId; j += TestResponderClient.sdAttrRetrievableMax) { int max = TestResponderClient.sdAttrRetrievableMax; if (j + max > lastId) { max = lastId - j; } int[] shortAttrSet = new int[max]; int id = j; for (int n = 0; n < max; n++, id++) { shortAttrSet[n] = id; } try { servRecord.populateRecord(shortAttrSet); } catch (IOException e) { Logger.error("Cannot populateRecord " + j, e); break; } } } public void servicesDiscovered(int transID, ServiceRecord[] servRecord) { if (this.stoped) { return; } for (int i = 0; i < servRecord.length; i++) { anyServicesFound = true; anyServicesFoundCount++; String url = servRecord[i].getConnectionURL(Configuration.getRequiredSecurity(), false); Logger.info("*found server " + url); if (this.config.discoveryOnce) { if (Configuration.testAllServiceAttributes.booleanValue() && (TestResponderClient.sdAttrRetrievableMax != 0)) { // populateAllservicesAttributes(servRecord[i]); } Logger.debug(this.config.logID + "ServiceRecord " + (i + 1) + "/" + servRecord.length + "\n" + BluetoothTypesInfo.toString(servRecord[i])); } if (url == null) { // Bogus service Record continue; } RemoteDeviceInfo.saveServiceURL(servRecord[i]); boolean isBlueCoveTestService; if (this.config.searchOnlyBluecoveUuid) { isBlueCoveTestService = ServiceRecordTester.testServiceAttributes(servRecord[i], servicesOnDeviceName, servicesOnDeviceAddress); } else { isBlueCoveTestService = ServiceRecordTester.hasServiceClassBlieCoveUUID(servRecord[i]); if (isBlueCoveTestService) { // Retive other service attributes if ((TestResponderClient.sdAttrRetrievableMax != 0) && (attrIDs != null) && (TestResponderClient.sdAttrRetrievableMax < attrIDs.length)) { // int[] shortAttrSet; for (int ai = TestResponderClient.sdAttrRetrievableMax; ai < attrIDs.length; ai++) { try { servRecord[i].populateRecord(new int[] { attrIDs[ai] }); } catch (IOException e) { Logger.error("populateRecord", e); } } } ServiceRecordTester.testServiceAttributes(servRecord[i], servicesOnDeviceName, servicesOnDeviceAddress); } } if (isBlueCoveTestService) { TestResponderClient.discoveryCount++; Logger.info(this.config.logID + "Found BlueCove SRV:" + TestResponderClient.niceDeviceName(servRecord[i].getHostDevice().getBluetoothAddress())); } if (this.config.searchOnlyBluecoveUuid || isBlueCoveTestService) { serverURLs.addElement(url); } else { Logger.info(this.config.logID + "is not TestService on " + TestResponderClient.niceDeviceName(servRecord[i].getHostDevice().getBluetoothAddress())); } if (isBlueCoveTestService) { servicesFound = true; } } } public synchronized void serviceSearchCompleted(int transID, int respCode) { switch (respCode) { case SERVICE_SEARCH_ERROR: Logger.error(this.config.logID + "error occurred while processing the service search"); break; case SERVICE_SEARCH_TERMINATED: Logger.info(this.config.logID + "SERVICE_SEARCH_TERMINATED"); break; case SERVICE_SEARCH_DEVICE_NOT_REACHABLE: Logger.info(this.config.logID + "SERVICE_SEARCH_DEVICE_NOT_REACHABLE"); break; } searchingServices = false; notifyAll(); } public synchronized void inquiryCompleted(int discType) { switch (discType) { case INQUIRY_ERROR: Logger.error("device inquiry ended abnormally"); deviceDiscoveryError = true; break; case INQUIRY_TERMINATED: Logger.info("Device discovery has been canceled by the application"); break; case INQUIRY_COMPLETED: } inquiringDevice = false; notifyAll(); } }