FileDocCategorySizeDatePackage
Protocol.javaAPI DocphoneME MR2 API (J2ME)36615Wed May 02 18:00:44 BST 2007com.sun.midp.io.j2me.mms

Protocol.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.mms;

import com.sun.midp.io.j2me.ProtocolBase;

import com.sun.midp.io.j2me.sms.MessageObject;

import com.sun.midp.security.Permissions;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Vector;
import javax.microedition.io.Connector;
import javax.microedition.io.Connection;
import javax.wireless.messaging.Message;
import javax.wireless.messaging.MessageConnection;

// Exceptions
import java.io.IOException;
import java.io.InterruptedIOException;

/**
 * MMS message connection implementation.
 *
 * <code>Protocol</code> itself is not instantiated. Instead, the application
 * calls <code>Connector.open</code> with an MMS URL string and obtains a
 * {@link javax.wireless.messaging.MessageConnection MessageConnection}
 * object. It is an instance of <code>MessageConnection</code> that is
 * instantiated. The Generic Connection Framework mechanism in CLDC will return
 * a <code>Protocol</code> object, which is the implementation of
 * <code>MessageConnection</code>. The <code>Protocol</code> object represents a
 * connection to a low-level transport mechanism.
 * <p>
 * Optional packages, such as <code>Protocol</code>, cannot reside in small
 * devices. The Generic Connection Framework allows an application to reach the
 * optional packages and classes indirectly. For example, an application can be
 * written with a string that is used to open a connection. Inside the
 * implementation of <code>Connector</code>, the string is mapped to a
 *  particular implementation: <code>Protocol</code>, in this case. This allows
 * the implementation to be optional even though the interface,
 * <code>MessageConnection</code>, is required.
 * <p>
 * Closing the connection frees an instance of <code>MessageConnection</code>.
 * <p>
 * The <code>Protocol</code> class contains methods to open and close the
 * connection to the low-level transport mechanism. The messages passed on the
 * transport mechanism are defined by the {@link MessageObject MessageObject}
 * class. Connections can be made in either client mode or server mode.
 * <ul>
 * <li>Client mode connections are for sending messages only. They are created
 * by passing a string identifying a destination address to the
 * <code>Connector.open()</code> method.</li>
 * <li>Server mode connections are for receiving and sending messages. They are
 * created by passing a string that identifies a port, or equivalent, on the
 * local host to the <code>Connector.open()</code> method.</li>
 * </ul>
 * The class also contains methods to send, receive, and construct
 * <code>Message</code> objects.
 * <p>
 * <p>
 * This class declares that it implements <code>StreamConnection</code> so it
 * can intercept calls to <code>Connector.open*Stream()</code> to throw an
 * <code>IllegalArgumentException</code>.
 * </p>
 */
public class Protocol extends ProtocolBase {

    /** Creates a message connection protocol handler. */
    public Protocol() {
        super();
        ADDRESS_PREFIX = "mms://";
    }

    /**
     * Gets the connection parameter in string mode.
     * @return string that contains a parameter 
     */
    protected String getAppID() {
        return appID;
    }

    /**
     * Sets the connection parameter in string mode.
     * @param newValue new value of connection parameter 
     */
    protected void setAppID(String newValue) {
        appID = newValue;
    }

    /**
     * The internal representation of the MMS message.
     */
    private class MMSPacket {
        /** The sender's address. */
        public byte[] fromAddress;
        /** The application ID associated with the message. */
        public byte[] appID;
        /** The application ID to which replies will be sent. */
        public byte[] replyToAppID;
        /** Entire message contents. */
        public byte[] message;
    };


    /*
     * Native function prototypes
     */

    /**
     * Native function to open a MMS connection.
     *
     * @param host The name of the host for this connection. Can be
     *     <code>null</code>.
     * @param appID The application ID associated with this connection.
     * Can be <code>null</code> for unblock sending and receiving messages.
     * @param msid The MIDlet suite ID.
     *
     * @return  returns handle to the open MMS connection.
     */
    private native int open0(String host, String appID, int msid)
         throws IOException;

