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: package com.sun.kvem.jsr082.bluetooth;
027:
028: import java.io.IOException;
029: import javax.bluetooth.BluetoothStateException;
030: import javax.bluetooth.DataElement;
031: import javax.bluetooth.DiscoveryListener;
032: import javax.bluetooth.RemoteDevice;
033: import javax.bluetooth.UUID;
034: import java.util.Hashtable;
035:
036: /**
037: * This class saves information about responses to SDP_ServiceSearchRequest
038: * and SDP_ServiceAttributeRequest requests
039: * and provides functionality of DiscoveryAgent.serviceSearch using
040: * multiple requests via SDPClient (Service Discovery Protocol)
041: *
042: */
043: class ServiceSearcher extends ServiceSearcherBase {
044:
045: /** Set to false in RR version - then the javac skip the code. */
046: private static final boolean DEBUG = false;
047:
048: /** this class name for debug. */
049: private static final String cn = "ServiceSearcher";
050:
051: /** ID of transaction this listener works in. */
052: private short transactionID;
053:
054: /**
055: * Listens for service discovery and is informed as a service is
056: * discovered.
057: */
058: private DiscoveryListener discListener;
059:
060: /** SDP client to send requests. */
061: private SDPClient sdp;
062:
063: /** Service records handles retrieved from a server response. */
064: private int[] handles;
065:
066: /** Number of service records handles processed. */
067: private int processedHandle;
068:
069: /** Indicates if this listener is inactive. */
070: private boolean inactive = false;
071:
072: /** Indicates if service search has been canceled. */
073: private boolean canceled = false;
074:
075: /** Indicates if listener notification has been called. */
076: private boolean notified = false;
077:
078: /** Current quantity of service discovering requests. */
079: private static int requestCounter = 0;
080:
081: /** Keeps the references of current service search requests. */
082: private static Hashtable searchers = new Hashtable();
083:
084: /**
085: * Creates ServiceSearcher and save all required info in it.
086: *
087: * @param attrSet list of attributes whose values are requested.
088: * @param uuidSet list of UUIDs that indicate services relevant to request.
089: * @param btDev remote Bluetooth device to listen response from.
090: * @param discListener discovery listener.
091: *
092: * @see SDPClient#serviceSearchRequest
093: * @see SDPClient#serviceAttributeRequest
094: */
095: ServiceSearcher(int[] attrSet, UUID[] uuidSet, RemoteDevice btDev,
096: DiscoveryListener discListener) {
097: super (attrSet, uuidSet, btDev);
098:
099: if (discListener == null) {
100: throw new NullPointerException("DiscoveryListener is null");
101: }
102: this .discListener = discListener;
103: }
104:
105: /**
106: * Starts SDP_ServiceSearchRequest.
107: *
108: * @see SDPClient#serviceSearchRequest
109: *
110: * @return ID of transaction that has been initiated by the request.
111: */
112: int start() throws BluetoothStateException {
113: synchronized (ServiceSearcher.class) {
114: if (requestCounter == ServiceRecordImpl.TRANS_MAX) {
115: throw new BluetoothStateException(
116: "Too much concurrent requests");
117: }
118: requestCounter++;
119: }
120: transactionID = SDPClient.newTransactionID();
121: searchers.put(new Integer(transactionID), this );
122:
123: try {
124: sdp = new SDPClient(btDev.getBluetoothAddress());
125: sdp.serviceSearchRequest(uuidSet, transactionID, this );
126: } catch (IOException ioe) {
127: if (DEBUG) {
128: ioe.printStackTrace();
129: }
130:
131: synchronized (this ) {
132: stop();
133: new NotifyListenerRunner(
134: DiscoveryListener.SERVICE_SEARCH_DEVICE_NOT_REACHABLE);
135: }
136: }
137:
138: return transactionID;
139: }
140:
141: /**
142: * Receives SDP_ErrorResponse and completes the search request activity
143: * by error reason.
144: *
145: * @param errorCode error code form SDP_ErrorResponse.
146: * @param info error details firm SDP_ErrorResponse.
147: * @param transactionID ID of transaction response got within.
148: */
149: public void errorResponse(int errorCode, String info,
150: int transactionID) {
151: if (DEBUG) {
152: System.out.println(cn + ".errorResponse: called");
153: }
154:
155: stop();
156:
157: if ((errorCode == SDP_INVALID_VERSION)
158: || (errorCode == SDP_INVALID_SYNTAX)
159: || (errorCode == SDP_INVALID_PDU_SIZE)
160: || (errorCode == SDP_INVALID_CONTINUATION_STATE)
161: || (errorCode == SDP_INSUFFICIENT_RESOURCES)) {
162: notifyListener(DiscoveryListener.SERVICE_SEARCH_ERROR);
163: System.err.println(info);
164: } else if (errorCode == SDP_INVALID_SR_HANDLE) {
165: notifyListener(DiscoveryListener.SERVICE_SEARCH_NO_RECORDS);
166: System.err.println(info);
167: } else if (errorCode == IO_ERROR) {
168: notifyListener(DiscoveryListener.SERVICE_SEARCH_DEVICE_NOT_REACHABLE);
169: } else if (errorCode == TERMINATED) {
170: new NotifyListenerRunner(
171: DiscoveryListener.SERVICE_SEARCH_TERMINATED);
172: }
173: }
174:
175: /**
176: * Receives array of handles retrieved form SDP_serviceSearchResponse.
177: *
178: * @param handleList service record handles retrieved from
179: * SDP_srviceSearchResponse.
180: * @param transactionID ID of transaction response has been received in.
181: */
182: public void serviceSearchResponse(int[] handleList,
183: int transactionID) {
184: if (DEBUG) {
185: System.out.println(cn + ".serviceSearchResponse: called");
186: }
187:
188: // there is no reason to perform response processing if search
189: // is canceled
190: if (isCanceled()) {
191: return;
192: }
193:
194: if (handleList == null || handleList.length == 0) {
195: stop();
196: notifyListener(DiscoveryListener.SERVICE_SEARCH_NO_RECORDS);
197: return;
198: }
199:
200: synchronized (this ) {
201: handles = handleList;
202: processedHandle = 0;
203: }
204:
205: try {
206: // there is no reason to request service attributes if service
207: // search is canceled
208: if (isCanceled()) {
209: return;
210: }
211: sdp.serviceAttributeRequest(handles[processedHandle],
212: attrSet, transactionID, this );
213: } catch (IOException ioe) {
214: if (DEBUG) {
215: ioe.printStackTrace();
216: }
217: stop();
218: notifyListener(DiscoveryListener.SERVICE_SEARCH_DEVICE_NOT_REACHABLE);
219: }
220: }
221:
222: /**
223: * Receives arrays of service record attributes and their values retrieved
224: * from server response.
225: */
226: public void serviceAttributeResponse(int[] attrIDs,
227: DataElement[] attributeValues, int transactionID) {
228: if (DEBUG) {
229: System.out
230: .println(cn + ".serviceAttributeResponse: called");
231: }
232:
233: // there is no reason to process service attributes if service
234: // search is canceled
235: if (isCanceled()) {
236: return;
237: }
238:
239: synchronized (this ) {
240: processedHandle++;
241: }
242:
243: if (attributeValues != null) {
244: ServiceRecordImpl[] serviceRecordSet = new ServiceRecordImpl[1];
245: serviceRecordSet[0] = new ServiceRecordImpl(btDev, attrIDs,
246: attributeValues);
247: try {
248: // The spec for DiscoveryAgent.cancelServiceSearch() says:
249: // "After receiving SERVICE_SEARCH_TERMINATED event,
250: // no further servicesDiscovered() events will occur
251: // as a result of this search."
252: if (isCanceled()) {
253: return;
254: }
255: discListener.servicesDiscovered(this .transactionID,
256: serviceRecordSet);
257: } catch (Throwable e) {
258: e.printStackTrace();
259: }
260: }
261:
262: if (processedHandle == handles.length) {
263: stop();
264: notifyListener(DiscoveryListener.SERVICE_SEARCH_COMPLETED);
265: return;
266: }
267:
268: try {
269: // there is no reason to continue attributes discovery if search
270: // is canceled
271: if (isCanceled()) {
272: return;
273: }
274: sdp.serviceAttributeRequest(handles[processedHandle],
275: attrSet, transactionID, this );
276: } catch (IOException ioe) {
277: if (DEBUG) {
278: ioe.printStackTrace();
279: }
280: stop();
281: notifyListener(DiscoveryListener.SERVICE_SEARCH_DEVICE_NOT_REACHABLE);
282: }
283: }
284:
285: /**
286: * Base class method not relevant to this subclass, must never be called.
287: */
288: public void serviceSearchAttributeResponse(int[] attrIDs,
289: DataElement[] attributeValues, int transactionID) {
290: if (DEBUG) {
291: System.out.println(cn
292: + ".serviceSearchAttributeResponse: called");
293: }
294: throw new RuntimeException("Unexpected call");
295: }
296:
297: /**
298: * Finishes the service searcher activity.
299: */
300: void stop() {
301: SDPClient sdp;
302: SDPClient.freeTransactionID(transactionID);
303: searchers.remove(new Integer(transactionID));
304:
305: synchronized (this ) {
306: if (this .sdp == null) {
307: return;
308: }
309: inactive = true;
310: sdp = this .sdp;
311: this .sdp = null;
312: }
313:
314: synchronized (ServiceSearcher.class) {
315: requestCounter--;
316: }
317:
318: try {
319: sdp.close();
320: } catch (IOException ioe) {
321: if (DEBUG) {
322: ioe.printStackTrace();
323: }
324: // ignore
325: }
326: }
327:
328: /**
329: * Cancels current transaction.
330: *
331: * @return false if there is no current transaction, cancels it and returns
332: * true otherwise.
333: */
334: boolean cancel() {
335: synchronized (this ) {
336: if (inactive) {
337: return false;
338: }
339: inactive = true;
340:
341: if (sdp == null) {
342: return false;
343: }
344:
345: if (canceled) {
346: return false;
347: }
348: canceled = true;
349: }
350:
351: // cancel running effective transaction if any.
352: // if sdp.cancelServiceSearch returns false (there is no running
353: // transactions) then call the notification directly.
354: if (!sdp.cancelServiceSearch(transactionID)) {
355: new NotifyListenerRunner(
356: DiscoveryListener.SERVICE_SEARCH_TERMINATED);
357: }
358:
359: return true;
360: }
361:
362: /**
363: * Determines whether the service search has been canceled by
364: * the application and did not complete.
365: *
366: * @return <code>true</code> indicates the service search has been
367: * canceled; <code>false</code> the application has not called
368: * cancel operation
369: */
370: private boolean isCanceled() {
371: return canceled;
372: }
373:
374: /**
375: * Cancels transaction with given ID.
376: *
377: * @param transactionID ID of transaction to be cancelled.
378: *
379: * @return false if there is no open transaction with ID given, true
380: * otherwise.
381: */
382: static boolean cancel(int transactionID) {
383: ServiceSearcher carrier = (ServiceSearcher) searchers
384: .get(new Integer(transactionID));
385:
386: if (carrier == null) {
387: return false;
388: } else {
389: return carrier.cancel();
390: }
391: }
392:
393: /**
394: * Notifies the listener that service search has
395: * been completed with specified response code.
396: *
397: * @param respCode response code.
398: */
399: private void notifyListener(int respCode) {
400: // guard against multiple notification calls
401: synchronized (this ) {
402: if (!notified) {
403: notified = true;
404: } else {
405: return;
406: }
407: }
408:
409: if (DEBUG) {
410: String codeStr = "Undefined";
411:
412: switch (respCode) {
413: case DiscoveryListener.SERVICE_SEARCH_COMPLETED:
414: codeStr = "SERVICE_SEARCH_COMPLETED";
415: break;
416:
417: case DiscoveryListener.SERVICE_SEARCH_DEVICE_NOT_REACHABLE:
418: codeStr = "SERVICE_SEARCH_DEVICE_NOT_REACHABLE";
419: break;
420:
421: case DiscoveryListener.SERVICE_SEARCH_ERROR:
422: codeStr = "SERVICE_SEARCH_ERROR";
423: break;
424:
425: case DiscoveryListener.SERVICE_SEARCH_NO_RECORDS:
426: codeStr = "SERVICE_SEARCH_NO_RECORDS";
427: break;
428:
429: case DiscoveryListener.SERVICE_SEARCH_TERMINATED:
430: codeStr = "SERVICE_SEARCH_TERMINATED";
431: break;
432: default:
433: }
434: System.out.println("serviceSearchCompleted:");
435: System.out.println("\ttransID=" + transactionID);
436: System.out.println("\trespCode=" + codeStr);
437: System.out.println("\tinactive=" + inactive);
438: }
439:
440: try {
441: discListener
442: .serviceSearchCompleted(transactionID, respCode);
443: } catch (Throwable e) {
444: e.printStackTrace();
445: }
446: }
447:
448: /**
449: * Runnable for launching <code>notifyListener()</code> in a separate
450: * thread.
451: *
452: * @see #notifyListener(int)
453: */
454: class NotifyListenerRunner implements Runnable {
455: /** Response code to pass to a listener. */
456: int respCode;
457:
458: /**
459: * Constructs Runnable instance to pass single response code, starts
460: * a thread for that.
461: *
462: * @param respCode response code value
463: */
464: NotifyListenerRunner(int respCode) {
465: this .respCode = respCode;
466: new Thread(this ).start();
467: }
468:
469: /**
470: * The <code>run()</code> method.
471: * @see java.lang.Runnable
472: */
473: public void run() {
474: notifyListener(respCode);
475: }
476: }
477: }
|