View Javadoc

1   /**
2    *  BlueCove - Java library for Bluetooth
3    *  Copyright (C) 2004 Intel Corporation
4    *  Copyright (C) 2006-2008 Vlad Skarzhevskyy
5    *
6    *  Licensed to the Apache Software Foundation (ASF) under one
7    *  or more contributor license agreements.  See the NOTICE file
8    *  distributed with this work for additional information
9    *  regarding copyright ownership.  The ASF licenses this file
10   *  to you under the Apache License, Version 2.0 (the
11   *  "License"); you may not use this file except in compliance
12   *  with the License.  You may obtain a copy of the License at
13   *
14   *    http://www.apache.org/licenses/LICENSE-2.0
15   *
16   *  Unless required by applicable law or agreed to in writing,
17   *  software distributed under the License is distributed on an
18   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
19   *  KIND, either express or implied.  See the License for the
20   *  specific language governing permissions and limitations
21   *  under the License.
22   *
23   *  @author vlads
24   *  @version $Id: BlueCoveImpl.java 2667 2008-12-25 20:26:28Z skarzhevskyy $
25   */
26  package com.intel.bluetooth;
27  
28  import java.security.AccessControlContext;
29  import java.security.AccessController;
30  import java.security.PrivilegedActionException;
31  import java.security.PrivilegedExceptionAction;
32  import java.util.Enumeration;
33  import java.util.Hashtable;
34  import java.util.Vector;
35  
36  import javax.bluetooth.BluetoothStateException;
37  
38  import com.intel.bluetooth.BluetoothStack.LibraryInformation;
39  
40  /**
41   *
42   * Singleton class used as holder for BluetoothStack.
43   *
44   * Under security manager all you need to do is initialize BlueCoveImpl inside Privileged context.
45   * <p>
46   * If automatic Bluetooth Stack detection is not enough Java System property "bluecove.stack" can be used to force
47   * desired Stack Initialization. Values "widcomm", "bluesoleil" or "winsock". By default winsock is selected if
48   * available.
49   * <p>
50   * Another property "bluecove.stack.first" is used optimize stack detection. If -Dbluecove.stack.first=widcomm then
51   * widcomm (bluecove.dll) stack is loaded first and if not available then BlueCove will switch to winsock. By default
52   * intelbth.dll is loaded first.
53   * <p>
54   * If multiple stacks are detected they are selected in following order: "winsock", "widcomm", "bluesoleil". Since
55   * BlueCove v2.0.1 "bluecove.stack.first" will alter the order of stack selection.
56   * <p>
57   * To use jsr-82 emulator set "bluecove.stack" value to "emulator".
58   * <p>
59   * If System property is not an option (e.g. when running in Webstart) create text file "bluecove.stack" or
60   * "bluecove.stack.first" containing stack name and add this file to BlueCove or Application jar. (Since v2.0.1)
61   * <p>
62   * Use `LocalDevice.getProperty("bluecove.stack")` to find out which stack is used.
63   *
64   */
65  public class BlueCoveImpl {
66  
67      public static final String BLUETOOTH_API_VERSION = "1.1.1";
68  
69      public static final String OBEX_API_VERSION = BLUETOOTH_API_VERSION;
70      
71  	public static final int versionMajor1 = 2;
72  
73  	public static final int versionMajor2 = 1;
74  
75  	public static final int versionMinor = 0;
76  
77  	public static final int versionBuild = 51;
78  
79  	public static final String versionSufix = ""; // SNAPSHOT
80  
81  	public static final String version = String.valueOf(versionMajor1) + "." + String.valueOf(versionMajor2) + "."
82  			+ String.valueOf(versionMinor) + versionSufix;
83  
84  	public static final int nativeLibraryVersionExpected = versionMajor1 * 1000000 + versionMajor2 * 10000
85  			+ versionMinor * 100 + versionBuild;
86  
87  	public static final String STACK_WINSOCK = "winsock";
88  
89  	public static final String STACK_WIDCOMM = "widcomm";
90  
91  	public static final String STACK_BLUESOLEIL = "bluesoleil";
92  
93  	public static final String STACK_TOSHIBA = "toshiba";
94  
95  	public static final String STACK_BLUEZ = "bluez";
96  
97  	public static final String STACK_OSX = "mac";
98  
99  	public static final String STACK_EMULATOR = "emulator";
100 
101 	// We can't use the same DLL on windows for all implementations.
102 	// Since WIDCOMM need to be compile /MD using VC6 and winsock /MT using
103 	// VC2005
104 	// This variable can be used to simplify development/test builds
105 	private static final boolean oneDLLbuild = false;
106 
107 	public static final String NATIVE_LIB_MS = "intelbth";
108 
109 	public static final String NATIVE_LIB_WIDCOMM = oneDLLbuild ? NATIVE_LIB_MS : "bluecove";
110 
111 	public static final String NATIVE_LIB_TOSHIBA = "bluecove";
112 
113 	public static final String NATIVE_LIB_BLUEZ = "bluecove";
114 
115 	public static final String NATIVE_LIB_OSX = "bluecove";
116 
117 	/**
118 	 * To work on BlueSoleil version 2.3 we need to compile C++ code /MT the same as winsock.
119 	 */
120 	public static final String NATIVE_LIB_BLUESOLEIL = NATIVE_LIB_MS;
121 
122 	static final int BLUECOVE_STACK_DETECT_MICROSOFT = 1;
123 
124 	static final int BLUECOVE_STACK_DETECT_WIDCOMM = 1 << 1;
125 
126 	static final int BLUECOVE_STACK_DETECT_BLUESOLEIL = 1 << 2;
127 
128 	static final int BLUECOVE_STACK_DETECT_TOSHIBA = 1 << 3;
129 
130 	static final int BLUECOVE_STACK_DETECT_OSX = 1 << 4;
131 
132 	public static final int BLUECOVE_STACK_DETECT_BLUEZ = 1 << 5;
133 
134 	public static final int BLUECOVE_STACK_DETECT_EMULATOR = 1 << 6;
135 
136 	static final String TRUE = "true";
137 
138 	static final String FALSE = "false";
139 
140 	private static final String FQCN = BlueCoveImpl.class.getName();
141 
142 	private static final Vector fqcnSet = new Vector();
143 
144 	/* The context to be used when loading native DLL */
145 	private Object accessControlContext;
146 
147 	private static ShutdownHookThread shutdownHookRegistered;
148 
149 	private static BlueCoveImpl instance;
150 
151 	private static BluetoothStackHolder singleStack;
152 
153 	private static ThreadLocalWrapper threadStack;
154 
155 	private static BluetoothStackHolder threadStackIDDefault;
156 
157 	private static Hashtable resourceConfigProperties = new Hashtable();
158 
159 	private static Hashtable/* <BluetoothStack, BluetoothStackHolder> */stacks = new Hashtable();
160 
161 	private static Vector initializationProperties = new Vector();
162 
163 	static {
164 		fqcnSet.addElement(FQCN);
165 		for (int i = 0; i < BlueCoveConfigProperties.INITIALIZATION_PROPERTIES.length; i++) {
166 			initializationProperties.addElement(BlueCoveConfigProperties.INITIALIZATION_PROPERTIES[i]);
167 		}
168 	}
169 
170 	/**
171 	 * Enables the use of Multiple Adapters and Bluetooth Stacks in parallel.
172 	 */
173 	private static class BluetoothStackHolder {
174 
175 		private BluetoothStack bluetoothStack;
176 
177 		Hashtable configProperties = new Hashtable();
178 
179 		private static BluetoothStack getBluetoothStack() throws BluetoothStateException {
180 			return instance().getBluetoothStack();
181 		}
182 
183 		public String toString() {
184 			if (bluetoothStack == null) {
185 				return "not initialized";
186 			}
187 			return bluetoothStack.toString();
188 		}
189 	}
190 
191 	/**
192 	 * bluetoothStack.destroy(); May stuck forever. Exit JVM anyway after timeout.
193 	 */
194 	private class AsynchronousShutdownThread extends Thread {
195 
196 		final Object monitor = new Object();
197 
198 		int shutdownStart = 0;
199 
200 		AsynchronousShutdownThread() {
201 			super("BluecoveAsynchronousShutdownThread");
202 		}
203 
204 		public void run() {
205 			synchronized (monitor) {
206 				while (shutdownStart == 0) {
207 					try {
208 						monitor.wait();
209 					} catch (InterruptedException e) {
210 						return;
211 					}
212 				}
213 			}
214 			if (shutdownStart == -1) {
215 				return;
216 			}
217 			if (!stacks.isEmpty()) {
218 				for (Enumeration en = stacks.elements(); en.hasMoreElements();) {
219 					BluetoothStackHolder s = (BluetoothStackHolder) en.nextElement();
220 					if (s.bluetoothStack != null) {
221 						try {
222 							s.bluetoothStack.destroy();
223 						} finally {
224 							s.bluetoothStack = null;
225 						}
226 					}
227 				}
228 				stacks.clear();
229 				System.out.println("BlueCove stack shutdown completed");
230 			}
231 			synchronized (monitor) {
232 				monitor.notifyAll();
233 			}
234 		}
235 
236 		void deRegister() {
237 			shutdownStart = -1;
238 			synchronized (monitor) {
239 				monitor.notifyAll();
240 			}
241 		}
242 	}
243 
244 	private class ShutdownHookThread extends Thread {
245 
246 		AsynchronousShutdownThread shutdownHookThread;
247 
248 		ShutdownHookThread(AsynchronousShutdownThread shutdownHookThread) {
249 			super("BluecoveShutdownHookThread");
250 			this.shutdownHookThread = shutdownHookThread;
251 		}
252 
253 		public void run() {
254 			final Object monitor = shutdownHookThread.monitor;
255 			synchronized (monitor) {
256 				shutdownHookThread.shutdownStart = 1;
257 				monitor.notifyAll();
258 				if (!stacks.isEmpty()) {
259 					try {
260 						monitor.wait(7000);
261 					} catch (InterruptedException e) {
262 					}
263 				}
264 			}
265 		}
266 
267 		void deRegister() {
268 			shutdownHookRegistered = null;
269 			UtilsJavaSE.runtimeRemoveShutdownHook(this);
270 			shutdownHookThread.deRegister();
271 		}
272 	}
273 
274 	/**
275 	 * Applications should not used this function. Allow default initialization. In Secure environment instance() should
276 	 * be called initially from secure context.
277 	 *
278 	 * @return Instance of the class, getBluetoothStack() can be called.
279 	 */
280 	public static synchronized BlueCoveImpl instance() {
281 		if (instance == null) {
282 			instance = new BlueCoveImpl();
283 		}
284 		return instance;
285 	}
286 
287 	private BlueCoveImpl() {
288 		try {
289 			accessControlContext = AccessController.getContext();
290 		} catch (Throwable javaME) {
291 		}
292 		// Initialization in WebStart.
293 		DebugLog.isDebugEnabled();
294 		copySystemProperties(null);
295 	}
296 
297 	static int getNativeLibraryVersion() {
298 		return nativeLibraryVersionExpected;
299 	}
300 
301 	private synchronized void createShutdownHook() {
302 		if (shutdownHookRegistered != null) {
303 			return;
304 		}
305 		AsynchronousShutdownThread shutdownHookThread = new AsynchronousShutdownThread();
306 		if (UtilsJavaSE.runtimeAddShutdownHook(shutdownHookRegistered = new ShutdownHookThread(shutdownHookThread))) {
307 			UtilsJavaSE.threadSetDaemon(shutdownHookThread);
308 			shutdownHookThread.start();
309 		}
310 	}
311 
312 	private int getStackId(String stack) {
313 		if (STACK_WIDCOMM.equalsIgnoreCase(stack)) {
314 			return BLUECOVE_STACK_DETECT_WIDCOMM;
315 		} else if (STACK_BLUESOLEIL.equalsIgnoreCase(stack)) {
316 			return BLUECOVE_STACK_DETECT_BLUESOLEIL;
317 		} else if (STACK_TOSHIBA.equalsIgnoreCase(stack)) {
318 			return BLUECOVE_STACK_DETECT_TOSHIBA;
319 		} else if (STACK_WINSOCK.equalsIgnoreCase(stack)) {
320 			return BLUECOVE_STACK_DETECT_MICROSOFT;
321 		} else if (STACK_BLUEZ.equalsIgnoreCase(stack)) {
322 			return BLUECOVE_STACK_DETECT_BLUEZ;
323 		} else if (STACK_WINSOCK.equalsIgnoreCase(stack)) {
324 			return BLUECOVE_STACK_DETECT_OSX;
325 		} else if (STACK_EMULATOR.equalsIgnoreCase(stack)) {
326 			return BLUECOVE_STACK_DETECT_EMULATOR;
327 		} else {
328 			return 0;
329 		}
330 	}
331 
332 	private Class loadStackClass(String classPropertyName, String classNameDefault) throws BluetoothStateException {
333 		String className = getConfigProperty(classPropertyName);
334 		if (className == null) {
335 			className = classNameDefault;
336 		}
337 		try {
338 			return Class.forName(className);
339 		} catch (ClassNotFoundException e) {
340 			DebugLog.error(className, e);
341 		}
342 		throw new BluetoothStateException("BlueCove " + className + " not available");
343 	}
344 
345 	private BluetoothStack newStackInstance(Class ctackClass) throws BluetoothStateException {
346 		String className = ctackClass.getName();
347 		try {
348 			return (BluetoothStack) ctackClass.newInstance();
349 		} catch (InstantiationException e) {
350 			DebugLog.error(className, e);
351 		} catch (IllegalAccessException e) {
352 			DebugLog.error(className, e);
353 		}
354 		throw new BluetoothStateException("BlueCove " + className + " can't instantiate");
355 	}
356 
357 	private BluetoothStack loadStack(String classPropertyName, String classNameDefault) throws BluetoothStateException {
358 		return newStackInstance(loadStackClass(classPropertyName, classNameDefault));
359 	}
360 
361 	static void loadNativeLibraries(BluetoothStack stack) throws BluetoothStateException {
362 		// Check is libraries already loaded
363 		try {
364 			if ((UtilsJavaSE.canCallNotLoadedNativeMethod) && (stack.isNativeCodeLoaded())) {
365 				return;
366 			}
367 		} catch (Error e) {
368 			// We caught UnsatisfiedLinkError
369 		}
370 		LibraryInformation[] libs = stack.requireNativeLibraries();
371 		if ((libs == null) || (libs.length == 0)) {
372 			// No native libs for this stack
373 			return;
374 		}
375 		for (int i = 0; i < libs.length; i++) {
376 			Class c = libs[i].stackClass;
377 			if (c == null) {
378 				c = stack.getClass();
379 			}
380 			if (!NativeLibLoader.isAvailable(libs[i].libraryName, c)) {
381 				throw new BluetoothStateException("BlueCove library " + libs[i].libraryName + " not available");
382 			}
383 		}
384 	}
385 
386 	private static boolean isNativeLibrariesAvailable(BluetoothStack stack) {
387 		try {
388 			if (UtilsJavaSE.canCallNotLoadedNativeMethod) {
389 				return stack.isNativeCodeLoaded();
390 			}
391 		} catch (Error e) {
392 			// We caught UnsatisfiedLinkError
393 		}
394 		LibraryInformation[] libs = stack.requireNativeLibraries();
395 		if ((libs == null) || (libs.length == 0)) {
396 			// No native libs for this stack
397 			return true;
398 		}
399 		for (int i = 0; i < libs.length; i++) {
400 			Class c = libs[i].stackClass;
401 			if (c == null) {
402 				c = stack.getClass();
403 			}
404 			if (!NativeLibLoader.isAvailable(libs[i].libraryName, c)) {
405 				return false;
406 			}
407 		}
408 		return true;
409 	}
410 
411 	private BluetoothStack detectStack() throws BluetoothStateException {
412 
413 		BluetoothStack detectorStack = null;
414 
415 		String stackFirstDetector = getConfigProperty(BlueCoveConfigProperties.PROPERTY_STACK_FIRST);
416 
417 		String stackSelected = getConfigProperty(BlueCoveConfigProperties.PROPERTY_STACK);
418 
419 		if (stackFirstDetector == null) {
420 			stackFirstDetector = stackSelected;
421 		}
422 		if (STACK_EMULATOR.equals(stackSelected)) {
423 			detectorStack = loadStack("bluecove.emulator.class", "com.intel.bluetooth.BluetoothEmulator");
424 		} else {
425 			switch (NativeLibLoader.getOS()) {
426 			case NativeLibLoader.OS_LINUX:
427 				Class stackClass = loadStackClass("bluecove.bluez.class", "com.intel.bluetooth.BluetoothStackBlueZ");
428 				detectorStack = newStackInstance(stackClass);
429 				loadNativeLibraries(detectorStack);
430 				stackSelected = detectorStack.getStackID();
431 				break;
432 			case NativeLibLoader.OS_MAC_OS_X:
433 				detectorStack = new BluetoothStackOSX();
434 				loadNativeLibraries(detectorStack);
435 				stackSelected = detectorStack.getStackID();
436 				break;
437 			case NativeLibLoader.OS_WINDOWS:
438 			case NativeLibLoader.OS_WINDOWS_CE:
439 				detectorStack = createDetectorOnWindows(stackFirstDetector);
440 				if (DebugLog.isDebugEnabled()) {
441 					detectorStack.enableNativeDebug(DebugLog.class, true);
442 				}
443 				break;
444 			default:
445 				throw new BluetoothStateException("BlueCove not available");
446 
447 			}
448 		}
449 
450 		int libraryVersion = detectorStack.getLibraryVersion();
451 		if (nativeLibraryVersionExpected != libraryVersion) {
452 			DebugLog.fatal("BlueCove native library version mismatch " + libraryVersion + " expected "
453 					+ nativeLibraryVersionExpected);
454 			throw new BluetoothStateException("BlueCove native library version mismatch");
455 		}
456 
457 		if (stackSelected == null) {
458 			// auto detect
459 			int aval = detectorStack.detectBluetoothStack();
460 			DebugLog.debug("BluetoothStack detected", aval);
461 			int detectorID = getStackId(detectorStack.getStackID());
462 			if ((aval & detectorID) != 0) {
463 				stackSelected = detectorStack.getStackID();
464 			} else if ((aval & BLUECOVE_STACK_DETECT_MICROSOFT) != 0) {
465 				stackSelected = STACK_WINSOCK;
466 			} else if ((aval & BLUECOVE_STACK_DETECT_WIDCOMM) != 0) {
467 				stackSelected = STACK_WIDCOMM;
468 			} else if ((aval & BLUECOVE_STACK_DETECT_BLUESOLEIL) != 0) {
469 				stackSelected = STACK_BLUESOLEIL;
470 			} else if ((aval & BLUECOVE_STACK_DETECT_TOSHIBA) != 0) {
471 				stackSelected = STACK_TOSHIBA;
472 			} else if ((aval & BLUECOVE_STACK_DETECT_OSX) != 0) {
473 				stackSelected = STACK_OSX;
474 			} else {
475 				DebugLog.fatal("BluetoothStack not detected");
476 				throw new BluetoothStateException("BluetoothStack not detected");
477 			}
478 		} else {
479 			DebugLog.debug("BluetoothStack selected", stackSelected);
480 		}
481 
482 		BluetoothStack stack = setBluetoothStack(stackSelected, detectorStack);
483 		stackSelected = stack.getStackID();
484 		copySystemProperties(stack);
485 		if (!stackSelected.equals(STACK_EMULATOR)) {
486 			System.out.println("BlueCove version " + version + " on " + stackSelected);
487 		}
488 		return stack;
489 	}
490 
491 	/**
492 	 * List the local adapters that can be initialized using configuration property "bluecove.deviceID". (Linux BlueZ
493 	 * and Emulator)
494 	 *
495 	 * The first stack/adapter would be initialized if not initialized already, you can exclude it from the list.
496 	 *
497 	 * The function return empty list on non bluez environment.
498 	 *
499 	 * @return List of Strings
500 	 * @throws BluetoothStateException
501 	 *             if stack interface can't be initialized
502 	 * @see com.intel.bluetooth.BlueCoveConfigProperties#PROPERTY_LOCAL_DEVICE_ID
503 	 * @see com.intel.bluetooth.BlueCoveLocalDeviceProperties#LOCAL_DEVICE_PROPERTY_DEVICE_ID
504 	 */
505 	public static Vector getLocalDevicesID() throws BluetoothStateException {
506 		Vector v = new Vector();
507 		String ids = BluetoothStackHolder.getBluetoothStack().getLocalDeviceProperty(
508 				BlueCoveLocalDeviceProperties.LOCAL_DEVICE_DEVICES_LIST);
509 		if (ids != null) {
510 			UtilsStringTokenizer tok = new UtilsStringTokenizer(ids, ",");
511 			while (tok.hasMoreTokens()) {
512 				v.addElement(tok.nextToken());
513 			}
514 		}
515 		return v;
516 	}
517 
518 	/**
519 	 * API that enables the use of Multiple Adapters and Bluetooth Stacks in parallel in the same JVM. Each thread
520 	 * should call setThreadBluetoothStackID() before using JSR-82 API.
521 	 *
522 	 * Affects the following JSR-82 API methods:
523 	 *
524 	 * <pre>
525 	 *  LocalDevice.getLocalDevice();
526 	 *  LocalDevice.getProperty(String);
527 	 *  Connector.open(...);
528 	 *  methods of RemoteDevice instance created by user.
529 	 * </pre>
530 	 *
531 	 * <STRONG>Example</STRONG>
532 	 * <P>
533 	 *
534 	 * <pre>
535 	 * BlueCoveImpl.useThreadLocalBluetoothStack();
536 	 * // On Windows
537 	 * BlueCoveImpl.setConfigProperty(&quot;bluecove.stack&quot;, &quot;widcomm&quot;);
538 	 * // On Linux or in Emulator
539 	 * // BlueCoveImpl.setConfigProperty(&quot;bluecove.deviceID&quot;, &quot;0&quot;);
540 	 *
541 	 * final Object id1 = BlueCoveImpl.getThreadBluetoothStackID();
542 	 * ... do some work with stack 1
543 	 *
544 	 * // Illustrates attaching thread to already initialized stack interface
545 	 * Thread t1 = new Thread() {
546 	 *    public void run() {
547 	 *        BlueCoveImpl.setThreadBluetoothStackID(id1);
548 	 *        agent = LocalDevice.getLocalDevice().getDiscoveryAgent();
549 	 *        agent.startInquiry(...);
550 	 *        .....
551 	 *    }
552 	 * };
553 	 * t1.start();
554 	 *
555 	 * // Illustrates initialization of new/different stack interface in new thread
556 	 * // Start another thread that is using different stack
557 	 * Thread t2 = new Thread() {
558 	 *    public void run() {
559 	 *        // On Windows
560 	 *        BlueCoveImpl.setConfigProperty(&quot;bluecove.stack&quot;, &quot;winsock&quot;);
561 	 *        // On Linux or in Emulator
562 	 *        // BlueCoveImpl.setConfigProperty(&quot;bluecove.deviceID&quot;, &quot;1&quot;);
563 	 *        agent = LocalDevice.getLocalDevice().getDiscoveryAgent();
564 	 *        agent.startInquiry(...);
565 	 *        .....
566 	 *    }
567 	 * }
568 	 * t2.start();
569 	 *
570 	 * Thread t3 = new Thread() {
571 	 *    public void run() {
572 	 *    	  // Wrong, will produce error: Thread StackID not configured
573 	 *        Connector.open(&quot;btspp://12345678:1&quot;);
574 	 *        .....
575 	 *    }
576 	 * };
577 	 * t3.start();
578 	 *
579 	 * </pre>
580 	 *
581 	 * @see #setConfigProperty
582 	 */
583 	public static synchronized void useThreadLocalBluetoothStack() {
584 		if (threadStack == null) {
585 			threadStack = new ThreadLocalWrapper();
586 		}
587 		BluetoothStackHolder s = ((BluetoothStackHolder) threadStack.get());
588 		if (s == null) {
589 			// Move initialized single stack to this thread
590 			if (singleStack != null) {
591 				s = singleStack;
592 				singleStack = null;
593 			} else {
594 				s = new BluetoothStackHolder();
595 			}
596 			threadStack.set(s);
597 		}
598 	}
599 
600 	/**
601 	 * Initialize BluetoothStack if not already done and returns the ID to be used in other threads accessing the same
602 	 * stack.
603 	 *
604 	 * @return an object that represents Adapter/BluetoothStack, stackID to be used in call to
605 	 *         <code>setThreadBluetoothStackID</code>
606 	 * @throws BluetoothStateException
607 	 *             if the Bluetooth system could not be initialized
608 	 */
609 	public static synchronized Object getThreadBluetoothStackID() throws BluetoothStateException {
610 		useThreadLocalBluetoothStack();
611 		BluetoothStackHolder.getBluetoothStack();
612 		return threadStack.get();
613 	}
614 
615 	/**
616 	 * Returns the ID to be used in other threads accessing the same stack.
617 	 *
618 	 * @return an object that represents Adapter/BluetoothStack, stackID to be used in call to
619 	 *         <code>setThreadBluetoothStackID</code> or <code>null<code> if ThreadLocalBluetoothStack not used.
620 	 */
621 	public static synchronized Object getCurrentThreadBluetoothStackID() {
622 		if (threadStack == null) {
623 			return null;
624 		}
625 		return threadStack.get();
626 	}
627 
628 	/**
629 	 * Updates the current Thread BluetoothStack. Updating is possible only if <code>stackID</code> was obtained using
630 	 * the <code>getThreadBluetoothStackID()</code> method. Should be called before connection is made or LocalDevice
631 	 * received from LocalDevice.getLocalDevice().
632 	 *
633 	 * @param stackID
634 	 *            stackID to use or <code>null</code> to detach the current Thread
635 	 */
636 	public static synchronized void setThreadBluetoothStackID(Object stackID) {
637 		if ((stackID != null) && (!(stackID instanceof BluetoothStackHolder))) {
638 			throw new IllegalArgumentException("stackID is not valid");
639 		}
640 		if (threadStack == null) {
641 			throw new IllegalArgumentException("ThreadLocal configuration is not initialized");
642 		}
643 		threadStack.set(stackID);
644 	}
645 
646 	/**
647 	 * Detach BluetoothStack from ThreadLocal. Used for removing itself from container threads. Also can be use to
648 	 * initialize different stack in the same thread.
649 	 */
650 	public static synchronized void releaseThreadBluetoothStack() {
651 		if (threadStack == null) {
652 			throw new IllegalArgumentException("ThreadLocal configuration is not initialized");
653 		}
654 		threadStack.set(null);
655 	}
656 
657 	/**
658 	 * Set default Thread BluetoothStack for Threads that do not call <code>setThreadBluetoothStackID(stackID)</code>.
659 	 * Updating is possible only if <code>stackID</code> was obtained using the <code>getThreadBluetoothStackID()</code>
660 	 * method.
661 	 *
662 	 * @param stackID
663 	 *            stackID to use or <code>null</code> to remove default
664 	 */
665 	public static synchronized void setDefaultThreadBluetoothStackID(Object stackID) {
666 		if ((stackID != null) && (!(stackID instanceof BluetoothStackHolder))) {
667 			throw new IllegalArgumentException("stackID is not valid");
668 		}
669 		if (threadStack == null) {
670 			throw new IllegalArgumentException("ThreadLocal configuration is not initialized");
671 		}
672 		threadStackIDDefault = (BluetoothStackHolder) stackID;
673 	}
674 
675 	static synchronized void setThreadBluetoothStack(BluetoothStack bluetoothStack) {
676 		if (threadStack == null) {
677 			return;
678 		}
679 		BluetoothStackHolder s = ((BluetoothStackHolder) threadStack.get());
680 		if ((s != null) && (s.bluetoothStack == bluetoothStack)) {
681 			return;
682 		}
683 
684 		BluetoothStackHolder sh = (BluetoothStackHolder) stacks.get(bluetoothStack);
685 		if (sh == null) {
686 			throw new RuntimeException("ThreadLocal not found for BluetoothStack");
687 		}
688 		threadStack.set(sh);
689 	}
690 
691 	/**
692 	 * Shutdown BluetoothStack assigned for current Thread and clear configuration properties for this thread
693 	 */
694 	public static synchronized void shutdownThreadBluetoothStack() {
695 		// ThreadLocal configuration is not initialized
696 		if (threadStack == null) {
697 			return;
698 		}
699 		BluetoothStackHolder s = ((BluetoothStackHolder) threadStack.get());
700 		if (s == null) {
701 			return;
702 		}
703 		if (threadStackIDDefault == s) {
704 			threadStackIDDefault = null;
705 		}
706 		s.configProperties.clear();
707 		if (s.bluetoothStack != null) {
708 			BluetoothConnectionNotifierBase.shutdownConnections(s.bluetoothStack);
709 			RemoteDeviceHelper.shutdownConnections(s.bluetoothStack);
710 			s.bluetoothStack.destroy();
711 			stacks.remove(s.bluetoothStack);
712 			s.bluetoothStack = null;
713 		}
714 	}
715 
716 	/**
717 	 * Shutdown all BluetoothStacks interfaces initialized by BlueCove
718 	 */
719 	public static synchronized void shutdown() {
720 		for (Enumeration en = stacks.elements(); en.hasMoreElements();) {
721 			BluetoothStackHolder s = (BluetoothStackHolder) en.nextElement();
722 			s.configProperties.clear();
723 			if (s.bluetoothStack != null) {
724 				BluetoothConnectionNotifierBase.shutdownConnections(s.bluetoothStack);
725 				RemoteDeviceHelper.shutdownConnections(s.bluetoothStack);
726 				try {
727 					s.bluetoothStack.destroy();
728 				} finally {
729 					s.bluetoothStack = null;
730 				}
731 			}
732 		}
733 		stacks.clear();
734 		singleStack = null;
735 		threadStackIDDefault = null;
736 		if (shutdownHookRegistered != null) {
737 			shutdownHookRegistered.deRegister();
738 		}
739 		clearSystemProperties();
740 	}
741 
742 	/**
743 	 * API that can be used to configure BlueCove properties instead of System properties. Initialization properties
744 	 * should be changed before stack initialized. If <code>null</code> is passed as the <code>value</code> then the
745 	 * property will be removed.
746 	 *
747 	 * @param name
748 	 *            property name
749 	 * @param value
750 	 *            property value
751 	 *
752 	 * @see com.intel.bluetooth.BlueCoveConfigProperties
753 	 *
754 	 * @exception IllegalArgumentException
755 	 *                if the stack already initialized and property can't be changed.
756 	 */
757 	public static void setConfigProperty(String name, String value) {
758 		if (name == null) {
759 			throw new NullPointerException("key is null");
760 		}
761 		BluetoothStackHolder sh = currentStackHolder(true);
762 		if ((sh.bluetoothStack != null) && (initializationProperties.contains(name))) {
763 			throw new IllegalArgumentException("BlueCove Stack already initialized");
764 		}
765 		if (value == null) {
766 			sh.configProperties.remove(name);
767 		} else {
768 			sh.configProperties.put(name, value);
769 		}
770 	}
771 
772 	static String getConfigProperty(String key) {
773 		if (key == null) {
774 			throw new NullPointerException("key is null");
775 		}
776 		String value = null;
777 		BluetoothStackHolder sh = currentStackHolder(false);
778 		if (sh != null) {
779 			value = (String) sh.configProperties.get(key);
780 		}
781 		if (value == null) {
782 			try {
783 				value = System.getProperty(key);
784 			} catch (SecurityException webstart) {
785 			}
786 		}
787 		if (value == null) {
788 			synchronized (resourceConfigProperties) {
789 				Object casheValue = resourceConfigProperties.get(key);
790 				if (casheValue != null) {
791 					if (casheValue instanceof String) {
792 						value = (String) casheValue;
793 					}
794 				} else {
795 					value = Utils.getResourceProperty(BlueCoveImpl.class, key);
796 					if (value == null) {
797 						resourceConfigProperties.put(key, new Object());
798 					} else {
799 						resourceConfigProperties.put(key, value);
800 					}
801 				}
802 			}
803 		}
804 		return value;
805 	}
806 
807 	static boolean getConfigProperty(String key, boolean defaultValue) {
808 		String value = getConfigProperty(key);
809 		if (value != null) {
810 			return TRUE.equals(value) || "1".equals(value);
811 		} else {
812 			return defaultValue;
813 		}
814 	}
815 
816 	static int getConfigProperty(String key, int defaultValue) {
817 		String value = getConfigProperty(key);
818 		if (value != null) {
819 			return Integer.parseInt(value);
820 		} else {
821 			return defaultValue;
822 		}
823 	}
824 
825 	static String[] getSystemPropertiesList() {
826         String[] p = { BluetoothConsts.PROPERTY_BLUETOOTH_MASTER_SWITCH, BluetoothConsts.PROPERTY_BLUETOOTH_SD_ATTR_RETRIEVABLE_MAX,
827                 BluetoothConsts.PROPERTY_BLUETOOTH_CONNECTED_DEVICES_MAX, BluetoothConsts.PROPERTY_BLUETOOTH_L2CAP_RECEIVEMTU_MAX,
828                 BluetoothConsts.PROPERTY_BLUETOOTH_SD_TRANS_MAX, BluetoothConsts.PROPERTY_BLUETOOTH_CONNECTED_INQUIRY_SCAN,
829                 BluetoothConsts.PROPERTY_BLUETOOTH_CONNECTED_PAGE_SCAN, BluetoothConsts.PROPERTY_BLUETOOTH_CONNECTED_INQUIRY,
830                 BluetoothConsts.PROPERTY_BLUETOOTH_CONNECTED_PAGE };
831         return p;
832     }
833 
834 	static void clearSystemProperties() {
835 		UtilsJavaSE.setSystemProperty(BluetoothConsts.PROPERTY_BLUETOOTH_API_VERSION, null);
836 		UtilsJavaSE.setSystemProperty(BluetoothConsts.PROPERTY_OBEX_API_VERSION, null);
837 		String[] property = getSystemPropertiesList();
838 		for (int i = 0; i < property.length; i++) {
839 			UtilsJavaSE.setSystemProperty(property[i], null);
840 		}
841 	}
842 
843 	void copySystemProperties(BluetoothStack bluetoothStack) {
844 		UtilsJavaSE.setSystemProperty(BluetoothConsts.PROPERTY_BLUETOOTH_API_VERSION, BlueCoveImpl.BLUETOOTH_API_VERSION);
845 		UtilsJavaSE.setSystemProperty(BluetoothConsts.PROPERTY_OBEX_API_VERSION, BlueCoveImpl.OBEX_API_VERSION);
846 		if (bluetoothStack != null) {
847 			String[] property = getSystemPropertiesList();
848 			for (int i = 0; i < property.length; i++) {
849 				UtilsJavaSE.setSystemProperty(property[i], bluetoothStack.getLocalDeviceProperty(property[i]));
850 			}
851 		}
852 	}
853 
854 	public String getLocalDeviceFeature(int featureID) throws BluetoothStateException {
855 		return ((BluetoothStackHolder.getBluetoothStack().getFeatureSet() & featureID) != 0) ? TRUE : FALSE;
856 	}
857 
858 	private BluetoothStack createDetectorOnWindows(String stackFirst) throws BluetoothStateException {
859 		if (stackFirst != null) {
860 			DebugLog.debug("detector stack", stackFirst);
861 			BluetoothStack detectorStack;
862 			if (STACK_WIDCOMM.equalsIgnoreCase(stackFirst)) {
863 				detectorStack = new BluetoothStackWIDCOMM();
864 				if (isNativeLibrariesAvailable(detectorStack)) {
865 					return detectorStack;
866 				}
867 			} else if (STACK_BLUESOLEIL.equalsIgnoreCase(stackFirst)) {
868 				detectorStack = new BluetoothStackBlueSoleil();
869 				if (isNativeLibrariesAvailable(detectorStack)) {
870 					return detectorStack;
871 				}
872 			} else if (STACK_WINSOCK.equalsIgnoreCase(stackFirst)) {
873 				detectorStack = new BluetoothStackMicrosoft();
874 				if (isNativeLibrariesAvailable(detectorStack)) {
875 					return detectorStack;
876 				}
877 			} else if (STACK_TOSHIBA.equalsIgnoreCase(stackFirst)) {
878 				detectorStack = new BluetoothStackToshiba();
879 				if (isNativeLibrariesAvailable(detectorStack)) {
880 					return detectorStack;
881 				}
882 			} else {
883 				throw new IllegalArgumentException("Invalid BlueCove detector stack [" + stackFirst + "]");
884 			}
885 		}
886 		BluetoothStack stack = new BluetoothStackMicrosoft();
887 		if (isNativeLibrariesAvailable(stack)) {
888 			return stack;
889 		}
890 
891 		stack = new BluetoothStackWIDCOMM();
892 		if (isNativeLibrariesAvailable(stack)) {
893 			return stack;
894 		}
895 
896 		throw new BluetoothStateException("BlueCove libraries not available");
897 	}
898 
899 	/**
900 	 * @deprecated use setConfigProperty("bluecove.stack", ...);
901 	 *
902 	 * @param stack
903 	 * @return stack ID
904 	 * @throws BluetoothStateException
905 	 */
906 	public String setBluetoothStack(String stack) throws BluetoothStateException {
907 		return setBluetoothStack(stack, null).getStackID();
908 	}
909 
910 	private synchronized BluetoothStack setBluetoothStack(String stack, BluetoothStack detectorStack)
911 			throws BluetoothStateException {
912 		if (singleStack != null) {
913 			if (singleStack.bluetoothStack != null) {
914 				singleStack.bluetoothStack.destroy();
915 				stacks.remove(singleStack.bluetoothStack);
916 				singleStack.bluetoothStack = null;
917 			}
918 		} else if (threadStack != null) {
919 			BluetoothStackHolder s = ((BluetoothStackHolder) threadStack.get());
920 			if ((s != null) && (s.bluetoothStack != null)) {
921 				s.bluetoothStack.destroy();
922 				stacks.remove(s.bluetoothStack);
923 				s.bluetoothStack = null;
924 			}
925 		}
926 		BluetoothStack newStack;
927 		if ((detectorStack != null) && (detectorStack.getStackID()).equalsIgnoreCase(stack)) {
928 			newStack = detectorStack;
929 		} else if (STACK_WIDCOMM.equalsIgnoreCase(stack)) {
930 			newStack = new BluetoothStackWIDCOMM();
931 		} else if (STACK_BLUESOLEIL.equalsIgnoreCase(stack)) {
932 			newStack = new BluetoothStackBlueSoleil();
933 		} else if (STACK_TOSHIBA.equalsIgnoreCase(stack)) {
934 			newStack = new BluetoothStackToshiba();
935 		} else {
936 			newStack = new BluetoothStackMicrosoft();
937 		}
938 		loadNativeLibraries(newStack);
939 		int libraryVersion = newStack.getLibraryVersion();
940 		if (nativeLibraryVersionExpected != libraryVersion) {
941 			DebugLog.fatal("BlueCove native library version mismatch " + libraryVersion + " expected "
942 					+ nativeLibraryVersionExpected);
943 			throw new BluetoothStateException("BlueCove native library version mismatch");
944 		}
945 
946 		if (DebugLog.isDebugEnabled()) {
947 			newStack.enableNativeDebug(DebugLog.class, true);
948 		}
949 		newStack.initialize();
950 		createShutdownHook();
951 
952 		// Store stack in Thread or static variables
953 		BluetoothStackHolder sh = currentStackHolder(true);
954 		sh.bluetoothStack = newStack;
955 		stacks.put(newStack, sh);
956 		if (threadStack != null) {
957 			threadStack.set(sh);
958 		}
959 		return newStack;
960 	}
961 
962 	public void enableNativeDebug(boolean on) {
963 		BluetoothStackHolder s = currentStackHolder(false);
964 		if ((s != null) && (s.bluetoothStack != null)) {
965 			s.bluetoothStack.enableNativeDebug(DebugLog.class, on);
966 		}
967 	}
968 
969 	private static BluetoothStackHolder currentStackHolder(boolean create) {
970 		if (threadStack != null) {
971 			BluetoothStackHolder s = ((BluetoothStackHolder) threadStack.get());
972 			if ((s == null) && (threadStackIDDefault != null)) {
973 				return threadStackIDDefault;
974 			}
975 			if ((s == null) && create) {
976 				s = new BluetoothStackHolder();
977 				threadStack.set(s);
978 			}
979 			return s;
980 		} else {
981 			if ((singleStack == null) && create) {
982 				singleStack = new BluetoothStackHolder();
983 			}
984 			return singleStack;
985 		}
986 	}
987 
988 	/**
989 	 * Applications should not used this function.
990 	 *
991 	 * @return current BluetoothStack implementation
992 	 * @throws BluetoothStateException
993 	 *             when BluetoothStack not detected. If one connected the hardware later, BlueCove would be able to
994 	 *             recover and start correctly
995 	 * @exception Error
996 	 *                if called from outside of BlueCove internal code.
997 	 */
998 	public synchronized BluetoothStack getBluetoothStack() throws BluetoothStateException {
999 		Utils.isLegalAPICall(fqcnSet);
1000 		BluetoothStackHolder sh = currentStackHolder(false);
1001 		if ((sh != null) && (sh.bluetoothStack != null)) {
1002 			return sh.bluetoothStack;
1003 		} else if ((sh == null) && (threadStack != null)) {
1004 			throw new BluetoothStateException("No BluetoothStack or Adapter for current thread");
1005 		}
1006 
1007 		BluetoothStack stack;
1008 		if (accessControlContext == null) {
1009 			stack = detectStack();
1010 		} else {
1011 			stack = detectStackPrivileged();
1012 		}
1013 		return stack;
1014 	}
1015 
1016 	private BluetoothStack detectStackPrivileged() throws BluetoothStateException {
1017 		try {
1018 			return (BluetoothStack) AccessController.doPrivileged(new PrivilegedExceptionAction() {
1019 				public Object run() throws BluetoothStateException {
1020 					return detectStack();
1021 				}
1022 			}, (AccessControlContext) accessControlContext);
1023 		} catch (PrivilegedActionException e) {
1024 			Throwable cause = UtilsJavaSE.getCause(e);
1025 			if (cause instanceof BluetoothStateException) {
1026 				throw (BluetoothStateException) cause;
1027 			}
1028 			throw (BluetoothStateException) UtilsJavaSE.initCause(new BluetoothStateException(e.getMessage()), cause);
1029 		}
1030 	}
1031 
1032 }