    /**
     * Unblock the receive thread.
     *
     * @param msid The MIDlet suite ID.
     *
     * @return  returns handle to the connection.
     */
    protected int unblock00(int msid)
        throws IOException {
        return open0(null, null, msid);
    }

    /**
     * Native function to close mms connection
     *
     * @param appID  The application ID associated with this connection.
     * @param handle  The MMS handle created when the connection was opened.
     * @param deRegister Deregistration appID when parameter is 1.
     *
     * @return  <code>0</code> if successful; <code>-1</code> for failure.
     */
    private native int close0(String appID, int handle, int deRegister);

    /**
     * Close connection.
     *
     * @param connHandle handle returned by open0
     * @param deRegister Deregistration appID when parameter is 1.
     * @return    0 on success, -1 on failure
     */
    protected int close00(int connHandle, int deRegister) {
        return close0(appID, connHandle, deRegister);
    }

    /**
     * Native function to get the device phone number
     *
     * @return  the phone number of device.
     */
    private native String getPhoneNumber0();

    /**
     * Sends an MMS message.
     *
     * @param handle The handle to the open MMS connection.
     * @param toAddress The recipient's MMS address.
     * @param fromAddress The sender's MMS address.
     * @param appID The application ID to be matched against incoming messages.
     * @param replyToAppID The ID of the application that processes replies.
     * @param msgHeader The message header context.
     * @param msgBody The message body context.
     *
     * @return the status of <code>0</code>, when bytes were sent;
     *	<code><0</code> when there is an error (This is accompanied by
     *	an exception.).
     */
    private native int send0(int handle,
                             String toAddress,
                             String fromAddress,
                             String appID,
                             String replyToAppID,
                             byte[] msgHeader,
                             byte[] msgBody) throws IOException;

    /**
     * Receives a MMS message.
     *
     * @param handle The handle to the the MMS connection.
     * @param appID The application ID to be matched against incoming messages.
     * @param msid The MIDlet suite ID.
     * @param packet The received message.
     *
     * @return    The number of bytes received.
     *
     * @exception IOException  if an I/O error occurs
     */
    private native int receive0(int handle, String appID, int msid,
                                MMSPacket packet) throws IOException;

    /**
     * Waits until message available
     *
     * @param appID  The application ID associated with this connection.
     * @param handle  The handle to the MMS connection.
     *
     * @return  <code>0</code> on success, <code>-1</code> on failure
     *
     * @exception IOException  if an I/O error occurs
     */
    private native int waitUntilMessageAvailable0(String appID, int handle)
                               throws IOException;

    /**
     * Waits until message available
     *
     * @param handle handle to connection
     * @return 0 on success, -1 on failure
     * @exception IOException  if an I/O error occurs
     */
    protected int waitUntilMessageAvailable00(int handle) 
        throws IOException {
        return waitUntilMessageAvailable0(appID, handle);
    }

    /**
     * Computes the number of transport-layer segments that would be required to
     * send the given message.
     *
     * @param msgBuffer The message to be sent.
     * @param msgLen The length of the message.
     * @param msgType The message type: binary or text.
     * @param hasPort Indicates if the message includes a source or destination
     *     port number.
     *
     * @return The number of transport-layer segments required to send the
     *     message.
     */
    private native int numberOfSegments0(byte msgBuffer[], int msgLen,
        int msgType, boolean hasPort);

    /*
     * Helper methods
     */

    /**
     * Checks the internal setting of the receive permission. Called from
     * the <code>receive</code> and <code>setMessageListener</code> methods.
     *
     * @exception InterruptedIOException if permission dialog was pre-empted.
     */
    protected void checkReceivePermission() throws InterruptedIOException {

        // Check for permission to receive.
        if (readPermission == false) {
            try {
                midletSuite.checkForPermission(Permissions.MMS_RECEIVE,
                                               "mms:receive");
                readPermission = true;
            } catch (InterruptedException ie) {
                throw new InterruptedIOException("Interrupted while trying " +
                                               "to ask the user permission.");
            }
        }
    }

    /*
     * MessageConnection Interface
     */

