001: /*
002: *
003: *
004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: */
026:
027: package com.sun.midp.jsr082.bluetooth;
028:
029: import com.sun.kvem.jsr082.bluetooth.DiscoveryAgentImpl;
030: import com.sun.midp.jsr082.BluetoothUtils;
031: import java.util.Enumeration;
032: import java.util.Hashtable;
033: import java.util.Vector;
034: import javax.bluetooth.BluetoothStateException;
035: import javax.bluetooth.DeviceClass;
036: import javax.bluetooth.DiscoveryListener;
037: import javax.bluetooth.RemoteDevice;
038:
039: /**
040: * Represents native Bluetooth stack provided by the system.
041: * Provides services such as device and service discovery.
042: */
043: public abstract class BluetoothStack {
044:
045: /** Instance of BluetoothStack's subclass used in the current isolate. */
046: private static BluetoothStack instance = null;
047:
048: /** Instance handle of the native porting layer class. */
049: private int nativeInstance = 0;
050:
051: /** Listener where discovery events are reported to. */
052: private DiscoveryListener discListener = null;
053:
054: /** Contains remote name request results. */
055: private Hashtable nameResults = new Hashtable();
056:
057: /** Contains authentication request results. */
058: private Hashtable authenticateResults = new Hashtable();
059:
060: /** Contains set encryption request results. */
061: private Hashtable encryptResults = new Hashtable();
062:
063: /** Timeout value in milliseconds for friendly name retrieval. */
064: private final long ASK_FRIENDLY_NAME_TIMEOUT = 0;
065:
066: /** Timeout value in milliseconds for authentication. */
067: private final long AUTHENTICATE_TIMEOUT = 0;
068:
069: /** Timeout value in milliseconds for setting encryption. */
070: private final long ENCRYPT_TIMEOUT = 0;
071:
072: /** Keeps the count of pending requests. */
073: int pollRequests = 0;
074:
075: /**
076: * Contains results of ongoing inquiry to avoid reporting the same
077: * inquiry result twice.
078: */
079: Vector inquiryHistory = new Vector();
080:
081: /**
082: * Class constructor.
083: */
084: protected BluetoothStack() {
085: initialize();
086: }
087:
088: /**
089: * Allocates native resources.
090: */
091: private native void initialize();
092:
093: /**
094: * Releases native resources.
095: */
096: private native void finalize();
097:
098: /**
099: * Returns a BluetoothStack object.
100: *
101: * @return an instance of BluetoothStack subclass
102: */
103: public synchronized static BluetoothStack getInstance() {
104: if (instance == null) {
105: // Note to porting engineer: please replace the class name with
106: // the one you intend to use on the target platform.
107: instance = new GenericBluetoothStack();
108: }
109: return instance;
110: }
111:
112: /**
113: * Returns a BluetoothStack object and guarantees that Bluetooth
114: * radio is on.
115: * @return an instance of BluetoothStack subclass
116: * @throws BluetoothStateException if BluetoothStack is off and
117: * cannot be turned on.
118: */
119: public synchronized static BluetoothStack getEnabledInstance()
120: throws BluetoothStateException {
121: getInstance();
122: if (!instance.isEnabled() && !instance.enable()) {
123: throw new BluetoothStateException(
124: "Failed turning Bluetooth on");
125: }
126: // intent here is launching EmulationPolling and SDPServer
127: // in emulation mode
128: com.sun.kvem.jsr082.bluetooth.SDDB.getInstance();
129: return instance;
130: }
131:
132: /**
133: * Checks if the Bluetooth radio is enabled.
134: *
135: * @return true if Bluetooth is enabled, false otherwise
136: */
137: public native boolean isEnabled();
138:
139: /**
140: * Enables Bluetooth radio.
141: *
142: * @return true if Bluetooth is enabled, false otherwise
143: */
144: public native boolean enable();
145:
146: /**
147: * Returns Bluetooth address of the local device.
148: *
149: * @return Bluetooth address of the local device, or null if
150: * the address could not be retrieved
151: */
152: public native String getLocalAddress();
153:
154: /**
155: * Returns user-friendly name for the local device.
156: *
157: * @return User-friendly name for the local device, or null if
158: * the name could not be retrieved
159: */
160: public native String getLocalName();
161:
162: /**
163: * Returns class of device including service classes.
164: *
165: * @return class of device value, or -1 if the information could not
166: * be retrieved
167: */
168: public native int getDeviceClass();
169:
170: /**
171: * Sets major service class bits of the device.
172: *
173: * @param classes an integer whose binary representation indicates the major
174: * service class bits that should be set
175: * @return true if the operation succeeded, false otherwise
176: */
177: public native boolean setServiceClasses(int classes);
178:
179: /**
180: * Retrieves the inquiry access code that the local Bluetooth device is
181: * scanning for during inquiry scans.
182: *
183: * @return inquiry access code, or -1 if the information could not
184: * be retrieved
185: */
186: public native int getAccessCode();
187:
188: /**
189: * Sets the inquiry access code that the local Bluetooth device is
190: * scanning for during inquiry scans.
191: *
192: * @param accessCode inquiry access code to be set (valid values are in the
193: * range 0x9e8b00 to 0x9e8b3f), or 0 to take the device out of
194: * discoverable mode
195: * @return true if the operation succeeded, false otherwise
196: */
197: public native boolean setAccessCode(int accessCode);
198:
199: /**
200: * Places the device into inquiry mode.
201: *
202: * @param accessCode the type of inquiry
203: * @param listener the event listener that will receive discovery events
204: * @return true if the inquiry was started, false otherwise
205: */
206: public boolean startInquiry(int accessCode,
207: DiscoveryListener listener) {
208: if (discListener != null || listener == null) {
209: return false;
210: }
211: discListener = listener;
212: if (startInquiry(accessCode)) {
213: inquiryHistory.removeAllElements();
214: startPolling();
215: return true;
216: }
217: return false;
218: }
219:
220: /**
221: * Removes the device from inquiry mode.
222: *
223: * @param listener the listener that is receiving inquiry events
224: * @return true if the inquiry was canceled, false otherwise
225: */
226: public boolean cancelInquiry(DiscoveryListener listener) {
227: if (discListener != listener) {
228: return false;
229: }
230: if (cancelInquiry()) {
231: stopPolling();
232: discListener = null;
233: return true;
234: }
235: return false;
236: }
237:
238: /**
239: * Retrieves friendly name from a remote device synchronously.
240: *
241: * @param addr remote device address
242: * @return friendly name of the remote device, or <code>null</code>
243: * if the name could not be retrieved
244: */
245: public String askFriendlyNameSync(String addr) {
246: if (!askFriendlyName(addr)) {
247: return null;
248: }
249: nameResults.remove(addr);
250: startPolling();
251: return (String) waitResult(nameResults, addr,
252: ASK_FRIENDLY_NAME_TIMEOUT);
253: }
254:
255: /**
256: * Performs remote device authentication synchronously.
257: *
258: * @param addr remote device address
259: * @return <code>true</code> if authentication was successful,
260: * <code>false</code> otherwise
261: */
262: public boolean authenticateSync(String addr) {
263: if (!authenticate(addr)) {
264: return false;
265: }
266: int handle = getHandle(addr);
267: authenticateResults.remove(new Integer(handle));
268: startPolling();
269: Boolean result = (Boolean) waitResult(authenticateResults,
270: new Integer(handle), AUTHENTICATE_TIMEOUT);
271: if (result == null) {
272: return false;
273: }
274: return result.booleanValue();
275: }
276:
277: /**
278: * Sets encryption mode synchronously.
279: *
280: * @param addr remote device address
281: * @param enable <code>true</code> if the encryption needs to be enabled,
282: * <code>false</code> otherwise
283: * @return <code>true</code> if authentication was successful,
284: * <code>false</code> otherwise
285: */
286: public boolean encryptSync(String addr, boolean enable) {
287: if (!encrypt(addr, enable)) {
288: return false;
289: }
290: int handle = getHandle(addr);
291: encryptResults.remove(new Integer(handle));
292: startPolling();
293: Boolean result = (Boolean) waitResult(encryptResults,
294: new Integer(handle), ENCRYPT_TIMEOUT);
295: if (result == null) {
296: return false;
297: }
298: return result.booleanValue();
299: }
300:
301: /**
302: * Starts a supplementary polling thread.
303: */
304: public synchronized void startPolling() {
305: pollRequests++;
306: PollingThread.resume();
307: }
308:
309: /**
310: * Cancels event polling for one request. Polling thread will continue to
311: * run unless there are no other pending requests.
312: */
313: public synchronized void stopPolling() {
314: pollRequests--;
315: if (pollRequests > 0) {
316: return;
317: }
318: PollingThread.suspend();
319: }
320:
321: /**
322: * Checks for Bluetooth events and processes them.
323: */
324: public void pollEvents() {
325: while (checkEvents()) {
326: BluetoothEvent event = retrieveEvent();
327: if (event != null) {
328: event.dispatch();
329: }
330: }
331: }
332:
333: /**
334: * Retrieves Bluetooth event.
335: *
336: * @return a Bluetooth event object
337: */
338: protected abstract BluetoothEvent retrieveEvent();
339:
340: /**
341: * Called when an inquiry request is completed.
342: *
343: * @param success indicates whether inquiry completed successfully
344: */
345: void onInquiryComplete(boolean success) {
346: if (discListener == null) {
347: return;
348: }
349: stopPolling();
350: discListener = null;
351: inquiryHistory.removeAllElements();
352: int type = success ? DiscoveryListener.INQUIRY_COMPLETED
353: : DiscoveryListener.INQUIRY_ERROR;
354: DiscoveryAgentImpl.getInstance().inquiryCompleted(type);
355: }
356:
357: /**
358: * Called when an inquiry result is obtained.
359: *
360: * @param result inquiry result object
361: */
362: void onInquiryResult(InquiryResult result) {
363: if (discListener == null) {
364: return;
365: }
366: String addr = result.getAddress();
367: Enumeration e = inquiryHistory.elements();
368: while (e.hasMoreElements()) {
369: InquiryResult oldResult = (InquiryResult) e.nextElement();
370: if (oldResult.getAddress().equals(addr)) {
371: // inquiry result is already in our possession
372: return;
373: }
374: }
375: inquiryHistory.addElement(result);
376: RemoteDevice dev = DiscoveryAgentImpl.getInstance()
377: .getRemoteDevice(addr);
378: DiscoveryAgentImpl.getInstance().addCachedDevice(addr);
379: discListener.deviceDiscovered(dev, result.getDeviceClass());
380: }
381:
382: /**
383: * Called when a name retrieval request is completed.
384: *
385: * @param addr Bluetooth address of a remote device
386: * @param name friendly name of the device
387: */
388: void onNameRetrieve(String addr, String name) {
389: stopPolling();
390: putResult(nameResults, addr, name);
391: }
392:
393: /**
394: * Called when an authentication request is completed.
395: *
396: * @param handle connection handle for an ACL connection
397: * @param result indicates whether the operation was successful
398: */
399: void onAuthenticationComplete(int handle, boolean result) {
400: stopPolling();
401: putResult(authenticateResults, new Integer(handle),
402: new Boolean(result));
403: }
404:
405: /**
406: * Called when a set encryption request is completed.
407: *
408: * @param handle connection handle for an ACL connection
409: * @param result indicates whether the operation was successful
410: */
411: void onEncryptionChange(int handle, boolean result) {
412: stopPolling();
413: putResult(encryptResults, new Integer(handle), new Boolean(
414: result));
415: }
416:
417: /**
418: * Puts result value into hastable and notifies threads waiting for the
419: * result to appear.
420: *
421: * @param hashtable <code>Hashtable</code> object where the result will be
422: * stored
423: * @param key key identifying the result
424: * @param value value of the result
425: */
426: private void putResult(Hashtable hashtable, Object key, Object value) {
427: synchronized (hashtable) {
428: hashtable.put(key, value);
429: hashtable.notify();
430: }
431: }
432:
433: /**
434: * Waits for the specified key to appear in the given hastable. If the key
435: * does not appear within the timeout specified, <code>null</code> value is
436: * returned.
437: *
438: * @param hashtable <code>Hashtable</code> object where the key is expected
439: * @param key the key expected to appear in the hastable
440: * @param timeout timeout value in milliseconds
441: * @return <code>Object</code> corresponding to the given key
442: */
443: private Object waitResult(Hashtable hashtable, Object key,
444: long timeout) {
445: synchronized (hashtable) {
446: if (timeout == 0) {
447: // infinite timeout
448: while (true) {
449: if (hashtable.containsKey(key)) {
450: return hashtable.remove(key);
451: }
452: try {
453: // wait for a new key-value pair to appear in hashtable
454: hashtable.wait();
455: } catch (InterruptedException e) {
456: return null;
457: }
458: }
459: }
460: // endTime indicates time up to which the method is allowed to run
461: long endTime = System.currentTimeMillis() + timeout;
462: while (true) {
463: if (hashtable.containsKey(key)) {
464: return hashtable.remove(key);
465: }
466: // update timeout value
467: timeout = endTime - System.currentTimeMillis();
468: if (timeout <= 0) {
469: return null;
470: }
471: try {
472: // wait for a new key-value pair to appear in hashtable
473: hashtable.wait(timeout);
474: } catch (InterruptedException e) {
475: return null;
476: }
477: }
478: }
479: }
480:
481: /**
482: * Retrieves default ACL connection handle for the specified remote device.
483: *
484: * @param addr the Bluetooth address of the remote device
485: * @return ACL connection handle value
486: */
487: private native int getHandle(String addr);
488:
489: /**
490: * Passes device discovery request to the native porting layer.
491: *
492: * @param accessCode the type of inquiry
493: * @return <code>true</code> if the operation was accepted,
494: * <code>false</code> otherwise
495: */
496: private native boolean startInquiry(int accessCode);
497:
498: /**
499: * Passes cancellation of device discovery request to the native porting
500: * layer.
501: *
502: * @return <code>true</code> if the operation was accepted,
503: * <code>false</code> otherwise
504: */
505: private native boolean cancelInquiry();
506:
507: /**
508: * Passes remote device's friendly name acquisition request to the native
509: * porting layer.
510: *
511: * @param addr Bluetooth address of the remote device
512: * @return <code>true</code> if the operation was accepted,
513: * <code>false</code> otherwise
514: */
515: private native boolean askFriendlyName(String addr);
516:
517: /**
518: * Passes remote device authentication request to the native porting layer.
519: *
520: * @param addr Bluetooth address of the remote device
521: * @return <code>true</code> if the operation was accepted,
522: * <code>false</code> otherwise
523: */
524: private native boolean authenticate(String addr);
525:
526: /**
527: * Passes connection encryption change request to the native porting layer.
528: *
529: * @param addr Bluetooth address of the remote device
530: * @param enable <code>true</code> if the encryption needs to be enabled,
531: * <code>false</code> otherwise
532: * @return <code>true</code> if the operation was accepted,
533: * <code>false</code> otherwise
534: */
535: private native boolean encrypt(String addr, boolean enable);
536:
537: /**
538: * Checks if Bluetooth events are available on the native porting layer.
539: *
540: * @return <code>true</code> if there are pending events,
541: * <code>false</code> otherwise
542: */
543: private native boolean checkEvents();
544:
545: /**
546: * Reads binary event data from the native porting layer. This data can
547: * be interpreted differently by different subclasses of BluetoothStack.
548: *
549: * @param data byte array to be filled with data
550: * @return number of bytes read
551: */
552: protected native int readData(byte[] data);
553:
554: /**
555: * Creates Java String object from UTF-8 encoded string.
556: *
557: * @param buffer buffer containing the string in UTF-8 format
558: * @param offset offset of the first character in the buffer
559: * @param length length of the encoded string in bytes
560: * @return Java String object containing the string in UTF-16 format
561: */
562: protected static native String stringUTF8(byte[] buffer,
563: int offset, int length);
564: }
565:
566: /**
567: * Supplementary thread which periodically polls Bluetooth stack for events.
568: */
569: class PollingThread extends Thread {
570:
571: /** Instance of this class. */
572: private static PollingThread instance = new PollingThread();
573:
574: /** Flag indicating if this thread should be suspended. */
575: private static boolean suspended = true;
576:
577: /** Polling interval in milliseconds. */
578: private final int POLL_INTERVAL = 1000;
579:
580: /**
581: * Class constructor.
582: */
583: public PollingThread() {
584: }
585:
586: /**
587: * Suspends this thread.
588: */
589: public static void suspend() {
590: synchronized (instance) {
591: suspended = true;
592: }
593: }
594:
595: /**
596: * Resumes this thread.
597: */
598: public static void resume() {
599: try {
600: instance.start();
601: } catch (IllegalThreadStateException e) {
602: }
603: synchronized (instance) {
604: suspended = false;
605: instance.notify();
606: }
607: }
608:
609: /**
610: * Execution body.
611: */
612: public void run() {
613: BluetoothStack stack = BluetoothStack.getInstance();
614: while (true) {
615: try {
616: synchronized (this ) {
617: if (suspended) {
618: wait();
619: }
620: }
621: stack.pollEvents();
622: synchronized (this ) {
623: wait(POLL_INTERVAL);
624: }
625: } catch (InterruptedException e) {
626: break;
627: }
628: }
629: }
630:
631: }
632:
633: /**
634: * Supplementary thread which dispatches Bluetooth events to user notifiers.
635: */
636: class Dispatcher extends Thread {
637:
638: /** Instance of this class. */
639: private static Dispatcher instance = new Dispatcher();
640:
641: /** Vector containing Bluetooth events awaiting to be dispatched. */
642: private static Vector events = new Vector();
643:
644: /**
645: * Class constructor.
646: */
647: public Dispatcher() {
648: }
649:
650: /**
651: * Puts the event in the event queue.
652: * It also starts event dispatcher thread if it's not running yet.
653: *
654: * @param event the event to be enqueued
655: */
656: public static void enqueue(BluetoothEvent event) {
657: try {
658: instance.start();
659: } catch (IllegalThreadStateException e) {
660: }
661: synchronized (events) {
662: events.addElement(event);
663: events.notify();
664: }
665: }
666:
667: /**
668: * Execution body.
669: */
670: public void run() {
671: while (true) {
672: BluetoothEvent event = null;
673: synchronized (events) {
674: if (!events.isEmpty()) {
675: event = (BluetoothEvent) events.firstElement();
676: events.removeElementAt(0);
677: } else {
678: try {
679: events.wait();
680: } catch (InterruptedException e) {
681: break;
682: }
683: }
684: }
685: if (event != null) {
686: event.process();
687: }
688: }
689: }
690:
691: }
|