1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 package net.sf.bluecove;
26
27 import java.io.IOException;
28 import java.util.Enumeration;
29 import java.util.Vector;
30
31 import javax.bluetooth.BluetoothStateException;
32 import javax.bluetooth.DeviceClass;
33 import javax.bluetooth.DiscoveryAgent;
34 import javax.bluetooth.DiscoveryListener;
35 import javax.bluetooth.LocalDevice;
36 import javax.bluetooth.RemoteDevice;
37 import javax.bluetooth.ServiceRecord;
38 import javax.bluetooth.UUID;
39
40 import org.bluecove.tester.log.Logger;
41 import org.bluecove.tester.util.RuntimeDetect;
42 import org.bluecove.tester.util.StringUtils;
43 import org.bluecove.tester.util.TimeUtils;
44
45 import net.sf.bluecove.util.BluetoothTypesInfo;
46
47 public class TestClientBluetoothInquirer implements DiscoveryListener {
48
49 private final TestClientConfig config;
50
51 boolean stoped = false;
52
53 boolean inquiring;
54
55 boolean inquiringDevice;
56
57 boolean searchingServices;
58
59 boolean deviceDiscoveryError;
60
61 Vector devices = new Vector();
62
63 Vector serverURLs = new Vector();
64
65 public int[] attrIDs;
66
67 public final UUID L2CAP = new UUID(0x0100);
68
69 public final UUID RFCOMM = new UUID(0x0003);
70
71 private UUID searchUuidSet[];
72
73 private UUID searchUuidSet2[];
74
75 DiscoveryAgent discoveryAgent;
76
77 int servicesSearchTransID;
78
79 private String servicesOnDeviceName = null;
80
81 private String servicesOnDeviceAddress = null;
82
83 private boolean servicesFound = false;
84
85 boolean anyServicesFound = false;
86
87 private int anyServicesFoundCount;
88
89 public TestClientBluetoothInquirer(TestClientConfig config) {
90 this.config = config;
91 inquiringDevice = false;
92 inquiring = false;
93 if (this.config.searchOnlyBluecoveUuid) {
94 if (Configuration.useServiceClassExtUUID.booleanValue()) {
95 searchUuidSet = new UUID[] { L2CAP, RFCOMM, Configuration.blueCoveUUID(), Consts.uuidSrvClassExt };
96 } else {
97 searchUuidSet = new UUID[] { L2CAP, RFCOMM, Configuration.blueCoveUUID() };
98 }
99 if ((Configuration.supportL2CAP) && (Configuration.testL2CAP.booleanValue())) {
100 if (Configuration.useServiceClassExtUUID.booleanValue()) {
101 searchUuidSet2 = new UUID[] { L2CAP, Configuration.blueCoveL2CAPUUID(), Consts.uuidSrvClassExt };
102 } else {
103 searchUuidSet2 = new UUID[] { L2CAP, Configuration.blueCoveL2CAPUUID() };
104 }
105 }
106 } else {
107 searchUuidSet = new UUID[] { Configuration.discoveryUUID };
108 }
109 if (!Configuration.testServiceAttributes.booleanValue()) {
110 attrIDs = null;
111 } else if (Configuration.testAllServiceAttributes.booleanValue()) {
112 int allSize = ServiceRecordTester.allTestServiceAttributesSize();
113 attrIDs = new int[allSize + 1];
114 attrIDs[0] = Consts.TEST_SERVICE_ATTRIBUTE_INT_ID;
115 for (int i = 0; i < allSize; i++) {
116 attrIDs[1 + i] = Consts.SERVICE_ATTRIBUTE_ALL_START + i;
117 }
118 } else if (Configuration.testIgnoreNotWorkingServiceAttributes.booleanValue()) {
119 attrIDs = new int[] { Consts.TEST_SERVICE_ATTRIBUTE_INT_ID, Consts.TEST_SERVICE_ATTRIBUTE_URL_ID,
120 Consts.TEST_SERVICE_ATTRIBUTE_BYTES_ID, Consts.VARIABLE_SERVICE_ATTRIBUTE_BYTES_ID,
121 Consts.SERVICE_ATTRIBUTE_BYTES_SERVER_INFO };
122 } else {
123 attrIDs = new int[] {
124 0x0009,
125 0x0100,
126 Consts.TEST_SERVICE_ATTRIBUTE_INT_ID, Consts.TEST_SERVICE_ATTRIBUTE_STR_ID,
127 Consts.TEST_SERVICE_ATTRIBUTE_URL_ID, Consts.TEST_SERVICE_ATTRIBUTE_LONG_ID,
128 Consts.TEST_SERVICE_ATTRIBUTE_BYTES_ID, Consts.VARIABLE_SERVICE_ATTRIBUTE_BYTES_ID,
129 Consts.SERVICE_ATTRIBUTE_BYTES_SERVER_INFO, 0x0303,
130 };
131 }
132 }
133
134 public boolean hasServers() {
135 return ((serverURLs != null) && (serverURLs.size() >= 1));
136 }
137
138 public void shutdown() {
139 stoped = true;
140 if (inquiring && (discoveryAgent != null)) {
141 cancelInquiry();
142 cancelServiceSearch();
143 }
144 }
145
146 private void cancelInquiry() {
147 try {
148 if (discoveryAgent != null) {
149 if (discoveryAgent.cancelInquiry(this)) {
150 Logger.debug("Device inquiry was canceled");
151 } else if (inquiringDevice) {
152 Logger.debug("Device inquiry was not canceled");
153 }
154 }
155 } catch (Throwable e) {
156 Logger.error("Cannot cancel Device inquiry", e);
157 }
158 }
159
160 private void cancelServiceSearch() {
161 try {
162 if ((servicesSearchTransID != 0) && (discoveryAgent != null)) {
163 discoveryAgent.cancelServiceSearch(servicesSearchTransID);
164 servicesSearchTransID = 0;
165 }
166 } catch (Throwable e) {
167 }
168 }
169
170 public boolean runDeviceInquiry() {
171 boolean needToFindDevice = Configuration.clientContinuousDiscoveryDevices
172 || ((devices.size() == 0) && (serverURLs.size() == 0));
173 try {
174 if (this.config.useDiscoveredDevices) {
175 copyDiscoveredDevices();
176 this.config.useDiscoveredDevices = false;
177 } else if (needToFindDevice) {
178 Logger.debug("Starting Device inquiry");
179 deviceDiscoveryError = false;
180 devices.removeAllElements();
181 long start = System.currentTimeMillis();
182 inquiring = true;
183 inquiringDevice = true;
184 try {
185 discoveryAgent = LocalDevice.getLocalDevice().getDiscoveryAgent();
186 boolean started = discoveryAgent.startInquiry(DiscoveryAgent.GIAC, this);
187 if (!started) {
188 Logger.error("Inquiry was not started (may be because the accessCode is not supported)");
189 return false;
190 }
191 } catch (BluetoothStateException e) {
192 Logger.error("Cannot start Device inquiry", e);
193 return false;
194 }
195
196
197 while (inquiringDevice) {
198 synchronized (this) {
199 try {
200 wait();
201 } catch (InterruptedException e) {
202 return false;
203 }
204 }
205 }
206 inquiringDevice = false;
207 if (this.stoped) {
208 return true;
209 }
210 cancelInquiry();
211 Logger.debug(" Device inquiry took " + TimeUtils.secSince(start));
212 RemoteDeviceInfo.discoveryInquiryFinished(TimeUtils.since(start));
213 if (deviceDiscoveryError && (devices.size() == 0)) {
214 return false;
215 }
216 }
217
218 if (Configuration.clientContinuousServicesSearch || serverURLs.size() == 0) {
219 serverURLs.removeAllElements();
220 try {
221 return startServicesSearch();
222 } finally {
223 cancelServiceSearch();
224 }
225 } else {
226 return true;
227 }
228 } finally {
229 inquiring = false;
230 inquiringDevice = false;
231 }
232 }
233
234 private void copyDiscoveredDevices() {
235 if (RemoteDeviceInfo.devices.size() == 0) {
236 Logger.warn("No device in history, run Discovery");
237 }
238 for (Enumeration iter = RemoteDeviceInfo.devices.elements(); iter.hasMoreElements();) {
239 RemoteDeviceInfo dev = (RemoteDeviceInfo) iter.nextElement();
240 devices.addElement(dev.remoteDevice);
241 }
242 if (devices.size() == 0) {
243 if (Configuration.storage == null) {
244 Logger.warn("no storage");
245 return;
246 }
247 String lastURL = Configuration.getLastServerURL();
248 if (StringUtils.isStringSet(lastURL)) {
249 Logger.info("Will used device from recent Connections");
250 devices.addElement(new RemoteDeviceIheritance(BluetoothTypesInfo.extractBluetoothAddress(lastURL)));
251 } else {
252 Logger.warn("no recent Connections");
253 }
254 }
255 }
256
257 public void deviceDiscovered(RemoteDevice remoteDevice, DeviceClass cod) {
258 if (this.stoped) {
259 return;
260 }
261 if (Configuration.listedDevicesOnly.booleanValue()
262 && !Configuration.isWhiteDevice(remoteDevice.getBluetoothAddress())) {
263 Logger.debug("ignore device " + TestResponderClient.niceDeviceName(remoteDevice.getBluetoothAddress())
264 + " " + BluetoothTypesInfo.toString(cod) + " (not listed)");
265 return;
266 }
267 if (Configuration.useMajorDeviceClass(cod.getMajorDeviceClass())) {
268 devices.addElement(remoteDevice);
269 } else {
270 Logger.debug("ignore device " + TestResponderClient.niceDeviceName(remoteDevice.getBluetoothAddress())
271 + " " + BluetoothTypesInfo.toString(cod) + " (by code)");
272 return;
273 }
274 String name = "";
275 try {
276 if ((Configuration.discoveryGetDeviceFriendlyName.booleanValue()) || RuntimeDetect.isBlueCove) {
277 name = " [" + remoteDevice.getFriendlyName(false) + "]";
278 }
279 } catch (IOException e) {
280 Logger.debug("er.getFriendlyName," + remoteDevice.getBluetoothAddress(), e);
281 }
282 if (remoteDevice.isTrustedDevice()) {
283 name += " Trusted";
284 }
285 RemoteDeviceInfo.deviceFound(remoteDevice);
286 Logger.debug("deviceDiscovered " + TestResponderClient.niceDeviceName(remoteDevice.getBluetoothAddress())
287 + name + " " + remoteDevice.getBluetoothAddress() + " " + BluetoothTypesInfo.toString(cod));
288 }
289
290 private boolean startServicesSearch() {
291 if (devices.size() == 0) {
292 return true;
293 }
294 Logger.debug(this.config.logID + "Starting Services search " + TimeUtils.timeNowToString());
295 long inquiryStart = System.currentTimeMillis();
296 nextDevice: for (Enumeration iter = devices.elements(); iter.hasMoreElements();) {
297 if (this.stoped) {
298 break;
299 }
300 servicesFound = false;
301 anyServicesFound = false;
302 anyServicesFoundCount = 0;
303 long start = System.currentTimeMillis();
304 RemoteDevice remoteDevice = (RemoteDevice) iter.nextElement();
305 String name = "";
306 if ((Configuration.discoveryGetDeviceFriendlyName.booleanValue()) || RuntimeDetect.isBlueCove) {
307 try {
308 name = remoteDevice.getFriendlyName(false);
309 if ((name != null) && (name.length() > 0)) {
310 TestResponderClient.recentDeviceNames.put(remoteDevice.getBluetoothAddress().toUpperCase(),
311 name);
312 }
313 } catch (Throwable e) {
314 Logger.error(this.config.logID + "er.getFriendlyName," + remoteDevice.getBluetoothAddress(), e);
315 }
316 }
317 servicesOnDeviceAddress = remoteDevice.getBluetoothAddress();
318 servicesOnDeviceName = TestResponderClient.niceDeviceName(servicesOnDeviceAddress);
319 if (servicesOnDeviceName.equals(name)) {
320 name = "";
321 }
322 Logger.debug(this.config.logID + "Search Services on " + servicesOnDeviceAddress + " "
323 + servicesOnDeviceName + " " + name);
324
325 int transID = -1;
326
327 for (int uuidType = 1; uuidType <= 2; uuidType++) {
328 UUID[] uuidSet = searchUuidSet;
329 if (uuidType == 2) {
330 if (searchUuidSet2 != null) {
331 uuidSet = searchUuidSet2;
332 } else {
333 break;
334 }
335 }
336 try {
337 discoveryAgent = LocalDevice.getLocalDevice().getDiscoveryAgent();
338
339 int[] shortAttrSet;
340 if ((TestResponderClient.sdAttrRetrievableMax != 0) && (attrIDs != null)
341 && (TestResponderClient.sdAttrRetrievableMax < attrIDs.length)) {
342 shortAttrSet = new int[TestResponderClient.sdAttrRetrievableMax];
343 for (int i = 0; i < TestResponderClient.sdAttrRetrievableMax; i++) {
344 shortAttrSet[i] = attrIDs[i];
345 }
346 Logger.debug(this.config.logID + "search attr first " + shortAttrSet.length + " of "
347 + attrIDs.length);
348 } else {
349 shortAttrSet = attrIDs;
350 }
351 searchingServices = true;
352 servicesSearchTransID = discoveryAgent.searchServices(shortAttrSet, uuidSet, remoteDevice, this);
353 transID = servicesSearchTransID;
354 if (transID <= 0) {
355 Logger.warn(this.config.logID + "servicesSearch TransID mast be positive, " + transID);
356 }
357 } catch (BluetoothStateException e) {
358 Logger.error(this.config.logID + "Cannot start searchServices on " + servicesOnDeviceName, e);
359 if (!this.config.searchServiceRetry) {
360 this.stoped = true;
361 return false;
362 }
363 continue nextDevice;
364 }
365
366
367 while (searchingServices) {
368 synchronized (this) {
369 try {
370 wait();
371 } catch (InterruptedException e) {
372 break;
373 }
374 }
375 }
376 cancelServiceSearch();
377 }
378
379 RemoteDeviceInfo.searchServices(remoteDevice, servicesFound, TimeUtils.since(start));
380 String msg = (anyServicesFound) ? "; " + anyServicesFoundCount + " service(s) found" : "; no services";
381 Logger.debug(this.config.logID + "Services Search " + transID + " took " + TimeUtils.secSince(start) + msg);
382 }
383 String msg = "";
384 if (serverURLs.size() > 0) {
385 msg = "; BC Srv(s) " + serverURLs.size();
386 }
387 Logger.debug(this.config.logID + "Services search completed " + TimeUtils.secSince(inquiryStart) + msg);
388 return true;
389 }
390
391 void populateAllservicesAttributes(ServiceRecord servRecord) {
392 int lastId = 0xffff;
393 for (int j = 0; j <= lastId; j += TestResponderClient.sdAttrRetrievableMax) {
394 int max = TestResponderClient.sdAttrRetrievableMax;
395 if (j + max > lastId) {
396 max = lastId - j;
397 }
398 int[] shortAttrSet = new int[max];
399 int id = j;
400 for (int n = 0; n < max; n++, id++) {
401 shortAttrSet[n] = id;
402 }
403 try {
404 servRecord.populateRecord(shortAttrSet);
405 } catch (IOException e) {
406 Logger.error("Cannot populateRecord " + j, e);
407 break;
408 }
409 }
410 }
411
412 public void servicesDiscovered(int transID, ServiceRecord[] servRecord) {
413 if (this.stoped) {
414 return;
415 }
416 for (int i = 0; i < servRecord.length; i++) {
417 anyServicesFound = true;
418 anyServicesFoundCount++;
419 String url = servRecord[i].getConnectionURL(Configuration.getRequiredSecurity(), false);
420 Logger.info("*found server " + url);
421 if (this.config.discoveryOnce) {
422 if (Configuration.testAllServiceAttributes.booleanValue()
423 && (TestResponderClient.sdAttrRetrievableMax != 0)) {
424
425 }
426 Logger.debug(this.config.logID + "ServiceRecord " + (i + 1) + "/" + servRecord.length + "\n"
427 + BluetoothTypesInfo.toString(servRecord[i]));
428 }
429 if (url == null) {
430
431 continue;
432 }
433 RemoteDeviceInfo.saveServiceURL(servRecord[i]);
434
435 boolean isBlueCoveTestService;
436
437 if (this.config.searchOnlyBluecoveUuid) {
438 isBlueCoveTestService = ServiceRecordTester.testServiceAttributes(servRecord[i], servicesOnDeviceName,
439 servicesOnDeviceAddress);
440 } else {
441 isBlueCoveTestService = ServiceRecordTester.hasServiceClassBlieCoveUUID(servRecord[i]);
442 if (isBlueCoveTestService) {
443
444
445 if ((TestResponderClient.sdAttrRetrievableMax != 0) && (attrIDs != null)
446 && (TestResponderClient.sdAttrRetrievableMax < attrIDs.length)) {
447
448 for (int ai = TestResponderClient.sdAttrRetrievableMax; ai < attrIDs.length; ai++) {
449 try {
450 servRecord[i].populateRecord(new int[] { attrIDs[ai] });
451 } catch (IOException e) {
452 Logger.error("populateRecord", e);
453 }
454 }
455 }
456
457 ServiceRecordTester.testServiceAttributes(servRecord[i], servicesOnDeviceName,
458 servicesOnDeviceAddress);
459 }
460 }
461
462 if (isBlueCoveTestService) {
463 TestResponderClient.discoveryCount++;
464 Logger.info(this.config.logID + "Found BlueCove SRV:"
465 + TestResponderClient.niceDeviceName(servRecord[i].getHostDevice().getBluetoothAddress()));
466 }
467
468 if (this.config.searchOnlyBluecoveUuid || isBlueCoveTestService) {
469 serverURLs.addElement(url);
470 } else {
471 Logger.info(this.config.logID + "is not TestService on "
472 + TestResponderClient.niceDeviceName(servRecord[i].getHostDevice().getBluetoothAddress()));
473 }
474 if (isBlueCoveTestService) {
475 servicesFound = true;
476 }
477 }
478 }
479
480 public synchronized void serviceSearchCompleted(int transID, int respCode) {
481 switch (respCode) {
482 case SERVICE_SEARCH_ERROR:
483 Logger.error(this.config.logID + "error occurred while processing the service search");
484 break;
485 case SERVICE_SEARCH_TERMINATED:
486 Logger.info(this.config.logID + "SERVICE_SEARCH_TERMINATED");
487 break;
488 case SERVICE_SEARCH_DEVICE_NOT_REACHABLE:
489 Logger.info(this.config.logID + "SERVICE_SEARCH_DEVICE_NOT_REACHABLE");
490 break;
491 }
492 searchingServices = false;
493 notifyAll();
494 }
495
496 public synchronized void inquiryCompleted(int discType) {
497 switch (discType) {
498 case INQUIRY_ERROR:
499 Logger.error("device inquiry ended abnormally");
500 deviceDiscoveryError = true;
501 break;
502 case INQUIRY_TERMINATED:
503 Logger.info("Device discovery has been canceled by the application");
504 break;
505 case INQUIRY_COMPLETED:
506 }
507 inquiringDevice = false;
508 notifyAll();
509 }
510
511 }