    /**
     * Construct a new message object of a text, binary or multipart message
     * type and specify a destination address. When a
     * <code>MULTIPART_MESSAGE</code> constant is passed in, the created object
     * implements the <code>MultipartMessage</code> interface.
     * <p>
     * If this method is called in a sending mode, a new <code>Message</code>
     * object is requested from the connection. For example:
     * <p>
     * <code>Message msg = conn.newMessage(MULTIPART_MESSAGE);</code>
     * <p>
     * The <code>Message</code> does not have the destination address set. It
     * must be set by the application before the message is sent.
     * <p>
     * If this method is called in receiving mode, the <code>Message</code>
     * object does have its address set. The application can act on the object
     * to extract the address and message data.
     * <p>
     * <!-- The <code>type</code> parameter indicates the number of bytes that
     * should be allocated for the message. No restrictions are placed on the
     * application for the value of <code>size</code>. A value of
     * <code>null</code> is permitted and creates a <code>Message</code> object
     * with a 0-length message. -->
     *
     * @param type <code>MULTIPART_MESSAGE</code> is the only type permitted.
     *
     * @return A new MMS <code>Message</code> object.
     */
    public Message newMessage(String type) {

        String address = null;

        // Provide the default address from the original open.
        if (host != null) {
            address = ADDRESS_PREFIX + host;
            if (appID != null) {
                address = address + ":" + appID;
            }
        }

        return newMessage(type, address);
    }

    /**
     * Construct a new <code>MULTIPART_MESSAGE</code> message object and specify
     * a destination address. The <code>MULTIPART_MESSAGE</code> constant must
     * be passed in. The created object implements the
     * <code>MultipartMessage</code> interface.
     * <p>
     * The destination address <code>addr</code> has the following format:
     * <p>
     * <code>mms://<em>phone_number</em>:<em>application_id</em></code>
     *
     * @param type <code>MULTIPART_MESSAGE</code> is the only type permitted.
     * @param addr  The destination address of the message.
     *
     * @return A new MMS <code>Message</code> object.
     */
    public Message newMessage(String type, String addr) {

        if (!type.equals(MessageConnection.MULTIPART_MESSAGE)) {
            throw new IllegalArgumentException("Message type not supported.");
        }

        return new MultipartObject(addr);
    }

