FileDocCategorySizeDatePackage
BTSPPNotifierImpl.javaAPI DocphoneME MR2 API (J2ME)16026Wed May 02 18:00:30 BST 2007com.sun.midp.io.j2me.btspp

BTSPPNotifierImpl.java

/*
 *
 *
 * Copyright  1990-2007 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt).
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions.
 */
package com.sun.midp.io.j2me.btspp;

import javax.bluetooth.*;

import javax.microedition.io.StreamConnection;
import javax.microedition.io.StreamConnectionNotifier;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InterruptedIOException;

import java.util.Vector;
import java.util.Enumeration;
import com.sun.midp.io.BluetoothUrl;
import com.sun.midp.jsr082.bluetooth.BluetoothPush;
import com.sun.midp.midlet.MIDletStateHandler;
import com.sun.midp.midlet.MIDletSuite;
import com.sun.kvem.jsr082.bluetooth.SDDB;
import com.sun.kvem.jsr082.bluetooth.BCC;
import com.sun.kvem.jsr082.bluetooth.ServiceRecordImpl;
import com.sun.kvem.jsr082.bluetooth.BluetoothNotifier;

/**
 * Bluetooth Serial Port Profile notifier implementation.
 */
public class BTSPPNotifierImpl extends BluetoothNotifier
    implements StreamConnectionNotifier {

    /** Static initializer. */
    static {
        initialize();
    }

    /**
     * Native static class initializer.
     */
    private native static void initialize();

    /**
     * Native finalizer.
     * Releases all native resources used by this connection.
     */
    private native void finalize();

    /**
     * Identidies this connection at native layer,
     * <code>-1<code> if connection is not open.
     */
    private int handle = -1;

    // IMPL_NOTE make private when moving emul below the porting layer completed.
    /**
     * Temporary stores peer's native handle for accepted connection.
     * Used by <code>doAccept</code> method.
     */
    int peerHandle = -1;

    /**
     * Temporary stores remote device address for accepted connection.
     */
    byte[] peerAddress = new byte[6];

    /** Indicates whether notifier is listening for incoming connections. */
    private boolean isListenMode = false;

    /**
     * Channel Id.
     */
    private int cid;

    /** Keeps channel id for service record validation. */
    private DataElement CHANNEL_ID;

    /** Keeps L2CAP UUID for service record validation. */
    static final DataElement L2CAP_UUID =
        new DataElement(DataElement.UUID, new UUID(0x0100));

    /** Keeps RFCOMM UUID for service record validation. */
    static final DataElement RFCOMM_UUID =
        new DataElement(DataElement.UUID, new UUID(0x0003));

    /** Keeps SPP UUID for service record validation. */
    static final DataElement SPP_UUID =
        new DataElement(DataElement.UUID, new UUID(0x1101));

    /** Bluetooth PushRegistry handle, used in native methods only. */
    private int pushHandle = 0;

    /**
     * Creates instance of <code>BTSPPNotifierImpl</code>.
     *
     * @param url <code>BluetoothUrl</code> that represents server
     *        connection string to create notifier for.
     * @param mode I/O access mode
     * @throws IOException if there is no available channels to open connection
     * @throws ServiceRegistrationException if there is no available channel
     */
    BTSPPNotifierImpl(BluetoothUrl url, int mode) throws IOException,
               ServiceRegistrationException {
        super(url, mode);
        MIDletSuite suite = MIDletStateHandler.getMidletStateHandler().
                getMIDletSuite();
        String connUrl = "btspp:" + url.caseSensitiveUrl;
        if (suite != null && pushCheckout(connUrl, suite.getID())) {
            serviceRec = BluetoothPush.getServiceRecord(this, connUrl);
            checkServiceRecord();
        } else {
            serviceRec = createServiceRecord(this, url, doCreate(url));
        }
    }

    /**
     * Checks out (takes ownership of) an active server connection maintained
     * by push subsystem.
     *
     * @param url URL used during registration of the push entry
     * @param suiteId suite id
     * @return true if the operation succeeds, false otherwise
     */
    private native boolean pushCheckout(String url, int suiteId);

    /**
     * Creates an empty service record for the given URL and channel value.
     *
     * @param notifier SPP notifier object to be associated with the record
     * @param url URL from which a new record is constructed
     * @param cn channel value assigned to the notifier
     * @return a new service record instance
     */
    public static ServiceRecordImpl createServiceRecord(
            BTSPPNotifierImpl notifier, BluetoothUrl url, int cn) {
        DataElement serviceList = new DataElement(DataElement.DATSEQ);
        serviceList.addElement(new DataElement(DataElement.UUID,
                new UUID(url.uuid, false)));
        serviceList.addElement(SPP_UUID);
        DataElement protocolList = new DataElement(DataElement.DATSEQ);
        DataElement protocol = new DataElement(DataElement.DATSEQ);
        protocol.addElement(L2CAP_UUID);
        protocolList.addElement(protocol);
        protocol = new DataElement(DataElement.DATSEQ);
        protocol.addElement(RFCOMM_UUID);
        if (notifier != null) {
            notifier.CHANNEL_ID = new DataElement(DataElement.U_INT_1, cn);
            protocol.addElement(notifier.CHANNEL_ID);
        } else {
            protocol.addElement(new DataElement(DataElement.U_INT_1, cn));
        }
        protocolList.addElement(protocol);
        int[] attrIDs;
        DataElement[] attrVals;
        if (url.name != null) {
            DataElement name = new DataElement(DataElement.STRING, url.name);
            attrIDs = new int[] {
                ServiceRecordImpl.SERVICE_CLASS_ATTR_ID,
                ServiceRecordImpl.PROTOCOL_DESCRIPTOR_LIST,
                ServiceRecordImpl.NAME_ATTR_ID
            };
            attrVals = new DataElement[] { serviceList, protocolList, name };
        } else {
            attrIDs = new int[] {
                ServiceRecordImpl.SERVICE_CLASS_ATTR_ID,
                ServiceRecordImpl.PROTOCOL_DESCRIPTOR_LIST
            };
            attrVals = new DataElement[] { serviceList, protocolList };
        }
        return new ServiceRecordImpl(notifier, attrIDs, attrVals);
    }

    /**
     * Creates an empty service record for the given URL
     *
     * @param url URL from which a new record is constructed
     * @return a new service record instance
     */
    public static ServiceRecordImpl createServiceRecord(String url) {
        return createServiceRecord(null, new BluetoothUrl(url), 0);
    }

    /**
     * Ensures that the service record is valid.
     *
     * @throws ServiceRegistrationException if the structure of the
     *         <code>srvRecord</code> is  missing any mandatory service
     *         attributes, or if an attempt has been made to change any of the
     *         values described as fixed
     */
    protected void checkServiceRecord() throws ServiceRegistrationException {
        synchronized (serviceRec) {
            // check if the ServiceClassIDList is not missing
            if (serviceRec.getAttributeValue(
                    ServiceRecordImpl.SERVICE_CLASS_ATTR_ID) == null) {
                throw new ServiceRegistrationException(
                        "ServiceClassIDList is missing.");
            }
            // check the ProtocolList is not missed
            DataElement protocolList = serviceRec.getAttributeValue(
                    ServiceRecordImpl.PROTOCOL_DESCRIPTOR_LIST);
            if (protocolList == null) {
                throw new ServiceRegistrationException(
                        "ProtocolDescriptorList is missing.");
            }
            Enumeration protocolListEnum =
                    (Enumeration)protocolList.getValue();
            boolean l2cap = false;
            boolean rfcomm = false;
            while (protocolListEnum.hasMoreElements()) {
                Enumeration protocolEnum = (Enumeration)((DataElement)
                        protocolListEnum.nextElement()).getValue();
                // check that L2CAP/RFCOMM UUIDs are not missing, check the CN
                // is not missing and the value has not changed
                while (protocolEnum.hasMoreElements()) {
                    DataElement element = (DataElement)protocolEnum.
                            nextElement();
                    if (compareDataElements(element, L2CAP_UUID)) {
                        l2cap = true;
                    }
                    if (compareDataElements(element, RFCOMM_UUID)) {
                        rfcomm = true;
                        if (!protocolEnum.hasMoreElements() ||
                                !compareDataElements((DataElement)protocolEnum.
                                nextElement(), CHANNEL_ID)) {
                            throw new ServiceRegistrationException(
                                    "Channel value has changed.");
                        }
                    }
                    if (l2cap && rfcomm) {
                        return;
                    }
                }
            }
        }
        throw new ServiceRegistrationException("L2CAP UUID is missing.");
    }

    /**
     * Ensures that this notifier can accept connections.
     *
     * @throws IOException if notifier is closed or device is not
     *         in connectable mode
     * @throws ServiceRegistrationException if the service record is not valid
     */
    private void ensureConnectable() throws IOException {
        if (isClosed) {
            throw new BluetoothConnectionException(
                    BluetoothConnectionException.FAILED_NOINFO,
                    "Notifier is closed.");
        }
        if (!BCC.getInstance().isConnectable()) {
            throw new BluetoothStateException("The device is not connectable.");
        }
        checkServiceRecord();
    }

    /**
     * Accepts client connection to the service this notifier is assigned to.
     * Adds corresponding service record to the SDDB, blocks until a successfull
     * connection to a client is established, retrieves the connection.
     *
     * @return bi-directional connection to a client just accepted.
     *
     * @throws IOException if notifier is closed or device is not
     * in connectable mode.
     */
    public StreamConnection acceptAndOpen()
            throws IOException, ServiceRegistrationException {
        ensureConnectable();

        // switch on listen mode if it has not been done yet
        doListen();

        // adds the record only if is not yet in SDDB
        SDDB.getInstance().updateServiceRecord(serviceRec);

        StreamConnection client;
        do {
            ensureConnectable();

            // accept incoming connections if any
            client = doAccept();
        } while (client == null);

        return client;
    }

    /**
     * Closes this notifier making corresponding service record inaccessible.
     * updates the information on the communication server.
     *
     * @throws IOException if an error occured lower in Bluetooth stack.
     */
    public void close() throws IOException {
        if (isClosed) {
            return;
        }
        isClosed = true;

        SDDB.getInstance().removeServiceRecord(serviceRec);

        doClose();
    }

    /**
     * Force listen mode by calling underlying stack methods.
     *
     * @throws IOException if an error occured
     */

    private void doListen() throws IOException {
        // force listening if it had not been done yet
        if (!isListenMode) {
            listen0();

            isListenMode = true;
        }
    }

    /**
     * Force Bluetooth stack to listen for incoming client connections.
     *
     * Note: the method gets native connection handle directly from
     * <code>handle<code> field of <code>BTSPPNotifierImpl</code> object.
     *
     * @throws IOException if an I/O error occurs
     */
    private native void listen0() throws IOException;

    /**
     * Advertises acception by calling underlying stack methods.
     *
     * @return BTSPPConnection instance to work with accepted client
     */
    private StreamConnection doAccept() throws IOException {
        if (!isListenMode) {
            throw new BluetoothStateException("Device is not in listen mode");
        }

        /*
         * Note: native handle is set to peerHandleID field directly
         * by accept0 method and retrieved by L2CAPConnectionImpl constructor.
         */
        accept0();

        return new BTSPPConnectionImpl(url, mode, this);
    }

    /**
     * Accepts incoming client connection request.
     *
     * Note: the method gets native connection handle directly from
     * <code>handle<code> field of <code>BTSPPNotifierImpl</code> object.
     *
     * Note: new native connection handle to work with accepted incoming
     * client connection is setted directly to <code>handle</code> field of
     * appropriate <code>BTSPPConnectionImpl</code> object.
     *
     * @return <code>0</code> if incoming client connection was successfully
     *         accepted;
     *         <code>-1</code> if there is no pending incoming connections
     * @throws IOException if an I/O error occurs
     */
    private native int accept0() throws IOException;


    /**
     * Closes this notifier at native layer.
     */
    private void doClose() throws IOException {
        close0();
    }

    /**
     * Closes this server connection.
     * Releases all native resources (such as sockets) owned by this notifier.
     *
     * Note: the method gets native connection handle directly from
     * <code>handle<code> field of <code>BTSPPNotifierImpl</code> object.
     *
     * @throws IOException IOException if an I/O error occurs
     */
    private native void close0() throws IOException;


    /**
     * Creates an instanse of server connection at native layer.
     *
     * @param url <code>BluetoothUrl</code> that represents server
     *        connection string to create notifier for
     * @return selected channel number to listen for incoming connections
     */
    private int doCreate(BluetoothUrl url) throws IOException {
        return create0(url.authenticate, url.authorize,
                url.encrypt, url.master);
    }

    /**
     * Creates a server connection.
     *
     * Note: the method sets native connection handle directly to
     * <code>handle<code> field of <code>BTSPPNotifierImpl</code> object.
     *
     * @param auth   <code>true</code> if authication is required
     * @param authz  <code>true</code> if authorization is required
     * @param enc    <code>true</code> indicates
     *                what connection must be encrypted
     * @param master <code>true</code> if client requires to be
     *               a connection's master
     * @return selected channel number to listen for incoming connections on
     * @throws IOException IOException if an I/O error occurs
     */
    private native int create0(boolean auth, boolean authz,
        boolean enc, boolean master) throws IOException;

}