View Javadoc

1   /**
2    *  BlueCove - Java library for Bluetooth
3    *  Copyright (C) 2006-2008 Vlad Skarzhevskyy
4    *
5    *  Licensed to the Apache Software Foundation (ASF) under one
6    *  or more contributor license agreements.  See the NOTICE file
7    *  distributed with this work for additional information
8    *  regarding copyright ownership.  The ASF licenses this file
9    *  to you under the Apache License, Version 2.0 (the
10   *  "License"); you may not use this file except in compliance
11   *  with the License.  You may obtain a copy of the License at
12   *
13   *    http://www.apache.org/licenses/LICENSE-2.0
14   *
15   *  Unless required by applicable law or agreed to in writing,
16   *  software distributed under the License is distributed on an
17   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18   *  KIND, either express or implied.  See the License for the
19   *  specific language governing permissions and limitations
20   *  under the License.
21   *
22   *  @author vlads
23   *  @version $Id: TestClientBluetoothInquirer.java 2607 2008-12-17 23:51:33Z skarzhevskyy $
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, // BluetoothProfileDescriptorList
125 					0x0100, // Service name
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, // SupportedFormatList
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 				// By this time inquiryCompleted maybe already been called,
196 				// because we are too fast
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 				// By this time serviceSearchCompleted maybe already been
366 				// called, because we are too fast
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 					// populateAllservicesAttributes(servRecord[i]);
425 				}
426 				Logger.debug(this.config.logID + "ServiceRecord " + (i + 1) + "/" + servRecord.length + "\n"
427 						+ BluetoothTypesInfo.toString(servRecord[i]));
428 			}
429 			if (url == null) {
430 				// Bogus service Record
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 					// Retive other service attributes
445 					if ((TestResponderClient.sdAttrRetrievableMax != 0) && (attrIDs != null)
446 							&& (TestResponderClient.sdAttrRetrievableMax < attrIDs.length)) {
447 						// int[] shortAttrSet;
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 }