    /**
     * Send an MMS message over the connection. The data are extracted from the
     * <code>Message</code> object payload for use by the underlying transport
     * layer.
     *
     * @param msg  A <code>Message</code> object.
     *
     * @exception java.io.IOException if the message could not be sent
     *            or because of network failure.
     * @exception java.lang.IllegalArgumentException if the message is
     *            incomplete or contains invalid information. This exception
     *            is also thrown if the payload of the message exceeds
     *            the maximum length for the given messaging protocol.
     * @exception java.io.InterruptedIOException if a timeout occurs while
     *            either trying to send the message or if this
     *            <code>Connection</code> object is closed during this
     *            <code>send</code> operation.
     * @exception java.lang.NullPointerException if the parameter is null.
     * @exception java.lang.SecurityException if the application does not
     *            have permission to send the message.
     */
    public void send(Message msg) throws IOException {

        if (msg == null) {
            throw new NullPointerException("Null message");
        }

        // If this isn't a multipart message, bail out.
        if (!(msg instanceof MultipartObject)) {
            throw new IllegalArgumentException("Unsupported message type");
        }

        // Make sure the connection is still open.
        ensureOpen();

        // Create the multi-part object that will be used below.
        MultipartObject mpo = (MultipartObject)msg;

        /*
         * Check for valid MMS URL connection format. Note that the addresses in
         * the lists are not used. This is simply a check to make sure that the
         * addresses can be placed into the multipart object's header when the
         * header and message are bundled within MultipartObject.
         *
         * Process each MMS address in the to:, cc: and bcc: address lists. An
         * MMS address assumes this form: address:appID
         *
         * Each MMS address is parsed to extract the address and application ID
         * data. Those parts are then checked for validity.
         *
         * The loop starts by processing all addresses in the to: field (if
         * any), followed by the addresses in the cc: list, then the bcc: list.
         *
         */
        Vector allAddresses = new Vector();
        String[] addresses = mpo.getAddresses("to");
        int currIndex = 0;
        boolean checkedTo = false;
        boolean checkedCC = false;

        // The application ID extracted from an address in the address list.
        String parsedAppID = null;
        while (true) {

            /*
             * If no addresses were in the to: field, or if all addresses have
             * been extracted and checked from the current address list
             * (Initially, the to: list), then continue to process the cc: list
             * (if any), next, followed by the bcc: list.
             */
            if (addresses == null || currIndex >= addresses.length) {

                if (!checkedTo) {

                    // The to: list has been processed. Process cc: list, next.
                    checkedTo = true;
                    addresses = mpo.getAddresses("cc");
                    currIndex = 0;
                    continue;

                } else if (!checkedCC) {

                    // The cc: list has been processed. Process bcc: list, next.
                    checkedCC = true;
                    addresses = mpo.getAddresses("bcc");
                    currIndex = 0;
                    continue;
                } else {

                    /*
                     * The to:, cc: and bcc: lists have now been checked, so
                     * bail out of the while() loop.
                     */
                    break;
                }
            }

            /*
             * Pick up the next address and add it to the list. Then, parse it
             * to extract the address and application ID parts.
             */
            String addr = addresses[currIndex++];
            allAddresses.addElement(addr);

            MMSAddress parsedAddress = MMSAddress.getParsedMMSAddress(addr);

            if (parsedAddress == null ||
                parsedAddress.type == MMSAddress.INVALID_ADDRESS ||
                parsedAddress.type == MMSAddress.APP_ID) {
                throw new IllegalArgumentException(
                    "Invalid MMS address: " + addr);
            }

            if (parsedAppID == null) {
                parsedAppID = parsedAddress.appId;
            } else if (parsedAddress.appId != null &&
                !parsedAppID.equals(parsedAddress.appId)) {
                throw new IllegalArgumentException("Only one Application-ID "
                    + "can be specified per message");
            }

        } // while

        if (allAddresses.size() == 0) {
            throw new IllegalArgumentException("No to, cc, or bcc addresses.");
        }

        /*
         * Since JTWI requires the destination phone number (not port)
         * and the number of messages to be displayed in the permission
         * dialog to the user, the permission check must happen after the
         * address check and message disassembly.
         */
        try {
            /*
             * IMPL_NOTE: (Original comment for MMS): Add the display of the addresses
             * in a nice way, and the size of all attachments and total size, as
             * per page 66 (Appendix E) of the specification.
             *
             * Nicer display can happen by creating an MMSAddressList class that
             * extends Vector and overrides toString().
             *
             */
            midletSuite.checkForPermission(Permissions.MMS_SEND,
					   allAddresses.toString(),
                              Integer.toString(numberOfSegments(msg)));
        } catch (InterruptedException ie) {
            throw new InterruptedIOException(
                 "Interrupted while trying to ask the user permission");
        }

        // Construct the target address protocol string.
        String messageAppID = mpo.getApplicationID();
        String toAddress = "mms://:";
        if (messageAppID != null) {
            toAddress = toAddress + messageAppID;
        }

        /*
         * If no application ID was supplied, use the ID that was used to open
         * the connection as the default ID.
         */
        String replyToAppID = null;
        if (messageAppID != null && host == null) {
            replyToAppID = appID;
            mpo.setReplyToApplicationID(replyToAppID);
        }

        // Retain the original "from:" address.
        String oldFromAddress = ((MessageObject)mpo).getAddress();

        // Create the "reply-to" information.
        String phoneNumber = getPhoneNumber0();
        String fromAddress = "mms://" + phoneNumber;
        if (replyToAppID != null) {
            fromAddress = fromAddress + ":" + replyToAppID;
        }
        mpo.setFromAddress(fromAddress);

        // Send the message and reply information.
        byte[] header = mpo.getHeaderAsByteArray();
        byte[] body = mpo.getBodyAsByteArray();
        try {
            send0(connHandle, toAddress, fromAddress, messageAppID, replyToAppID,
                  header, body);
        } catch (IOException ex) {
            io2InterruptedIOExc(ex, "sending");
        }

        // Reset the from: address to its original form.
        mpo.setFromAddress(oldFromAddress);
    }

