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.midp.io.j2me.btspp;
027:
028: import javax.bluetooth.*;
029:
030: import javax.microedition.io.StreamConnection;
031: import javax.microedition.io.StreamConnectionNotifier;
032: import java.io.InputStream;
033: import java.io.OutputStream;
034: import java.io.DataInputStream;
035: import java.io.DataOutputStream;
036: import java.io.IOException;
037: import java.io.InterruptedIOException;
038:
039: import java.util.Vector;
040: import java.util.Enumeration;
041: import com.sun.midp.io.BluetoothUrl;
042: import com.sun.midp.jsr082.bluetooth.BluetoothPush;
043: import com.sun.midp.midlet.MIDletStateHandler;
044: import com.sun.midp.midlet.MIDletSuite;
045: import com.sun.kvem.jsr082.bluetooth.SDDB;
046: import com.sun.kvem.jsr082.bluetooth.BCC;
047: import com.sun.kvem.jsr082.bluetooth.ServiceRecordImpl;
048: import com.sun.kvem.jsr082.bluetooth.BluetoothNotifier;
049:
050: /**
051: * Bluetooth Serial Port Profile notifier implementation.
052: */
053: public class BTSPPNotifierImpl extends BluetoothNotifier implements
054: StreamConnectionNotifier {
055:
056: /** Static initializer. */
057: static {
058: initialize();
059: }
060:
061: /**
062: * Native static class initializer.
063: */
064: private native static void initialize();
065:
066: /**
067: * Native finalizer.
068: * Releases all native resources used by this connection.
069: */
070: private native void finalize();
071:
072: /**
073: * Identidies this connection at native layer,
074: * <code>-1<code> if connection is not open.
075: */
076: private int handle = -1;
077:
078: // IMPL_NOTE make private when moving emul below the porting layer completed.
079: /**
080: * Temporary stores peer's native handle for accepted connection.
081: * Used by <code>doAccept</code> method.
082: */
083: int peerHandle = -1;
084:
085: /**
086: * Temporary stores remote device address for accepted connection.
087: */
088: byte[] peerAddress = new byte[6];
089:
090: /** Indicates whether notifier is listening for incoming connections. */
091: private boolean isListenMode = false;
092:
093: /**
094: * Channel Id.
095: */
096: private int cid;
097:
098: /** Keeps channel id for service record validation. */
099: private DataElement CHANNEL_ID;
100:
101: /** Keeps L2CAP UUID for service record validation. */
102: static final DataElement L2CAP_UUID = new DataElement(
103: DataElement.UUID, new UUID(0x0100));
104:
105: /** Keeps RFCOMM UUID for service record validation. */
106: static final DataElement RFCOMM_UUID = new DataElement(
107: DataElement.UUID, new UUID(0x0003));
108:
109: /** Keeps SPP UUID for service record validation. */
110: static final DataElement SPP_UUID = new DataElement(
111: DataElement.UUID, new UUID(0x1101));
112:
113: /** Bluetooth PushRegistry handle, used in native methods only. */
114: private int pushHandle = 0;
115:
116: /**
117: * Creates instance of <code>BTSPPNotifierImpl</code>.
118: *
119: * @param url <code>BluetoothUrl</code> that represents server
120: * connection string to create notifier for.
121: * @param mode I/O access mode
122: * @throws IOException if there is no available channels to open connection
123: * @throws ServiceRegistrationException if there is no available channel
124: */
125: BTSPPNotifierImpl(BluetoothUrl url, int mode) throws IOException,
126: ServiceRegistrationException {
127: super (url, mode);
128: MIDletSuite suite = MIDletStateHandler.getMidletStateHandler()
129: .getMIDletSuite();
130: String connUrl = "btspp:" + url.caseSensitiveUrl;
131: if (suite != null && pushCheckout(connUrl, suite.getID())) {
132: serviceRec = BluetoothPush.getServiceRecord(this , connUrl);
133: checkServiceRecord();
134: } else {
135: serviceRec = createServiceRecord(this , url, doCreate(url));
136: }
137: }
138:
139: /**
140: * Checks out (takes ownership of) an active server connection maintained
141: * by push subsystem.
142: *
143: * @param url URL used during registration of the push entry
144: * @param suiteId suite id
145: * @return true if the operation succeeds, false otherwise
146: */
147: private native boolean pushCheckout(String url, int suiteId);
148:
149: /**
150: * Creates an empty service record for the given URL and channel value.
151: *
152: * @param notifier SPP notifier object to be associated with the record
153: * @param url URL from which a new record is constructed
154: * @param cn channel value assigned to the notifier
155: * @return a new service record instance
156: */
157: public static ServiceRecordImpl createServiceRecord(
158: BTSPPNotifierImpl notifier, BluetoothUrl url, int cn) {
159: DataElement serviceList = new DataElement(DataElement.DATSEQ);
160: serviceList.addElement(new DataElement(DataElement.UUID,
161: new UUID(url.uuid, false)));
162: serviceList.addElement(SPP_UUID);
163: DataElement protocolList = new DataElement(DataElement.DATSEQ);
164: DataElement protocol = new DataElement(DataElement.DATSEQ);
165: protocol.addElement(L2CAP_UUID);
166: protocolList.addElement(protocol);
167: protocol = new DataElement(DataElement.DATSEQ);
168: protocol.addElement(RFCOMM_UUID);
169: if (notifier != null) {
170: notifier.CHANNEL_ID = new DataElement(DataElement.U_INT_1,
171: cn);
172: protocol.addElement(notifier.CHANNEL_ID);
173: } else {
174: protocol
175: .addElement(new DataElement(DataElement.U_INT_1, cn));
176: }
177: protocolList.addElement(protocol);
178: int[] attrIDs;
179: DataElement[] attrVals;
180: if (url.name != null) {
181: DataElement name = new DataElement(DataElement.STRING,
182: url.name);
183: attrIDs = new int[] {
184: ServiceRecordImpl.SERVICE_CLASS_ATTR_ID,
185: ServiceRecordImpl.PROTOCOL_DESCRIPTOR_LIST,
186: ServiceRecordImpl.NAME_ATTR_ID };
187: attrVals = new DataElement[] { serviceList, protocolList,
188: name };
189: } else {
190: attrIDs = new int[] {
191: ServiceRecordImpl.SERVICE_CLASS_ATTR_ID,
192: ServiceRecordImpl.PROTOCOL_DESCRIPTOR_LIST };
193: attrVals = new DataElement[] { serviceList, protocolList };
194: }
195: return new ServiceRecordImpl(notifier, attrIDs, attrVals);
196: }
197:
198: /**
199: * Creates an empty service record for the given URL
200: *
201: * @param url URL from which a new record is constructed
202: * @return a new service record instance
203: */
204: public static ServiceRecordImpl createServiceRecord(String url) {
205: return createServiceRecord(null, new BluetoothUrl(url), 0);
206: }
207:
208: /**
209: * Ensures that the service record is valid.
210: *
211: * @throws ServiceRegistrationException if the structure of the
212: * <code>srvRecord</code> is missing any mandatory service
213: * attributes, or if an attempt has been made to change any of the
214: * values described as fixed
215: */
216: protected void checkServiceRecord()
217: throws ServiceRegistrationException {
218: synchronized (serviceRec) {
219: // check if the ServiceClassIDList is not missing
220: if (serviceRec
221: .getAttributeValue(ServiceRecordImpl.SERVICE_CLASS_ATTR_ID) == null) {
222: throw new ServiceRegistrationException(
223: "ServiceClassIDList is missing.");
224: }
225: // check the ProtocolList is not missed
226: DataElement protocolList = serviceRec
227: .getAttributeValue(ServiceRecordImpl.PROTOCOL_DESCRIPTOR_LIST);
228: if (protocolList == null) {
229: throw new ServiceRegistrationException(
230: "ProtocolDescriptorList is missing.");
231: }
232: Enumeration protocolListEnum = (Enumeration) protocolList
233: .getValue();
234: boolean l2cap = false;
235: boolean rfcomm = false;
236: while (protocolListEnum.hasMoreElements()) {
237: Enumeration protocolEnum = (Enumeration) ((DataElement) protocolListEnum
238: .nextElement()).getValue();
239: // check that L2CAP/RFCOMM UUIDs are not missing, check the CN
240: // is not missing and the value has not changed
241: while (protocolEnum.hasMoreElements()) {
242: DataElement element = (DataElement) protocolEnum
243: .nextElement();
244: if (compareDataElements(element, L2CAP_UUID)) {
245: l2cap = true;
246: }
247: if (compareDataElements(element, RFCOMM_UUID)) {
248: rfcomm = true;
249: if (!protocolEnum.hasMoreElements()
250: || !compareDataElements(
251: (DataElement) protocolEnum
252: .nextElement(),
253: CHANNEL_ID)) {
254: throw new ServiceRegistrationException(
255: "Channel value has changed.");
256: }
257: }
258: if (l2cap && rfcomm) {
259: return;
260: }
261: }
262: }
263: }
264: throw new ServiceRegistrationException("L2CAP UUID is missing.");
265: }
266:
267: /**
268: * Ensures that this notifier can accept connections.
269: *
270: * @throws IOException if notifier is closed or device is not
271: * in connectable mode
272: * @throws ServiceRegistrationException if the service record is not valid
273: */
274: private void ensureConnectable() throws IOException {
275: if (isClosed) {
276: throw new BluetoothConnectionException(
277: BluetoothConnectionException.FAILED_NOINFO,
278: "Notifier is closed.");
279: }
280: if (!BCC.getInstance().isConnectable()) {
281: throw new BluetoothStateException(
282: "The device is not connectable.");
283: }
284: checkServiceRecord();
285: }
286:
287: /**
288: * Accepts client connection to the service this notifier is assigned to.
289: * Adds corresponding service record to the SDDB, blocks until a successfull
290: * connection to a client is established, retrieves the connection.
291: *
292: * @return bi-directional connection to a client just accepted.
293: *
294: * @throws IOException if notifier is closed or device is not
295: * in connectable mode.
296: */
297: public StreamConnection acceptAndOpen() throws IOException,
298: ServiceRegistrationException {
299: ensureConnectable();
300:
301: // switch on listen mode if it has not been done yet
302: doListen();
303:
304: // adds the record only if is not yet in SDDB
305: SDDB.getInstance().updateServiceRecord(serviceRec);
306:
307: StreamConnection client;
308: do {
309: ensureConnectable();
310:
311: // accept incoming connections if any
312: client = doAccept();
313: } while (client == null);
314:
315: return client;
316: }
317:
318: /**
319: * Closes this notifier making corresponding service record inaccessible.
320: * updates the information on the communication server.
321: *
322: * @throws IOException if an error occured lower in Bluetooth stack.
323: */
324: public void close() throws IOException {
325: if (isClosed) {
326: return;
327: }
328: isClosed = true;
329:
330: SDDB.getInstance().removeServiceRecord(serviceRec);
331:
332: doClose();
333: }
334:
335: /**
336: * Force listen mode by calling underlying stack methods.
337: *
338: * @throws IOException if an error occured
339: */
340:
341: private void doListen() throws IOException {
342: // force listening if it had not been done yet
343: if (!isListenMode) {
344: listen0();
345:
346: isListenMode = true;
347: }
348: }
349:
350: /**
351: * Force Bluetooth stack to listen for incoming client connections.
352: *
353: * Note: the method gets native connection handle directly from
354: * <code>handle<code> field of <code>BTSPPNotifierImpl</code> object.
355: *
356: * @throws IOException if an I/O error occurs
357: */
358: private native void listen0() throws IOException;
359:
360: /**
361: * Advertises acception by calling underlying stack methods.
362: *
363: * @return BTSPPConnection instance to work with accepted client
364: */
365: private StreamConnection doAccept() throws IOException {
366: if (!isListenMode) {
367: throw new BluetoothStateException(
368: "Device is not in listen mode");
369: }
370:
371: /*
372: * Note: native handle is set to peerHandleID field directly
373: * by accept0 method and retrieved by L2CAPConnectionImpl constructor.
374: */
375: accept0();
376:
377: return new BTSPPConnectionImpl(url, mode, this );
378: }
379:
380: /**
381: * Accepts incoming client connection request.
382: *
383: * Note: the method gets native connection handle directly from
384: * <code>handle<code> field of <code>BTSPPNotifierImpl</code> object.
385: *
386: * Note: new native connection handle to work with accepted incoming
387: * client connection is setted directly to <code>handle</code> field of
388: * appropriate <code>BTSPPConnectionImpl</code> object.
389: *
390: * @return <code>0</code> if incoming client connection was successfully
391: * accepted;
392: * <code>-1</code> if there is no pending incoming connections
393: * @throws IOException if an I/O error occurs
394: */
395: private native int accept0() throws IOException;
396:
397: /**
398: * Closes this notifier at native layer.
399: */
400: private void doClose() throws IOException {
401: close0();
402: }
403:
404: /**
405: * Closes this server connection.
406: * Releases all native resources (such as sockets) owned by this notifier.
407: *
408: * Note: the method gets native connection handle directly from
409: * <code>handle<code> field of <code>BTSPPNotifierImpl</code> object.
410: *
411: * @throws IOException IOException if an I/O error occurs
412: */
413: private native void close0() throws IOException;
414:
415: /**
416: * Creates an instanse of server connection at native layer.
417: *
418: * @param url <code>BluetoothUrl</code> that represents server
419: * connection string to create notifier for
420: * @return selected channel number to listen for incoming connections
421: */
422: private int doCreate(BluetoothUrl url) throws IOException {
423: return create0(url.authenticate, url.authorize, url.encrypt,
424: url.master);
425: }
426:
427: /**
428: * Creates a server connection.
429: *
430: * Note: the method sets native connection handle directly to
431: * <code>handle<code> field of <code>BTSPPNotifierImpl</code> object.
432: *
433: * @param auth <code>true</code> if authication is required
434: * @param authz <code>true</code> if authorization is required
435: * @param enc <code>true</code> indicates
436: * what connection must be encrypted
437: * @param master <code>true</code> if client requires to be
438: * a connection's master
439: * @return selected channel number to listen for incoming connections on
440: * @throws IOException IOException if an I/O error occurs
441: */
442: private native int create0(boolean auth, boolean authz,
443: boolean enc, boolean master) throws IOException;
444:
445: }
|