001: /*
002: *
003: * Copyright (c) 2007, Sun Microsystems, Inc.
004: *
005: * All rights reserved.
006: *
007: * Redistribution and use in source and binary forms, with or without
008: * modification, are permitted provided that the following conditions
009: * are met:
010: *
011: * * Redistributions of source code must retain the above copyright
012: * notice, this list of conditions and the following disclaimer.
013: * * Redistributions in binary form must reproduce the above copyright
014: * notice, this list of conditions and the following disclaimer in the
015: * documentation and/or other materials provided with the distribution.
016: * * Neither the name of Sun Microsystems nor the names of its contributors
017: * may be used to endorse or promote products derived from this software
018: * without specific prior written permission.
019: *
020: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
021: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
022: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
023: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
024: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
025: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
026: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
027: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
028: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
029: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
030: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
031: */
032: package example.bluetooth.demo;
033:
034: import java.io.IOException;
035: import java.io.InputStream;
036: import java.io.OutputStream;
037:
038: import java.util.Enumeration;
039: import java.util.Hashtable;
040: import java.util.Vector;
041:
042: // jsr082 API
043: import javax.bluetooth.BluetoothStateException;
044: import javax.bluetooth.DataElement;
045: import javax.bluetooth.DeviceClass;
046: import javax.bluetooth.DiscoveryAgent;
047: import javax.bluetooth.DiscoveryListener;
048: import javax.bluetooth.LocalDevice;
049: import javax.bluetooth.RemoteDevice;
050: import javax.bluetooth.ServiceRecord;
051: import javax.bluetooth.UUID;
052:
053: // midp/cldc API
054: import javax.microedition.io.Connector;
055: import javax.microedition.io.StreamConnection;
056: import javax.microedition.lcdui.Image;
057:
058: /**
059: * Initialize BT device, search for BT services,
060: * presents them to user and picks his/her choice,
061: * finally download the choosen image and present
062: * it to user.
063: *
064: * @version ,
065: */
066: final class BTImageClient implements Runnable, DiscoveryListener {
067: /** Describes this server */
068: private static final UUID PICTURES_SERVER_UUID = new UUID(
069: "F0E0D0C0B0A000908070605040302010", false);
070:
071: /** The attribute id of the record item with images names. */
072: private static final int IMAGES_NAMES_ATTRIBUTE_ID = 0x4321;
073:
074: /** Shows the engine is ready to work. */
075: private static final int READY = 0;
076:
077: /** Shows the engine is searching bluetooth devices. */
078: private static final int DEVICE_SEARCH = 1;
079:
080: /** Shows the engine is searching bluetooth services. */
081: private static final int SERVICE_SEARCH = 2;
082:
083: /** Keeps the current state of engine. */
084: private int state = READY;
085:
086: /** Keeps the discovery agent reference. */
087: private DiscoveryAgent discoveryAgent;
088:
089: /** Keeps the parent reference to process specific actions. */
090: private GUIImageClient parent;
091:
092: /** Becomes 'true' when this component is finalized. */
093: private boolean isClosed;
094:
095: /** Process the search/download requests. */
096: private Thread processorThread;
097:
098: /** Collects the remote devices found during a search. */
099: private Vector /* RemoteDevice */devices = new Vector();
100:
101: /** Collects the services found during a search. */
102: private Vector /* ServiceRecord */records = new Vector();
103:
104: /** Keeps the device discovery return code. */
105: private int discType;
106:
107: /** Keeps the services search IDs (just to be able to cancel them). */
108: private int[] searchIDs;
109:
110: /** Keeps the image name to be load. */
111: private String imageNameToLoad;
112:
113: /** Keeps the table of {name, Service} to process the user choice. */
114: private Hashtable base = new Hashtable();
115:
116: /** Informs the thread the download should be canceled. */
117: private boolean isDownloadCanceled;
118:
119: /** Optimization: keeps service search pattern. */
120: private UUID[] uuidSet;
121:
122: /** Optimization: keeps attributes list to be retrieved. */
123: private int[] attrSet;
124:
125: /**
126: * Constructs the bluetooth server, but it is initialized
127: * in the different thread to "avoid dead lock".
128: */
129: BTImageClient(GUIImageClient parent) {
130: this .parent = parent;
131:
132: // we have to initialize a system in different thread...
133: processorThread = new Thread(this );
134: processorThread.start();
135: }
136:
137: /**
138: * Process the search/download requests.
139: */
140: public void run() {
141: // initialize bluetooth first
142: boolean isBTReady = false;
143:
144: try {
145: // create/get a local device and discovery agent
146: LocalDevice localDevice = LocalDevice.getLocalDevice();
147: discoveryAgent = localDevice.getDiscoveryAgent();
148:
149: // remember we've reached this point.
150: isBTReady = true;
151: } catch (Exception e) {
152: System.err.println("Can't initialize bluetooth: " + e);
153: }
154:
155: parent.completeInitialization(isBTReady);
156:
157: // nothing to do if no bluetooth available
158: if (!isBTReady) {
159: return;
160: }
161:
162: // initialize some optimization variables
163: uuidSet = new UUID[2];
164:
165: // ok, we are interesting in btspp services only
166: uuidSet[0] = new UUID(0x1101);
167:
168: // and only known ones, that allows pictures
169: uuidSet[1] = PICTURES_SERVER_UUID;
170:
171: // we need an only service attribute actually
172: attrSet = new int[1];
173:
174: // it's "images names" one
175: attrSet[0] = IMAGES_NAMES_ATTRIBUTE_ID;
176:
177: // start processing the images search/download
178: processImagesSearchDownload();
179: }
180:
181: /**
182: * Invoked by system when a new remote device is found -
183: * remember the found device.
184: */
185: public void deviceDiscovered(RemoteDevice btDevice, DeviceClass cod) {
186: // same device may found several times during single search
187: if (devices.indexOf(btDevice) == -1) {
188: devices.addElement(btDevice);
189: }
190: }
191:
192: /**
193: * Invoked by system when device discovery is done.
194: * <p>
195: * Remember the discType
196: * and process its evaluation in another thread.
197: */
198: public void inquiryCompleted(int discType) {
199: this .discType = discType;
200:
201: synchronized (this ) {
202: notify();
203: }
204: }
205:
206: public void servicesDiscovered(int transID,
207: ServiceRecord[] servRecord) {
208: for (int i = 0; i < servRecord.length; i++) {
209: records.addElement(servRecord[i]);
210: }
211: }
212:
213: public void serviceSearchCompleted(int transID, int respCode) {
214: // first, find the service search transaction index
215: int index = -1;
216:
217: for (int i = 0; i < searchIDs.length; i++) {
218: if (searchIDs[i] == transID) {
219: index = i;
220:
221: break;
222: }
223: }
224:
225: // error - unexpected transaction index
226: if (index == -1) {
227: System.err.println("Unexpected transaction index: "
228: + transID);
229: // process the error case here
230: } else {
231: searchIDs[index] = -1;
232: }
233:
234: /*
235: * Actually, we do not care about the response code -
236: * if device is not reachable or no records, etc.
237: */
238:
239: // make sure it was the last transaction
240: for (int i = 0; i < searchIDs.length; i++) {
241: if (searchIDs[i] != -1) {
242: return;
243: }
244: }
245:
246: // ok, all of the transactions are completed
247: synchronized (this ) {
248: notify();
249: }
250: }
251:
252: /** Sets the request to search the devices/services. */
253: void requestSearch() {
254: synchronized (this ) {
255: notify();
256: }
257: }
258:
259: /** Cancel's the devices/services search. */
260: void cancelSearch() {
261: synchronized (this ) {
262: if (state == DEVICE_SEARCH) {
263: discoveryAgent.cancelInquiry(this );
264: } else if (state == SERVICE_SEARCH) {
265: for (int i = 0; i < searchIDs.length; i++) {
266: discoveryAgent.cancelServiceSearch(searchIDs[i]);
267: }
268: }
269: }
270: }
271:
272: /** Sets the request to load the specified image. */
273: void requestLoad(String name) {
274: synchronized (this ) {
275: imageNameToLoad = name;
276: notify();
277: }
278: }
279:
280: /** Cancel's the image download. */
281: void cancelLoad() {
282: /*
283: * The image download process is done by
284: * this class's thread (not by a system one),
285: * so no need to wake up the current thread -
286: * it's running already.
287: */
288: isDownloadCanceled = true;
289: }
290:
291: /**
292: * Destroy a work with bluetooth - exits the accepting
293: * thread and close notifier.
294: */
295: void destroy() {
296: synchronized (this ) {
297: isClosed = true;
298: isDownloadCanceled = true;
299: notify();
300: }
301:
302: // wait for acceptor thread is done
303: try {
304: processorThread.join();
305: } catch (InterruptedException e) {
306: } // ignore
307: }
308:
309: /**
310: * Processes images search/download until component is closed
311: * or system error has happen.
312: */
313: private synchronized void processImagesSearchDownload() {
314: while (!isClosed) {
315: // wait for new search request from user
316: state = READY;
317:
318: try {
319: wait();
320: } catch (InterruptedException e) {
321: System.err.println("Unexpected interruption: " + e);
322:
323: return;
324: }
325:
326: // check the component is destroyed
327: if (isClosed) {
328: return;
329: }
330:
331: // search for devices
332: if (!searchDevices()) {
333: return;
334: } else if (devices.size() == 0) {
335: continue;
336: }
337:
338: // search for services now
339: if (!searchServices()) {
340: return;
341: } else if (records.size() == 0) {
342: continue;
343: }
344:
345: // ok, something was found - present the result to user now
346: if (!presentUserSearchResults()) {
347: // services are found, but no names there
348: continue;
349: }
350:
351: // the several download requests may be processed
352: while (true) {
353: // this download is not canceled, right?
354: isDownloadCanceled = false;
355:
356: // ok, wait for download or need to wait for next search
357: try {
358: wait();
359: } catch (InterruptedException e) {
360: System.err.println("Unexpected interruption: " + e);
361:
362: return;
363: }
364:
365: // check the component is destroyed
366: if (isClosed) {
367: return;
368: }
369:
370: // this means "go to the beginning"
371: if (imageNameToLoad == null) {
372: break;
373: }
374:
375: // load selected image data
376: Image img = loadImage();
377:
378: // this should never happen - monitor is taken...
379: if (isClosed) {
380: return;
381: }
382:
383: if (isDownloadCanceled) {
384: continue; // may be next image to be download
385: }
386:
387: if (img == null) {
388: parent.informLoadError("Can't load image: "
389: + imageNameToLoad);
390:
391: continue; // may be next image to be download
392: }
393:
394: // ok, show image to user
395: parent.showImage(img, imageNameToLoad);
396:
397: // may be next image to be download
398: continue;
399: }
400: }
401: }
402:
403: /**
404: * Search for bluetooth devices.
405: *
406: * @return false if should end the component work.
407: */
408: private boolean searchDevices() {
409: // ok, start a new search then
410: state = DEVICE_SEARCH;
411: devices.removeAllElements();
412:
413: try {
414: discoveryAgent.startInquiry(DiscoveryAgent.GIAC, this );
415: } catch (BluetoothStateException e) {
416: System.err.println("Can't start inquiry now: " + e);
417: parent.informSearchError("Can't start device search");
418:
419: return true;
420: }
421:
422: try {
423: wait(); // until devices are found
424: } catch (InterruptedException e) {
425: System.err.println("Unexpected interruption: " + e);
426:
427: return false;
428: }
429:
430: // this "wake up" may be caused by 'destroy' call
431: if (isClosed) {
432: return false;
433: }
434:
435: // no?, ok, let's check the return code then
436: switch (discType) {
437: case INQUIRY_ERROR:
438: parent.informSearchError("Device discovering error...");
439:
440: // fall through
441: case INQUIRY_TERMINATED:
442: // make sure no garbage in found devices list
443: devices.removeAllElements();
444:
445: // nothing to report - go to next request
446: break;
447:
448: case INQUIRY_COMPLETED:
449:
450: if (devices.size() == 0) {
451: parent.informSearchError("No devices in range");
452: }
453:
454: // go to service search now
455: break;
456:
457: default:
458: // what kind of system you are?... :(
459: System.err.println("system error:"
460: + " unexpected device discovery code: " + discType);
461: destroy();
462:
463: return false;
464: }
465:
466: return true;
467: }
468:
469: /**
470: * Search for proper service.
471: *
472: * @return false if should end the component work.
473: */
474: private boolean searchServices() {
475: state = SERVICE_SEARCH;
476: records.removeAllElements();
477: searchIDs = new int[devices.size()];
478:
479: boolean isSearchStarted = false;
480:
481: for (int i = 0; i < devices.size(); i++) {
482: RemoteDevice rd = (RemoteDevice) devices.elementAt(i);
483:
484: try {
485: searchIDs[i] = discoveryAgent.searchServices(attrSet,
486: uuidSet, rd, this );
487: } catch (BluetoothStateException e) {
488: System.err.println("Can't search services for: "
489: + rd.getBluetoothAddress() + " due to " + e);
490: searchIDs[i] = -1;
491:
492: continue;
493: }
494:
495: isSearchStarted = true;
496: }
497:
498: // at least one of the services search should be found
499: if (!isSearchStarted) {
500: parent.informSearchError("Can't search services.");
501:
502: return true;
503: }
504:
505: try {
506: wait(); // until services are found
507: } catch (InterruptedException e) {
508: System.err.println("Unexpected interruption: " + e);
509:
510: return false;
511: }
512:
513: // this "wake up" may be caused by 'destroy' call
514: if (isClosed) {
515: return false;
516: }
517:
518: // actually, no services were found
519: if (records.size() == 0) {
520: parent.informSearchError("No proper services were found");
521: }
522:
523: return true;
524: }
525:
526: /**
527: * Gets the collection of the images titles (names)
528: * from the services, prepares a hashtable to match
529: * the image name to a services list, presents the images names
530: * to user finally.
531: *
532: * @return false if no names in found services.
533: */
534: private boolean presentUserSearchResults() {
535: base.clear();
536:
537: for (int i = 0; i < records.size(); i++) {
538: ServiceRecord sr = (ServiceRecord) records.elementAt(i);
539:
540: // get the attribute with images names
541: DataElement de = sr
542: .getAttributeValue(IMAGES_NAMES_ATTRIBUTE_ID);
543:
544: if (de == null) {
545: System.err
546: .println("Unexpected service - missed attribute");
547:
548: continue;
549: }
550:
551: // get the images names from this attribute
552: Enumeration deEnum = (Enumeration) de.getValue();
553:
554: while (deEnum.hasMoreElements()) {
555: de = (DataElement) deEnum.nextElement();
556:
557: String name = (String) de.getValue();
558:
559: // name may be stored already
560: Object obj = base.get(name);
561:
562: // that's either the ServiceRecord or Vector
563: if (obj != null) {
564: Vector v;
565:
566: if (obj instanceof ServiceRecord) {
567: v = new Vector();
568: v.addElement(obj);
569: } else {
570: v = (Vector) obj;
571: }
572:
573: v.addElement(sr);
574: obj = v;
575: } else {
576: obj = sr;
577: }
578:
579: base.put(name, obj);
580: }
581: }
582:
583: return parent.showImagesNames(base);
584: }
585:
586: /**
587: * Loads selected image data.
588: */
589: private Image loadImage() {
590: if (imageNameToLoad == null) {
591: System.err.println("Error: imageNameToLoad=null");
592:
593: return null;
594: }
595:
596: // ok, get the list of service records
597: ServiceRecord[] sr = null;
598: Object obj = base.get(imageNameToLoad);
599:
600: if (obj == null) {
601: System.err.println("Error: no record for: "
602: + imageNameToLoad);
603:
604: return null;
605: } else if (obj instanceof ServiceRecord) {
606: sr = new ServiceRecord[] { (ServiceRecord) obj };
607: } else {
608: Vector v = (Vector) obj;
609: sr = new ServiceRecord[v.size()];
610:
611: for (int i = 0; i < v.size(); i++) {
612: sr[i] = (ServiceRecord) v.elementAt(i);
613: }
614: }
615:
616: // now try to load the image from each services one by one
617: for (int i = 0; i < sr.length; i++) {
618: StreamConnection conn = null;
619: String url = null;
620:
621: // the process may be canceled
622: if (isDownloadCanceled) {
623: return null;
624: }
625:
626: // first - connect
627: try {
628: url = sr[i].getConnectionURL(
629: ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false);
630: conn = (StreamConnection) Connector.open(url);
631: } catch (IOException e) {
632: System.err.println("Note: can't connect to: " + url);
633:
634: // ignore
635: continue;
636: }
637:
638: // then open a steam and write a name
639: try {
640: OutputStream out = conn.openOutputStream();
641: out.write(imageNameToLoad.length()); // length is 1 byte
642: out.write(imageNameToLoad.getBytes());
643: out.flush();
644: out.close();
645: } catch (IOException e) {
646: System.err.println("Can't write to server for: " + url);
647:
648: // close stream connection
649: try {
650: conn.close();
651: } catch (IOException ee) {
652: } // ignore
653:
654: continue;
655: }
656:
657: // then open a steam and read an image
658: byte[] imgData = null;
659:
660: try {
661: InputStream in = conn.openInputStream();
662:
663: // read a length first
664: int length = in.read() << 8;
665: length |= in.read();
666:
667: if (length <= 0) {
668: throw new IOException("Can't read a length");
669: }
670:
671: // read the image now
672: imgData = new byte[length];
673: length = 0;
674:
675: while (length != imgData.length) {
676: int n = in.read(imgData, length, imgData.length
677: - length);
678:
679: if (n == -1) {
680: throw new IOException("Can't read a image data");
681: }
682:
683: length += n;
684: }
685:
686: in.close();
687: } catch (IOException e) {
688: System.err
689: .println("Can't read from server for: " + url);
690:
691: continue;
692: } finally {
693: // close stream connection anyway
694: try {
695: conn.close();
696: } catch (IOException e) {
697: } // ignore
698: }
699:
700: // ok, may it's a chance
701: Image img = null;
702:
703: try {
704: img = Image.createImage(imgData, 0, imgData.length);
705: } catch (Exception e) {
706: // may be next time
707: System.err.println("Error: wrong image data from: "
708: + url);
709:
710: continue;
711: }
712:
713: return img;
714: }
715:
716: return null;
717: }
718: } // end of class 'BTImageClient' definition
|