    /**
     * Receives the bytes that have been sent over the connection, constructs a
     * <code>MultipartMessage</code> object, and returns the message.
     * <p>
     * If there are no <code>MultipartMessage</code>s waiting on the connection,
     * this method will block until a message is received, or the
     * <code>MessageConnection</code> is closed.
     *
     * @return  A <code>MultipartMessage</code> object.
     *
     * @exception java.io.IOException  if an error occurs while receiving a
     *            message.
     * @exception java.io.InterruptedIOException if this
     *            <code>MessageConnection</code> object is closed during this
     *            receive method call.
     * @exception java.lang.SecurityException if the application does not have
     *            permission to receive messages using the given application ID.
     */
    public synchronized Message receive() throws IOException {

        if (host != null) {
            throw new IOException(
                "Can not receive from client only connection: " + host);
        }

        // Check for permission to receive.
        checkReceivePermission();

        // Make sure the connection is still open.
        ensureOpen();

        // The connection must be read-only with no host address.
        if (((m_mode & Connector.READ) == 0) || (host != null)) {
            throw new IOException("Invalid connection mode.");
        }

        // No message received yet.
        Message msg = null;
        int length = 0;

        try {

            MMSPacket mmsPacket = new MMSPacket();

            /*
             * Packet has been received and deleted from inbox.
             * Time to wake up receive thread.
             */
            // Pick up the message from the message pool.
            length = receive0(connHandle, appID, midletSuite.getID(),
                              mmsPacket);

            if (length == 0) {
                throw new InterruptedIOException("No message received.");
            }

            if (length > 0) {

                /*
                 * Convert the message data into a multipart message. The
                 * message packet contains the appID that goes with the message
                 * as well as the entire message content in a big byte[] lump.
                 */
                msg = MultipartObject.createFromByteArray(mmsPacket.message);

                // IMPL_NOTE: KEEP ?
                // IMPL_NOTE: msg.setTimeStamp(tm.getTimeStamp());

                String fromAddress = null;
                if (mmsPacket.fromAddress != null) {
                    fromAddress = new String(mmsPacket.fromAddress);
                }

                String replyToAppID;
                if (mmsPacket.replyToAppID == null) {
                    replyToAppID = null;
                } else {
                    replyToAppID = new String(mmsPacket.replyToAppID);
                }

                ((MultipartObject)msg).setFromAddress(fromAddress);
                String phoneNumber = getPhoneNumber0();
                ((MultipartObject)msg).fixupReceivedMessageAddresses(
                    fromAddress, phoneNumber);
            }

        } catch (InterruptedIOException ex) {
            length = 0;  // Avoid the "finally" exception, below.
            throw new InterruptedIOException("MMS connection closed.");
        } catch (IOException ex) {
            io2InterruptedIOExc(ex, "receiving");
	} finally {
            if (length < 0) {
		throw new InterruptedIOException("Connection closed.");
            }
	}

        return msg;
    }

    /**
     * Returns the number of segments required to send the given
     * <code>Message</code>.
     * <p>
     * Note: The message is not actually sent. This method will compute the
     * number of segments needed when this message is split into the protocol
     * segments using the appropriate features of the underlying protocol. This
     * method does not take the possible limitations of the implementation into
     * account, which may limit the number of segments that can be sent, using
     * this feature. These limitations are protocol-specific and are documented
     * with the adapter definition for that protocol.
     * </p>
     * @param msg The <code>MultipartObject</code>message to be used for
     *     the computation.
     *
     * @return  The number of protocol segments needed for sending the message.
     *     Returns <code>0</code> if the <code>Message</code> object cannot be
     *     sent using the underlying protocol.
     */
    public int numberOfSegments(Message msg) {

        if (!(msg instanceof MultipartObject)) {
            return 0;
        }

        /** Number of segments that need to be sent. */
        int segments = 0;

        byte[] msgBuffer = null;
        try {
            msgBuffer = ((MultipartObject)msg).getAsByteArray();
        } catch (IOException ioe) {
            // ignore this.
        }

        // Pick up the message length.
        if (msgBuffer != null) {

            // There is always a "port" (Application ID).
            boolean hasPort = true;

            /* Compute the total number of transport-layer segments. */
            segments = numberOfSegments0(msgBuffer, msgBuffer.length,
                0, hasPort);
        }

        return  segments;
    }

    /**
     * Closes the connection. Resets the connection <code>open</code> flag to
     * <code>false</code>. Subsequent operations on a closed connection should
     * throw an appropriate exception.
     *
     * @exception IOException  if an I/O error occurs
     */
    public void close() throws IOException {

        /*
         * Set appID to null, in order to quit out of the while loop
         * in the receiver thread.
         */
        String save_appID = null;
        if (appID != null) {
            save_appID = new String(appID);
        }

        appID = null;

        synchronized (closeLock) {
            if (open) {
                /*
                 * Reset open flag early to prevent receive0 executed by 
                 * concurrent thread to operate on partially closed 
                 * connection 
                 */
                open = false;        /* Close the connection and unregister the application ID. */
                close0(save_appID, connHandle, 1);

                setMessageListener(null);

                /*
                 * Reset handle and other params to default
                 * values. Multiple calls to close() are allowed
                 * by the spec and the resetting would prevent any
                 * strange behaviour.
                 */
                connHandle = 0;
                host = null;
                m_mode = 0;
            }
        }
    }

    /*
     * ConnectionBaseInterface Interface
     */

    /**
     * Opens a connection. This method is called from the
     * <code>Connector.open()</code> method to obtain the destination address
     * given in the <code>name</code> parameter.
     * <p>
     * The format for the <code>name</code> string for this method is:
     * <p>
     * <code>mms://[<em>phone_number</em>]:[<em>application_id</em>]</code>
     * <p>
     * where the <em>phone_number</em> is optional. If the <em>phone_number</em>
     * parameter is present, the connection is being opened in client mode. This
     * means that messages can be sent. If the parameter is absent, the
     * connection is being opened in server mode. This means that messages can
     * be sent and received.
     * <p>
     * The connection that is opened is to a low-level transport mechanism which
     * can be any of the following:
     * <ul>
     *   <li>A datagram Short Message Peer-to-Peer (SMPP) to a service center.
     *   <li>A <code>comm</code> connection to a phone device with AT-commands.
     *   <li>A native MMS stack.
     * </ul>
     * Currently, the <code>mode</code> and <code>timeouts</code> parameters are
     * ignored.
     *
     * @param name  The target of the connection.
     * @param mode  Indicates whether the caller intends to write to the
     *              connection. Currently, this parameter is ignored.
     * @param timeouts  Indicates whether the caller wants timeout exceptions.
     *              Currently, this parameter is ignored.
     *
     * @return  This connection.
     *
     * @exception IOException  if the connection is closed or unavailable.
     */
    public Connection openPrim(String name, int mode, boolean timeouts)
        throws IOException {

        return openPrimInternal(name, mode, timeouts);
    }


    /*
     * StreamConnection Interface
     */

    /**
     * Open and return an input stream for a connection. This method always
     * throws <code>IllegalArgumentException</code>.
     *
     * @return                              An input stream.
     * @exception IOException               if an I/O error occurs.
     * @exception IllegalArgumentException  is thrown for all requests.
     */
    public InputStream openInputStream() throws IOException {
        throw new IllegalArgumentException("Not supported.");
    }

    /**
     * Open and return a data input stream for a connection. This method always
     * throws <code>IllegalArgumentException</code>.
     *
     * @return                              An input stream.
     * @exception IOException               if an I/O error occurs.
     * @exception IllegalArgumentException  is thrown for all requests.
     */
    public DataInputStream openDataInputStream() throws IOException {
        throw new IllegalArgumentException("Not supported.");
    }

    /**
     * Open and return an output stream for a connection. This method always
     * throws <code>IllegalArgumentException</code>.
     *
     * @return                              An output stream.
     * @exception IOException               if an I/O error occurs.
     * @exception IllegalArgumentException  is thrown for all requests.
     */
    public OutputStream openOutputStream() throws IOException {
        throw new IllegalArgumentException("Not supported.");
    }

    /**
     * Open and return a data output stream for a connection. This method always
     * throws <code>IllegalArgumentException</code>.
     *
     * @return                              An output stream.
     * @exception IOException               if an I/O error occurs.
     * @exception IllegalArgumentException  is thrown for all requests.
     */
    public DataOutputStream openDataOutputStream() throws IOException {
        throw new IllegalArgumentException("Not supported.");
    }


    /*
     * Protocol members
     */

    /**
     * Opens a connection. This method is called from the
     * <code>Connector.open()</code> method to obtain the destination address
     * given in the <code>name</code> parameter.
     * <p>
     * The format for the <code>name</code> string for this method is:
     * <p>
     * <code>mms://[<em>phone_number</em>]:[<em>application_id</em>]</code>
     * <p>
     * where the <em>phone_number</em> is optional. If the <em>phone_number</em>
     * parameter is present, the connection is being opened in client mode. This
     * means that messages can be sent. If the parameter is absent, the
     * connection is being opened in server mode. This means that messages can
     * be sent and received.
     * <p>
     * The connection that is opened is to a low-level transport mechanism which
     * can be any of the following:
     * <ul>
     *   <li>A datagram Short Message Peer-to-Peer (SMPP) to a service center.
     *   <li>A <code>comm</code> connection to a phone device with AT-commands.
     *   <li>A native MMS stack.
     * </ul>
     * Currently, the <code>mode</code> and <code>timeouts</code> parameters are
     * ignored.
     *
     * @param name  The target of the connection.
     * @param mode  Indicates whether the caller intends to write to the
     *              connection. Currently, this parameter is ignored.
     * @param timeouts  Indicates whether the caller wants timeout exceptions.
     *              Currently, this parameter is ignored.
     *
     * @return  This connection.
     *
     * @exception IOException  if the connection is closed or unavailable.
     * @exception IllegalArgumentException  if the parameters are invalid
     */
    public synchronized Connection openPrimInternal(String name,
                                                    int mode,
                                                    boolean timeouts)
                                       throws IOException {

        /*
         * The general form of a MMS address is <code>mms://host:port</code>.
         * The form at this point should now be <code>//host:port</code>
         */
        if ((name == null) || (name.length() <= 2) ||
            (name.charAt(0) != '/') || (name.charAt(1) != '/')) {

            throw new IllegalArgumentException("Missing protocol separator.");
        }

        String fullAddress = "mms:" + name;

        MMSAddress parsedAddress = MMSAddress.getParsedMMSAddress(fullAddress);
        if (parsedAddress == null) {
            throw new IllegalArgumentException("Invalid MMS connection URL");
        }

        host = null;
        if (parsedAddress.address != null) {
            host = new String(parsedAddress.address);
        }

        appID = null;
        if (parsedAddress.appId != null) {
            appID = new String(parsedAddress.appId);
        }

        // Make sure the I/O constraint is READ, WRITE or READ_WRITE, only.
        if ((mode != Connector.READ) && (mode != Connector.WRITE) &&
            (mode != Connector.READ_WRITE)) {
            throw new IllegalArgumentException("Invalid mode");
        }

        /*
         * If <code>host == null</code>, then this is a server endpoint at
         * the supplied <code>appId</code>.
         *
         * If <code>host != null</code>, then this is a client endpoint at an
         * application-id decided by the system, and the default address for
         * MMS messages to be sent is <code>mms://host:appId</code>.
         */
        if (mode == Connector.READ && host != null && host.length() > 0) {
            throw new IllegalArgumentException("Cannot read on " +
                                               "a client connection.");
        }

        /*
         * Perform a one-time check to see if the application has the permission
         * to use this connection type.
         */
        if (openPermission == false) {
            try {
                midletSuite.checkForPermission(Permissions.MMS_SERVER,
                                               "mms:open");
                openPermission = true;
            } catch (InterruptedException ie) {
                throw new InterruptedIOException("Interrupted while trying " +
                                                "to ask the user permission");
            }
        }

        try {
            connHandle = open0(host, appID, midletSuite.getID());
        } catch (IOException ioe) {
            m_mode = 0;
            throw new IOException("Unable to open MMS connection: " +
                                  ioe.getMessage());
        } catch (OutOfMemoryError oome) {
            m_mode = 0;
            throw new IOException("Unable to open MMS connection: " +
                                  oome.getMessage());
        }
        open = true;

        m_mode = mode;

        // Return this connection.
        return this;
    }

}