|
| By C. Enrique Ortiz, October 2005 |
|
| |
The Wireless Messaging API (WMA) provides a common interface you can use to enable an application based on the Mobile Information Device Profile (MIDP) to send and receive short text and binary messages, as well as multimedia messages. These messages typically are part of store-and-forward messaging systems such as the Short Messaging Service (SMS) and the Multimedia Messaging Service (MMS) that guarantee delivery of messages.
Originally introduced in the Java Community Process as JSR 120, WMA 1.1 has been enhanced and released as WMA 2.0 in JSR 205. You can find more information on the WMA, and a reference implementation, at the WMA technology page. This article covers the differences between versions 1.1 and 2.0, and shows you how to take full advantage of this messaging API.
| |
WMA targets cell phones and other devices that can send and receive wireless messages. It's a generic messaging API for sending not only individual text and binary messages but the multipart payloads typically used for transmitting multimedia messages.
How messages are delivered depends on the underlying transport, or bearer, such as GSM SMS, GSM CBS, CMDA SMS, or MMS. The message format and transport are defined by the respective standards, but for the most part WMA renders such details transparent to the application. It's important to note, though, that SMS and MMS transports are actually managed differently in the network, and that MMS is not just a means to transmit larger SMS packets. WMA doesn't place limits on message size and other restrictions but do be aware that the underlying transports do, as you'll see shortly.
WMA is based on the Generic Connection Framework (GCF). It's defined as a J2ME optional package; that is, it contains specialized APIs that can be added to a software stack based on a standard configuration. WMA's lowest common denominator is the Connected Limited Device Configuration (CLDC). Because the Connected Device Configuration (CDC) is a superset of CLDC, WMA can be included in both CLDC- and CDC-based stacks. Figure 1 summarizes the components of the WMA 2.0:
Figure 1: of the Wireless Messaging API 2.0 (
Click to enlarge.)
|
WMA, a required optional package
JTWI defines minimum and common characteristics that all compliant handsets must provide; it mandates support for WMA 1.1, but not WMA 2.0. MSA continues the evolution of mobile architectures and specifies WMA 2.0 to be a mandatory package. |
Even though WMA is an optional package with regard to the Java Platform, Mobile Edition (Java ME) as a whole, its presence is mandatory on handsets that claim to comply with JSR 185, the Java Technology for the Wireless Industry (JTWI) specification, and with JSR 248, the new Mobile Service Architecture (MSA) for CLDC.
All WMA-specific interfaces and classes are contained in a single package,
javax.wireless.messaging, which defines all the APIs required for sending and receiving wireless text, binary, and multi-part messages. Table 1 summarizes this package:
|
Interface
|
Description
|
Methods
|
1.1
|
2.0
|
|---|---|---|---|---|
| |
||||
Message
|
Base message interface, from which subinterfaces such as
BinaryMessage,
TextMessage, and
MultipartMessage are derived
|
getAddress()
|
X
|
X
|
BinaryMessage
|
Subinterface of
Message that represents a binary message and provides methods to set and get the binary payload
|
getPayloadData()
|
X
|
X
|
TextMessage
|
Subinterface of
Message that represents a text message and provides methods to set and get the text payload
|
getPayloadText()
|
X
|
X
|
MessageConnection
|
Subinterface of the GCF Connection, which provides a factory of
Messages, and methods to send and receive
Messages
|
newMessage()
|
X
|
X
|
MessageListener
|
Defines the listener interface to implement asynchronous notification of
Message objects
|
notifyIncomingMessage()
|
X
|
X
|
MessagePart
|
Defines a message part that can be added to a
MultipartMessage
|
getContent()
|
X
|
|
MultipartMessage
|
Subinterface of
Message that represents a multi-part message and provides methods to set and get multiple payloads
|
addAddress()
|
X
|
|
SizeExceededException
|
Exception thrown if the multipart message content is larger than the available memory or supported size for the message part
|
--
|
X
|
|
| |
||||
The differences between WMA versions 1.1 and 2.0 are related primarily to the support of multi-part messages used for MMS messaging. The next several sections provide some of the particulars of the different components of WMA. For the low-level details of each of the WMA methods, consult the WMA specification.
| |
WMA 2.0 defines four message representations, or types. In addition, it defines a message part in support of multipart messages, used for carrying multimedia messages. The next five sections describe the interfaces that represent messages, and the class that represents a message part.
Message Interface
The interface
javax.wireless.messaging.Message is the base type for all messages communicated using WMA - a
Message is what is sent and received, produced and consumed. In some respects, a
Message looks similar to a datagram: it has source and destination addresses, and a payload.
There are significant differences too. A WMA
Message includes a timestamp, and supports multiple payload types, as defined by its subinterfaces. Moreover, because wireless messaging is typically implemented on top of store-and-forward systems such as SMS and MMS, message delivery is reliable, guaranteed, and even traceable.
The interface specifies methods to get and set the message's source and destination addresses, and to get its timestamp:
String getAddress();
void setAddress(String address);
Date getTimestamp();
WMA 2.0 defines three subinterfaces of
Message, seen in Figure 2:
Figure 2: The Message Interface and its Subinterfaces (
Click to enlarge.)
|
Where:
BinaryMessage is for short binary messages typically over SMS.
TextMessage is for short text messages typically over SMS.
MultipartMessage is for multi-media messages typically over MMS.
BinaryMessage Interface
The
BinaryMessage subinterface represents a message with a binary payload, typically an SMS-based short binary message. A binary payload is encoded using 8-bit data, allowing 140 bytes per segment 133 if a port number is used. For more information about message segmentation refer to the section "
About Segmentation and Reassembly" later in this article. This interface declares methods to set and get the binary payload as an array of bytes:
byte[] getPayloadData();
void setPayloadData(byte[] bytes);
Methods to set and get the address of the message, and to get its timestamp, are all inherited from
Message.
TextMessage Interface
The
TextMessage subinterface represents a message with a text payload, typically an SMS-based short text message. This interface provides methods to set and get the text as an instance of
String:
String getPayloadText();
void setPayloadText(String data);
The underlying implementation is responsible for properly encoding or decoding the
String to or from the appropriate format before the text message is sent or received. The character encoding that's used affects the size of the message. For example, GSM 7-bit allows 160 characters per segment, or 152 if a port number is used, while UCS-2 allows for 70 double-byte characters per segment, or 66 if a port number is used. Again, refer to the section "
About Segmentation and Reassembly" for more information. As with
BinaryMessage, the methods to set and get the address of the message and get its timestamp are inherited from
Message.
MultipartMessage Interface
The
MultipartMessage subinterface represents a message that consists of multiple parts, typically an MMS-based multimedia message. This interface defines a container of one or more
MessageParts, and provides methods to manage the sender and recipient addresses, the message's headers, the "start message" content ID, and the message's parts:
boolean addAddress(String type, String address);
void addMessagePart(MessagePart messagePart) throws SizeExceededException;
String getAddress();
String[] getAddresses(String type);
String getHeader(String headerField);
MessagePart getMessagePart(String contentID);
MessagePart[] getMessageParts();
String getStartContentId();
String getSubject();
boolean removeAddress(String type, String address);
void removeAddresses();
void removeAddresses(String type);
boolean removeMessagePart(MessagePart messagePart);
boolean removeMessagePartId(String contentID);
boolean removeMessagePartLocation(String contentLocation);
void setAddress(String address);
void setHeader(String headerField, String headerValue);
void setStartContentId(String contentID);
void setSubject(String subject);
MultipartMessage overrides the methods to set and get the message's address it inherits from
Message.
Multipart messages follow the format of standard emails, which consist of RFC822-based headers and multiple parts based on the Multipurpose Internet Mail Extensions (MIME) standard defined by the World Wide Web Consortium (W3C) in RFC2045 and RFC2046, as Figure 3 illustrates:
Figure 3: Structure of an MMS Multipart Message (
Click to enlarge.)
|
The
MultipartMessage interface represents the multimedia message and its headers, while a
MessagePart class instance represents each individual MIME part.
MessagePart Class
As its name suggests,
MessagePart represents one part of a message. In addition to various constructors, this class provides methods to retrieve the content, and information about the content:
MessagePart(byte[] contents, int offset, int length, String mimeType, String contentId, String contentLocation, String encoding) throws SizeExceededException;
MessagePart(byte[] contents, String mimeType, String contentId, String contentLocation, String encoding) throws SizeExceededException;
MessagePart(java.io.InputStream is, String mimeType, String contentId, String contentLocation, String encoding) throws IOException, SizeExceededException;
public byte[] getContent();
public InputStream getContentAsStream();
public String getContentID();
public String getContentLocation();
public String getEncoding();
public int getLength();
public String getMIMEType();
A message part consists of a MIME type as defined in RFC2046, a content ID, a content location, and the content itself. You can construct a
MessagePart from a byte array or a
java.io.InputStream.
| |
In the Wireless Messaging API, input, output, and network connectivity are based on the
Generic Connection Framework (GCF). WMA connections are based on the
MessageConnection interface, which is a subinterface of GCF's
javax.microedition.io.Connection, as illustrated in the next figure:
Figure 4: The
MessageConnection and Its Relationship to the GCF
|
The
MessageConnection interface defines factory methods for creating
TextMessages,
BinaryMessages, and
MultipartMessages, a method to calculate the number of protocol segments needed for sending the message, methods to receive and send messages, and a method to set the message listener for this connection:
Message newMessage(String type);
Message newMessage(String type, String address);
int numberOfSegments(Message message);
Message receive() throws IOException, InterruptedIOException;
void send(Message message) throws IOException, InterruptedIOException;
void setMessageListener(MessageListener messageListener) throws IOException;
In addition, the interface defines
String constants, one of which you pass to the
newMessage() factory method to identify the type of
Message to create:
String
TEXT_MESSAGE = "text";
String
BINARY_MESSAGE = "binary";
String
MULTIPART_MESSAGE = "multipart";
When creating a message connection, always use one of these constants. Note that the WMA implementation uses
String.equals() to compare the string values, making these message types case-sensitive.
You open and close a
MessageConnection just as you do any other GCF Connection. To create the connection, call the connection factory's method
javax.microedition.io.Connector.open(). To close it call the appropriate method of the base connection interface,
javax.microedition.io.Connection.close(). You can have multiple
MessageConnections open simultaneously.
You can create a
MessageConnection in either of two modes: as a
client connection or as a
server connection. A client connection can only send messages, while a server connection can both send and receive. As with other GCF connections, you specify a connection as either client or server by way of the URL, as here:
scheme://address-part [params]
Where:
scheme
is the protocol to use
address-part
is the protocol-specific address for client or server connections
params
are scheme-dependent optional parameters in the form
;x=y
WMA 2.0 Supported Protocol Adapters and Standards
Support for WMA protocol adapters is based on the following standards:
|
The URL
scheme
for creating a
MessageConnection is not specific to a single protocol. Instead it is intended to support many wireless messaging protocols. The WMA specification defines the following messaging protocol adapters:
sms for Short Messaging System. SMS is bi-directional: you can create, send, and receive messages and thus support both client and server connections.
mms for Multimedia Messaging System. Like SMS, MMS is bi-directional, and thus supports both client and server connections.
cbs for Cell Broadcast Short
Message. CBS messages are broadcast by a base station, and can only be received; attempting to send over a
cbs connection results in an
IOException. CBS messages don't have a timestamp; trying to get a message's timestamp returns a
null.
The
address-part
of the URL specifies the handset and application. It also specifies whether the connection is a client or a server. A URL for a client connection includes a full
destination address, while the URL for a server connection specifies a
local address no host, just a protocol-specific local address, typically a port number or an application ID. For
sms the
address-part
comprises an MSISDN that identifies the handset, a port number that identifies the application, or both. For
mms, the
address-part
could consist of an email address, phone number, IPv4, IPv6, or shortcode-address, or an application ID, or both. For
cbs, which supports only receiving messages (server mode), the
address-part
is only a port number that identifies the application.
Examples of client connection URLs:
(MessageConnection)Connector.open ("sms://+15121234567:5000");
(MessageConnection)Connector.open ("mms://+15121234567: com.j2medeveloper.MyMmsApp");
No example for
cbs is included, as
cbs supports only server connections.
Examples of server connection URLs:
(MessageConnection)Connector.open("sms://:5000");
(MessageConnection)Connector.open("mms://:com.j2medeveloper.MyMmsApp");
(MessageConnection)Connector.open("cbs://:6000");
About Application IDs and Port Numbers
Application IDs identify the receiving application on a handset. For example, in the two URLs
sms://+5121234567:5000
and
mms://+5121234567:com.j2medeveloper.MyMmsApp
, the number
5000
and the string
com.j2medeveloper.MyMmsApp
are both application identifiers. In
sms
and
cbs
, applications are identified by port numbers, similarly to how port numbers are used for TCP and UDP connections. In
mms
, applications are identified by a string that typically is a fully qualified name such as
com.j2medeveloper.MyMmsApp
rather than a port number. Note that a port number takes space, typically 16 bits, away from the actual content of a short message. MMS application IDs, which can't exceed 32 characters in length, are optionally included as part of the multipart
Content-Type
message header. Creating an MMS server connection that specifies the application ID
com.j2medeveloper.MyMmsApp
will cause two parameters to be added to the value of the
Content-Type
message header:
Application-ID=com.j2medeveloper.MyMmsApp;
When a server connection is opened, a port number or application ID is specified (depending on the protocol used). A client wanting to send a message to that server application must indicate that same port number or application ID. The first application to bind to a given port number or application ID gets it, and attempting to bind to an already reserved local port number or application ID throws an
IOException
that your code must handle appropriately.
You can pick a random port number to use from the private/dynamic port range 49152-65635; the range 0-49151 is used by privileged applications or user applications such as WAP; refer to the WMA 2.0 specification for more information on which ones you should avoid. You can reserve your own port number for your application by contacting the Internet Assigned Numbers Authority (IANA). Note that sending a message to a handset without specifying a port number or application ID to identify an application typically causes that message to be delivered to the handset's default viewer application. |
For more information about URLs see the articles " The Generic Connection Framework," and " A Generic Connection Framework cheat sheet."
As Table 1 shows,
MessageConnection provides the methods
newMessage(),
send(), and
receive(), to create, send, and receive
Message objects respectively. The
numberOfSegments() method is of particular interest; it's used to determine segment information for a given
Message before it is sent see "
About Segmentation and Reassembly" for more information. You use
setMessageListener() to set the message listener that is called by the WMA implementation when new messages arrive.
WMA is a very intuitive, very simple-to-use API. Let's go over some code that shows how to take advantage of it.
| |
To create a client
MessageConnection you call the connection factory method
Connector.open(), passing a URL that specifies a valid WMA messaging protocol, and that includes either the local address for a server connection, or a full destination address for a client connection:
...
MessageConnection mc = null;
String connUrl = "mms://:com.j2medeveloper.MyMmsApp";
// My MMS server app
...
try {
mc = (MessageConnection)Connector.open(connUrl);
} catch (IOException ioe) {
// Port number or application identifier is already reserved
} catch (SecurityException se) {
// Permission to open the MessageConnection was not granted
}
...
|
It is good practice to define your application's port numbers and application identifiers in the Java Application Descriptor (JAD) file, and retrieve these at runtime, typically during application startup.
For instance, we can place the following attributes in the JAD file:
... SmsApplicationPort: 50000 CbsMessageID: 50001 MmsApplicationID: com.j2medeveloper.MyMmsApp ... |
To retrieve each of these attributes, we call
MIDlet.getAppProperty(String attributeName), passing it the name of the attribute to get. For illustration, let's define the helper method
loadWmaAttributes() to retrieve the WMA port and application ID from the JAD:
...
// Define the SMS and MMS Port and Application Identifiers
String smsPort;
String cbsMsgId;
String mmsAppId;
...
/**
* Loads the application WMA attributes
*/
private void loadWmaAttributes() {
mmsAppId = getAppProperty("MmsApplicationID");
smsAppId = getAppProperty("SmsApplicationPort");
cbsAppId = getAppProperty("CbsMessageID");
}
...
|
To illustrate how to create a connection, let's define the helper method
newMessageConnection(), which creates and returns a
MessageConnection:
/**
* Create a new MessageConnection
*
* @param connUrl is the server or client URL for the connection
* @param messageListener is the message listener for this
* connection
* @return a MessageConnection object
* @throws a ConnectionNotFoundException if
* the Connection target is not found,
* or the protocol not supported
* @throws an IOException if
* the Port number or application identifier is already reserved,
* or the connection has been closed,
* or if an attempt is made to register a listener on a client
* connection
* @throws an IllegalArgumentException if
* a Message type parameter is invalid
* or attempting to create a *Stream
* @throws SecurityException if
* permission to open the MessageConnection was not granted,
* or, when setting the message listener, it was determined
* that the application does not have permission to receive
* on the given port number
*/
final public MessageConnection newMessageConnection(
String connUrl,
MessageListener messageListener)
throws ConnectionNotFoundException, IOException,
IllegalArgumentException, SecurityException {
MessageConnection mc = null;
mc = (MessageConnection)Connector.open(connUrl);
mc.setMessageListener(messageListener);
return(mc);
}
|
The connection factory returns a
Connection subtype that must be properly cast; in our example, we cast the value returned by
Connector.open() to a
MessageConnection, which we then return to the caller. The helper method also sets a message listener for the connection, if one is specified. Note that setting the connection's message listener to
null deregisters any current message listener, which will then not receive any notifications. I'll cover message listeners later in the article.
Although GCF provides convenience methods to create
InputStream and
OutputStream for
StreamConnections, be aware that
MessageConnection doesn't support stream-based operations. Attempting to create a
*Stream will throw an
IllegalArgumentException.
| |
To close a connection, call the connection's
close() method, as shown in the next code snippet:
/**
* Closes the specified message connection
*
* @param connection is the connection to close
*/
static final public void closeConnection(MessageConnection connection) {
try {
if (connection != null) {
connection.setMessageListener(null);
// deregister the msg listener
connection.close();
}
} catch (IOException ioe) {
// Handle or ignore the exception...
}
}
|
This method first deregisters any message listener and then closes the connection. Don't forget to call the above method to close connections and unset your message listeners during cleanup, in
destroyApp().
| |
Recall that
MessageConnection provides methods to create and send
Messages. To create a message call the message factory method
newMessage(), and to send a message use the method
send(). Note that
newMessage() will return a message even if the connection has been closed.
As you learned earlier, you can create either a client connection or a server connection, specifying your choice in the connection URL. A message created from a client connection has its destination address already set, taken from the connection URL passed when the client connection was created. A server connection's destination address, on the other hand, is not already set; you must set it explicitly before sending the message.
Let's define the helper method
sendMessage() to send
Messages. The method takes for parameters the
MessageConnection to use, the actual
Message to send
BinaryMessage,
MultipartMessage or
TextMessage - and the optional destination URL.
A non-
null URL indicates that the caller wants to set the destination address before sending the message, as in the case of server connections. The method also checks whether the message can be sent, by calling the method
numberOfSegments().
Our example follows good practice by sending the message on its own thread of execution, to avoid contention with system threads such as the main display thread. The helper method
alertUser() is not shown; it's called here to warn the user if the message can't be sent. Note the different exceptions that could arise while sending the message:
/**
* Sends a Message on the specified connection
*
* @param mc the MessageConnection
* @param msg the message to send
* @param url the destination address, typically used in server mode
*/
final public void sendMessage(
final MessageConnection mc,
final Message msg,
final String url) {
Thread th = new Thread() {
public void run() {
try {
if (url!= null) msg.setAddress(url);
int segcount = mc.numberOfSegments(msg);
if (segcount == 0) {
alertUser(UiConstants.TXT_SEGCOUNT);
} else {
mc.send(msg);
}
} catch(Exception e) {
// Handle the Exception:
// IOException if the message could not be sent
// because there was a network failure or
// if the connection was closed
// IllegalArgumentException if the message was
// incomplete or contained invalid information
// This exception is also thrown if the payload
// of the message exceeds the maximum length
// for the given messaging protocol
// InterruptedIOException if a timeout occurs
// while trying to send the message or this
// Connection object was closed during this
// send operation
// NullPointerException if the parameter is null
// SecurityException if the application does not
// have permission to send the message
}
}
};
th.start();
}
|
| |
Now that we have covered the generic
sendMessage() helper method, let's define a helper method to send a
TextMessage.
/**
* Sends a TextMessage on the specified connection
*
* @param mc the MessageConnection
* @param msg the TextMessage to send
* @param url the destination address, typically used in server mode
*/
final public void sendTextMessage(
MessageConnection mc,
TextMessage msg,
String url) {
sendMessage(mc, msg, url);
}
|
The method assumes the
TextMessage has already been created. Let's now define a more useful version of
sendTextMessage(), one that takes an ordinary
String for input, and creates and sends the
TextMessage for us. Before sending, the method populates the outgoing
TextMessage by calling the method
setPayloadText().
/**
* Sends a TextMessage on the specified connection
*
* @param mc the MessageConnection
* @param msg the message to send
* @param url the destination address, typically used in server mode
*/
final public void sendTextMessage(
MessageConnection mc,
String msg,
String url) {
TextMessage tmsg = null;
tmsg = (TextMessage)
mc.newMessage(MessageConnection.TEXT_MESSAGE);
tmsg.setPayloadText(msg);
sendTextMessage(mc, tmsg, url);
}
|
Sending binary and multipart messages follows the same pattern.
| |
Now let's define another helper method, one to send a
BinaryMessage:
/**
* Sends a BinaryMessage on the specified connection
*
* @param mc the MessageConnection
* @param msg the Binary Message to send
* @param url the destination address, typically used in server mode
*/
final public void sendBinaryMessage(
MessageConnection mc,
BinaryMessage msg,
String url) {
sendMessage(mc, msg, url);
}
|
The method assumes the
BinaryMessage has already been created. Let's now define a more useful version of
sendBinaryMessage(), one that takes a byte array for input, and creates and sends the
BinaryMessage for us. Before sending, the method populates the outgoing
BinaryMessage by calling the method
setPayloadData().
/**
* Sends a BinaryMessage on the specified connection
*
* @param mc the MessageConnection
* @param msg the byte[] message to send
* @param url the destination address, typically used in server mode
*/
final public void sendBinaryMessage(
MessageConnection mc,
byte[] msg,
String url) {
BinaryMessage bmsg;
bmsg = (BinaryMessage)
mc.newMessage(MessageConnection.BINARY_MESSAGE);
bmsg.setPayloadData(msg);
sendMessage(mc, bmsg, url);
}
|
| |
Let's define one more helper method, to send a
MultipartMessage.
/**
* Sends a multi-part message on the specified connection
*
* @param mc the MessageConnection
* @param msg is the multiplart message to send
* @param url the destination address, typically used in server mode
*/
final public void sendMultipartMessage(
MessageConnection mc,
MultipartMessage msg,
String subject,
String url) {
sendMessage(mc, msg, url);
}
|
The method assumes the
MultipartMessage has already been created. As for text and binary messages, we'll define a more useful version of
sendMultipartMessage(), one that takes an array of
MessageParts for input, and creates and sends the
MultipartMessage for us. Before sending, the method populates the outgoing
MultipartMessage by adding to it each individual
MessagePart. The method takes a lot of arguments: the
MessageConnection; an array of
MessageParts; the start-content ID; lists of TO, CC, and BCC identifiers; a message subject; the message's priority; and the destination URL.
/**
* Sends a multi-part message on the specified connection
*
* @param mc the MessageConnection
* @param msgParts the array of message parts to send
* @param startContentID is the ID of the start multimedia
* content part (SMIL)
* @param to is the message's TO list
* @param cc is the message's CC list
* @param bcc is the message's BCC list
* @param subject is the message's subject
* @param priority is the message's priority
* @param url is the destination URL, typically used in server mode
*/
final public void sendMultipartMessage(
MessageConnection mc,
MessagePart[] msgParts,
String startContentID,
String[] to,
String[] cc,
String[] bcc,
String subject,
String priority,
String url) {
try {
int i=0;
MultipartMessage multipartMessage;
multipartMessage = (MultipartMessage)
mc.newMessage(MessageConnection.MULTIPART_MESSAGE);
if (to != null) {
for (i=0; i<to.length; i++) {
multipartMessage.addAddress("to", to[i]);
}
}
if (cc != null) {
for (i=0; i<cc.length; i++) {
multipartMessage.addAddress("cc", cc[i]);
}
}
if (bcc != null) {
for (i=0; i<bcc.length; i++) {
multipartMessage.addAddress("bcc", bcc[i]);
}
}
multipartMessage.setSubject(subject);
if ((priority.equals("high")) ||
(priority.equals("normal")) ||
(priority.equals("low"))) {
multipartMessage.setHeader("X-Mms-Priority", priority);
}
for (i=0; i<msgParts.length; i++) {
multipartMessage.addMessagePart(msgParts[i]);
}
multipartMessage.setStartContentId(startContentID);
sendMessage(mc, multipartMessage, url);
} catch(Exception e) {
// Handle the exception...
}
}
|
For multimedia messages with presentation information, such as those in the
Synchronized Media Integration Language (SMIL) defined by the World Wide Web Consortium (W3C), the multipart message's start-content ID should always be set to point to the message part that contains the presentation information. You do so by calling the method
setStartContentId(). You may set the start-content ID to
null; if it's not
null the reference must be to a valid message part, or an
IllegalArgumentException will be thrown. The result of this call is that the MMS content type becomes
application/vnd.wap.multipart.related, followed by a start parameter as shown next. Recipients of this type of message will then know how to retrieve the presentation information and present the message to the user appropriately.
[THE FOLLOWING ARE THE MMS HEADERS]
X-Mms-Message-Type: m-send-req
X-Mms-Transaction-ID: 123456
X-Mms-Version 1.0
X-Mms-Message-Class: Personal
X-Mms-Expiry: 36000
X-Mms-Priority: Normal
From: +15121234567
To: +15121234544
Date: Fri, 17 May 2005 21:30:30 -0600
Subject: A multimedia message sample
Content-Type:
application/vnd.wap.multipart.related;start=<start>;
type=application/smil;Application-ID=com.j2medeveloper.MyMmsApp;
Reply-To-Application-ID = com.j2medeveloper.MyMmsApp
nEntries: 2
[THE FOLLOWING IS THE MMS BODY THAT CONSIST OF MESSAGE PARTS]
[EACH MESSAGE PART CONSISTS OF TYPE, CONTENT ID, AND CONTENT]
Content-Type: image/png; name="image1.png"
content-id: <001>
[...IMAGE DATA...]
Content-Type: application/smil; name="first.sml"
content-id: <start>
<smil>
<body>
<seq repeatCount="indefinite">
<img src="image1.png" dur="3s" />
</seq>
</body>
</smil>
|
Next let's look at
MessageParts.
| |
As you've seen,
MessageParts are at the center of
MultipartMessages; each comprises a MIME type, a unique content ID, and the content itself.
You can construct a
MessagePart from a byte array or a
java.io.InputStream. Three constructors are available:
MessagePart(byte[] contents, int offset, int length, String mimeType, String contentId, String contentLocation, String encoding) throws SizeExceededException;
MessagePart(byte[] contents, String mimeType, String contentId, String contentLocation, String encoding) throws SizeExceededException;
MessagePart(java.io.InputStream contents, String mimeType, String contentId, String contentLocation, String encoding) throws IOException, SizeExceededException;
Where:
contents refers to the actual message content.
mimeType is the MIME type, as defined in RFC2046.
contentId is a required ID that uniquely identifies a message part, as defined in RFC2045.
contentLocation specifies the filename for the attached message represented by the content. A
null value means that no content location value is set for this message part.
encoding identifies the content's encoding scheme. A
null value means that no encoding is specified for this message part.
The first two constructors enable you to create the message part from a byte array, while the last constructor enables you to create the message part from an
InputStream. A rule of thumb says that you should construct the message part from an
InputStream instead of a
byte[] if the content size is close to 10KB.
The following code snippet illustrates now to create two message parts, one
text/plain and the other
image/png.
public final static String MIMETYPE_TEXT_PLAIN = "text/plain";
public final static String MIMETYPE_IMAGE_PNG = "image/png";
...
MessagePart textMessagePart, imageMessagePart;
try {
// Create a text/plain part
String textContent = "Hello world";
String textContentId = "text_hello";
String textContentLocation = "/helloworld.txt";
textMessagePart = new MessagePart(
textContent.getBytes(), 0,
textContent.length(),
MIMETYPE_TEXT_PLAIN,
textContentId,
textContentLocation,
null);
// Create an image/png part
InputStream imageContent;
String imageContentId = "img_hello";
String imageContentLocation = "/helloworld.png";
imageContent = getClass().getResourceAsStream
(imageContentLocation);
imageMessagePart = new MessagePart(
imageContent,
MIMETYPE_IMAGE_PNG,
imageContentId,
imageContentLocation,
null);
} catch (SizeExceededException see) {
// Handle exception
} catch (IOException ioe) {
// Handle exception
}
|
Your code must handle the following exceptions:
IOException if an exception other than
EOFException occurs while reading the
InputStream.
IllegalArgumentException if the specified MIME type is
null.
SizeExceededException if the content size is larger than the available memory or the size is not supported for the message part.
Once the individual message parts have been created, we are ready to send them using the
sendMultipartMessage() helper method you saw earlier, that automatically creates and sends the
MultipartMessage for us.
... String connUrl = ...; MessageConnection mc; MessagePart textMessagePart, imageMessagePart; MessagePart[] msgParts; ... mc = newMessageConnection(connUrl, null); ... // Create a message parts array textMessagePart = new MessagePart(...); imageMessagePart = new MessagePart(...); msgParts = new MessagePart[2]; msgParts[0] = textMessagePart; msgParts[1] = imageMessagePart; // Send the multiple parts sendMultipartMessage(mc, msgParts, connUrl); ... |
| |
Recall that only a server-mode
MessageConnection can receive messages. There are two approaches to receiving messages:
The approach to choose is really a matter of personal preference. I personally prefer the asynchronous approach, to avoid having a thread running even when there are no messages to process. This approach may be a bit more expensive on resources, as a new thread needs to be created and dispatched for each incoming message; but as the typical WMA use case is for messages that are not that frequent, the cost of dispatching one thread per message is acceptable.
Note that before a message is delivered to the application, the WMA implementation may request user confirmation.
For synchronous messaging you normally dispatch a thread during MIDlet initialization. This thread loops for messages, invoking
Message.receive() to block while it waits for incoming messages, then consuming them as they become available. As an example let's define the class
MySynchronousMsgReader, a Runnable that implements a Thread of execution to wait for and process incoming messages.
public class MySynchronousMsgReader implements Runnable {
MessageConnection messageConnection;
boolean done;
...
/**
* Constructor
*
* @param messageConnection is the MessageConnection to use
*/
public MySynchronousMsgReader(
MessageConnection messageConnection) {
this.messageConnection = messageConnection;
Thread th = new Thread(this);
th.start();
}
...
/**
* Sets the done flag to true
*/
public void setDone() {
done = true;
}
/**
* Message receive thread of execution
*/
public void run() {
while (!done) {
try {
Message message;
message = messageConnection.receive();
if (message != null) {
processMessage(message);
}
} catch (IOException ioe) {
// Handle or ignore the exception
}
}
}
/**
* Process the message
*
* @param message is the Message to process
*/
public void processMessage(message) {
// Here process the message
}
...
}
|
I'll discuss message processing shortly.
For asynchronous messaging WMA implements the conventional Observer or Listener design pattern for receiving messages asynchronously; that is, without blocking while waiting for messages. WMA defines the
MessageListener interface, with a single method,
notifyIncomingMessage(), which the platform invokes each time it receives a
Message.
To use a message listener the application must implement the interface
MessageListener and its
notifyIncomingMessage() method, thus:
public class MyClass implements MessageListener {
...
/**
* Asynchronous callback for inbound message
* Called by the WMA implementation when a new message is ready.
*
* @param connection is the message connection with incoming
* messages.
*/
public void notifyIncomingMessage(MessageConnection connection) {
readMessageThreaded(connection);
}
...
}
|
The message listener's
notifyIncomingMessage() method receives the
MessageConnection with inbound messages for input. Once this method has been invoked, the application must retrieve the new message by calling
messageConnection.receive(). The following methods implement some helper methods to receive messages.
/////////////////////////////////////////////////////////////////////
// Receive Helper Methods
/////////////////////////////////////////////////////////////////////
/**
* Thread of execution to read messages from MessageConnection
*
* @param messageConnection is the messageConnection with inbound
* messages
*/
public void readMessageThreaded(final MessageConnection messageConnection) {
Thread th = new Thread() {
public void run() {
readMessage(messageConnection);
}
};
th.start();
}
/**
* Reads a message from MessageConnection, processes the message
* @param messageConnection is the messageConnection with inbound
* messages
*/
public void readMessage(final MessageConnection messageConnection) {
try {
Message message = null;
message = messageConnection.receive();
if (message != null) {
processMessage(message);
}
} catch (IOException ioe) {
System.out.println("readMessage exception: " + ioe);
}
}
/**
* Process the message
* @param message is the Message to process
*/
public void processMessage(message) {
// Here process the message
}
|
Remember that, to enable asynchronous messaging, the application must register a message listener with the connection by calling
MessageConnection.setListener(), as in the helper method
newMessageConnection() you saw earlier. Also see that setting the connection's message listener to
null deregisters any current message listener, which will then not receive any notifications.
... mc.setMessageListener(messageListener); ... |
Note that the
readMessageThreaded() method we just defined spawns its own thread of execution to read the message. Because the listener method is called by the platform, you must always minimize the processing it does on the system thread. You should always dispatch a separate thread of execution to consume and process the message, so the platform spends as little time as possible in
notifyIncomingMessage().
| |
Once a message has been read by calling
messageConnection.receive(), it's ready for processing. The return type of
receive() is the generic supertype
Message, so before you can process the message appropriately you must discover its subtype:
BinaryMessage,
MultipartMessage, or
TextMessage.
Let's define a helper method that can be used to view WMA messages through MIDP's Liquid Crystal Display User Interface (LCDUI). It takes a
Message for input and returns UI elements or Items the application can display on an LCDUI Form. The
processMessage() method uses the
instanceof operator to learn the
Message's subtype, then calls the appropriate process-message method, which returns an array of
javax.microedition.lcdui.Items.
/**
* ProcessMessage processes a Message
*/
public Item[] processMessage(Message message) {
Item[] items = null;
// Process the received message appropriately to its type
if (message instanceof TextMessage) {
TextMessage tmsg = (TextMessage)message;
items = processTextMessage(tmsg);
} else
if (message instanceof BinaryMessage) {
BinaryMessage bmsg = (BinaryMessage)message;
items = processBinaryMessage(bmsg);
} else
if (message instanceof MultipartMessage) {
MultipartMessage mpmsg = (MultipartMessage)message;
items = processMultipartMessage(mpmsg);
} else {
// Ignore
System.out.println("Unrecognized message type...");
return null;
}
return items;
}
|
This method delegates the actual work of processing the message and returning the array of
javax.microedition.lcdui.Items to one of three helpers. One of these,
processTextMessage(), retrieves a text message's payload by calling
TextMessage.getPayloadText(), then returns a one-element
StringItem array that contains the message's text:
/**
* Process TextMessage
*
* @param tmsg is the TextMessage to process
*/
public Item[] processTextMessage(TextMessage tmsg) {
Item[] items;
String text = tmsg.getPayloadText();
items = new Item[1];
Item item = new StringItem(null, text);
items[0] = item;
return items;
}
|
The
processBinaryMessage() helper method is similar, but instead of processing a text message it retrieves a binary message's payload by calling
BinaryMessage.getPayloadData(), converts the byte array to a hexadecimal string, then returns a one-element
StringItem array that contains the hex:
/**
* Process BinaryMessage
*
* @param bmsg is the BinaryMessage to process
*/
public Item[] processBinaryMessage(BinaryMessage bmsg) {
Item[] items;
byte[] data = bmsg.getPayloadData();
String hex = toHex(data);
items = new Item[1];
Item item = new StringItem(null, hex);
items[0] = item;
return items;
}
/**
* toHex converts a byte array to human-readable hexadecimal
* This method was borrowed from examples in the Sun Wireless
* Toolkit :-)
* @param data is the array of bytes to convert
* @return a String containing the byte[] in hexadecimal format
*/
final private String toHex(byte[] data) {
StringBuffer buf = new StringBuffer();
for (int i = 0; i < data.length; i++) {
int intData = (int)data[i] & 0xFF;
if (intData < 0x10) buf.append("0");
// display 2 digits per byte i.e. 09
buf.append(Integer.toHexString(intData));
buf.append(' ');
}
return (buf.toString());
}
|
The next helper method,
processMultipartMessage() is a bit more complex.
The MMS headers in a
MultipartMessage are used to ensure delivery of the message from a sender to a recipient, and contain information such as TO, CC, BCC, and FROM addresses. The MMS body itself comprises one or more message parts, each consisting of headers, type, and body.
Each individual message part can be of any MIME type, but the message's overall MMS content type is one of three:
application/vnd.wap.mms-message if it is not a true multimedia message
application/vnd.wap.multipart.related if it is a multimedia message with related presentation information
application/vnd.wap.multipart.mixed if it is a multimedia message with unrelated parts
The following helper method
processMultipartMessage() retrieves the
MultipartMessage header information, then each message part, and finally returns an array of
Items that correspond to each part's MIME type.
...
// MIME Types
public final static String MIMETYPE_TEXT_PLAIN = "text/plain";
public final static String MIMETYPE_TEXT_XML = "text/xml";
public final static String MIMETYPE_TEXT_HTML = "text/html";
public final static String MIMETYPE_IMAGE_PNG = "image/png";
public final static String MIMETYPE_IMAGE_JPEG = "image/jpg";
public final static String MIMETYPE_IMAGE_GIF = "image/gif";
public final static String MIMETYPE_APPLICATION_OCTET_STREAM = "application/octet-stream";
...
/**
* Process MultipartMessage
*
* @param mpmsg is the MultipartMessageto process
* @return array of javax.microedition.lcdui.Item
*/
public Item[] processMultipartMessage(MultipartMessage mpmsg) {
// Get the Multipart message headers from the access methods:
// X-Mms-From, X-Mms-To, X-Mms-CC, X-Mms-BCC, X-Mms-Subject
String from = mpmsg.getAddress();
String tos[] = mpmsg.getAddresses("to");
String ccs[] = mpmsg.getAddresses("cc");
String bccs[] = mpmsg.getAddresses("bcc");
String froms[] = mpmsg.getAddresses("from");
String subject = mpmsg.getSubject();
// Get the rest of the headers by calling the getHeader() method
String mmsPriorityHeader = mpmsg.getHeader("X-Mms-Priority");
String mmsDeliveryTimeHeader =
mpmsg.getHeader("X-Mms-Delivery-Time");
// Get the message timestamp
Date timestamp = mpmsg.getTimestamp();
// Get the start-content ID, which will be set for multimedia
// content, and will contain the presentation information
// (not used in this method)
String startContentID = mpmsg.getStartContentId();
// Get to the message parts array
MessagePart[] messageParts = mpmsg.getMessageParts();
int itemCount = messageParts.length + 2;
// +2 is to account for from & subject
Item[] messageItems = new Item[messageParts.length];
// Create the UI elements for the message's Date and Subject
messageItems[0] = new StringItem("From", from);
messageItems[1] = new StringItem("Subject", subject);
// process each message part
for (int i=0; i<messageParts.length; i++) {
MessagePart messagePart = messageParts[i];
if (messagePart != null) {
String mimeType = messagePart.getMIMEType();
int length = messagePart.getLength();
byte[] content = messagePart.getContent();
String encoding = messagePart.getEncoding();
// Message content can be retrieved as an InputStream
InputStream contentAsInputStream =
messagePart.getContentAsStream();
// Used for multimedia messages, such as SMIL messages
String contentID = messagePart.getContentID();
String contentLocation =
messagePart.getContentLocation();
try {
// text/plain
if (mimeType.equals(MIMETYPE_TEXT_PLAIN)) {
String text = new String(content);
messageItems[i] = new StringItem(null, text);
}
// text/xml
else if (mimeType.equals(MIMETYPE_TEXT_XML)) {
messageItems[i] = new StringItem(null,
"text/xml is not supported");
}
// text/html
else if (mimeType.equals(MIMETYPE_TEXT_HTML)) {
messageItems[i] = new StringItem(null,
"text/html is not supported");
}
// application/octet-stream
else if (mimeType.equals
(MIMETYPE_APPLICATION_OCTET_STREAM)) {
String hex = toHex(content);
messageItems[i] = new StringItem(null, hex);
}
// image/png
else if (mimeType.equals(MIMETYPE_IMAGE_PNG)) {
Image img =
Image.createImage(content, 0, length);
messageItems[i] =
new ImageItem(null, img, 0, "Image");
}
// image/jpg
else if (mimeType.equals(MIMETYPE_IMAGE_JPEG)) {
messageItems[i] = new StringItem(null,
"image/jpeg is not supported");
}
// image/gif
else if (mimeType.equals(MIMETYPE_IMAGE_GIF)) {
messageItems[i] = new StringItem(null,
"image/gif is not supported");
}
} catch (Exception e) {
System.out.println(
"processMultipartMessage, Exception: " + e);
}
}
}
return items;
}
|
Note how some of the
MultipartMessage information that's stored as RFC822 headers is accessible through access methods, while other headers are available only by calling
getHeader() to retrieve them directly,. Other attributes are inaccessible for security reasons.
One of the headers, the start-content ID, refers to the message part that contains the presentation information for true multimedia messages, such as SMIL-based content. To retrieve the start message content ID, call the multipart message method
getStartContentId(), as here:
... // Get the start-content ID, which will be set for multimedia // content, and will contain the presentation information String startContentID = mpmsg.getStartContentId(); ... |
The format of the
X-Mms-Delivery-Time header is milliseconds since midnight January 1, 1970 UTC, as a string value. The format of the contents of
X-Mms-Priority is a string with the value "
high", "
normal", or "
low".
...
// Get the Multipart message headers via the access methods:
// X-Mms-From, X-Mms-To, X-Mms-CC, X-Mms-BCC, X-Mms-Subject
String from = mpmsg.getAddress();
String tos[] = mpmsg.getAddresses("to");
String ccs[] = mpmsg.getAddresses("cc");
String bccs[] = mpmsg.getAddresses("bcc");
String froms[] = mpmsg.getAddresses("from");
String subject = mpmsg.getSubject();
// Get the rest of the headers by calling the getHeader() method
String mmsPriorityHeader = mpmsg.getHeader("X-Mms-Priority");
String mmsDeliveryTimeHeader = mpmsg.getHeader("X-Mms-Delivery-Time");
// Get the message timestamp
Date timestamp = mpmsg.getTimestamp();
...
|
The method
getAddresses() takes for input the address type "
to", "
cc", "
bcc", or "
from", and returns a
String array, or
null if the specified address type is not found. There can be one or more TO, CC, and BCC addresses in any combination. The
getHeader() method takes the header name for input. It throws a
SecurityException if the requested header is restricted, or an
IllegalArgumentException if the header is unknown. For more information on MMS headers refer to Appendix D of the WMA 2.0 specification.
| |
Incoming WMA messages are initially stored on the device's Subscriber Identity Module (SIM) card, and removed from the SIM as they're delivered to the application.
Message persistence at the application level is the developer's responsibility. To add message persistence to your application you must serialize and deserialize the message payloads, and use the Record Management System (RMS) API to manage a local store.
| |
Some transports impose a limit on the size of a single message or on the number of segments that compose a message. Segmentation and reassembly (SAR) is a feature of some low-level transports that concerns breaking a large message into a number of smaller sequential, ordered segments, or transmission units. For example, a message sent on the SMS network is typically limited to 160 GSM 7-bit encoded characters or 140 binary 8-bit bytes; to send a longer message you must segment it. Reassembly is the opposite of segmentation: putting those smaller, related segments back together into a single message.
The WMA 2.0 specification mandates that WMA-based SMS implementations must support at least three SMS-protocol segments for a single message. Some implementations may support more, but applications will be more portable if they adhere to the three-segment limit, and refrain from sending messages that are larger than 456 GSM 7-bit encoded characters, 198 double-byte characters, or 399 binary bytes; see WMA 2.0 Appendix A.1.2 "Message Payload Concatenation" for more information. Sending a larger message may result in an
IOException. Note that for MMS messages, SAR is handled differently, and the size limitation is not as restrictive. Individual message parts can be kilobytes in size. A
SizeExceededException will result if an MMS message part is larger than the available memory, or than the supported size.
To help you deal with SMS message segmentation, WMA provides a method called
MessageConnection.numberOfSegments(), which provides segmentation information for a
Message before you send it with
MessageConnection.send(). The
numberOfSegments() method returns the number of segments it will take to send a message, or
0 if the message can't be sent. Let's look at a modified version of
sendTextMessage() that uses
numberOfSegments():
...
Message msg = ...;
int segcount = mc.numberOfSegments(msg);
if (segcount == 0) {
// can't send, alert the user
alertUser(SEGMENTATIONERROR);
}
...
|
With the information you get from
numberOfSegments() you can also warn the user about higher costs of sending longer messages. For example, sending a short message might cost 5 cents, while sending a large message in three segments might cost 15 cents. Users who want to control costs will welcome the opportunity to cancel longer messages.
| |
The WMA 2.0 defines two system properties to retrieve the address of the Short Message Service Center (SMSC) and the Multimedia Message Service Center (MMSC):
wireless.messaging.sms.smsc - the SMSC attribute name
wireless.messaging.mms.mmsc - the MMSC attribute name
Use the method
System.getProperty(String attributeName) to retrieve these values, thus:
...
String smsc = System.getProperty("wireless.messaging.sms.smsc");
String mmsc = System.getProperty("wireless.messaging.mms.mmsc");
...
|
The SMSC address is an MSISDN number such as
+18472549270. The MMSC address is an MSISDN number value like
+18472549270 or a URL like
http://mmsc.cingular.com.
| |
MIDP 2.0's push registry enables MIDlets to set themselves up to be launched automatically, without user initiation. The push registry manages network- and timer-initiated MIDlet activation; that is, it enables an inbound network connection or a timer-based alarm to wake a MIDlet up.
MIDlets can be activated by incoming WMA connections, if the underlying implementation provides the necessary support. Before a MIDlet can be activated, it must register with the push registry using either static or dynamic registration. Static registration is done by placing
MIDlet-Push-n entries in the JAD or MANIFEST file; dynamic registration is done at runtime, using the
PushRegistry object's
registerConnection() API.
Here's an example of using static registration:
... MIDlet-Push-1: sms://:50000, MyWmaMIDlet, +123456789 MIDlet-Push-2: cbs://:50001, MyWmaMIDlet, * MIDlet-Push-3: mms://:com.j2medeveloper.MyMmsApp, MyWmaMIDlet, * ... |
The first attribute value is the local (server) connection URL; that is, the server protocol and port number or application ID. The second value is the name of the MIDlet class responsible for handling incoming messages for the specified connection URL, and the third is a filter used to restrict the senders that can activate the MIDlet. The next code snippet shows how to use dynamic registration:
...
// Identify the MIDlet class
String midletClassName = this.getClass().getName();
// Register a static connection
String url = "sms://:50000";
// Use a filter for SMS
String filter = "+123456789"; // only allow messages from +123456789
...
try {
PushRegistry.registerConnection(url, midletClassName, filter);
} catch (IOException ioe) {
// Handle the exception
} catch (ClassNotFoundException ioe) {
// Handle the exception
}
...
|
Because the will maintain registrations across MIDlet invocations, it is important to unregister the push connection when it's no longer needed by calling the method
unregisterConnection():
...
String url = "sms://:50000";
...
try {
// unregisterConnection returns true if successful, false if not
status = PushRegistry.unregisterConnection(url);
}
catch(SecurityException e) {
// Handle the exception
}
...
|
You can discover whether your MIDlet was activated by the by calling the method
listConnections(). Let's define the helper method
processPushConnections() to discover and process any pushed connections. You typically invoke this method during application startup.
/**
* Discover whether there are pending push inbound connections
* and, if so, process
*/
public void processPushConnectionsThreaded() {
Thread th = new Thread() {
public void run() {
String[] connections =
PushRegistry.listConnections(true);
if (connections != null && connections.length > 0) {
for (int i=0; i<connections.length; i++) {
String connUrl = connections[i];
if ((connUrl.startsWith("sms")) ||
(connUrl.startsWith("cbs")) ||
(connUrl.startsWith("mms"))) {
try {
MessageConnection messageConnection;
messageConnection = (MessageConnection)
Connector.open(connUrl);
readMessage(messageConnection);
} catch (IOException ioe) {
// Ignore
}
}
}
}
}
};
th.start();
}
|
Note that the
processPushConnectionsThreaded() method runs on its own thread of execution, for two reasons: to avoid contentions with the system threads, and to serialize message processing so messages are processed in the same order they were received. "
The MIDP 2.0 Push Registry" has more information on how to use the push registry's API.
| |
WMA does not define a security mechanism, but instead uses the underlying platform's security framework. On some platforms, including MIDP 2.0, networking operations are considered privileged operations < meaning that to open a connection or to send and receive messages, permissions must be requested by the application and granted by the platform; specifics are implementation-dependent. In MIDP 2.0, permissions are requested by way of the JAD or the manifest, and granted (or not) by the user when the operation (in our case open, send, or receive) is invoked. Note that for signed MIDlets, permissions must be defined in the manifest.
Here's an example of requesting WMA permissions via the JAD or manifest:
... MIDlet-Permissions: javax.microedition.io.Connector.sms, javax.wireless.messaging.sms.receive, javax.wireless.messaging.sms.send, javax.microedition.io.Connector.cbs, javax.wireless.messaging.cbs.receive, javax.microedition.io.Connector.mms, javax.wireless.messaging.mms.receive, javax.wireless.messaging.mms.send, javax.microedition.io.PushRegistry ... |
The WMA defines the
SecurityException class to notify the application of permission exceptions it encounters when invoking platform services. This exception can be thrown as follows:
javax.microedition.io.Connector: if the application is not granted permission to create a connection for a given messaging protocol, as defined by the platform security services
MessageConnection.send(): if the application has no permission to send messages on the specified port
MessageConnection.receive(): if the application has no permission to receive messages on the specified port
For more information about MIDP 2.0 permissions, see the MIDP 2.0 specification and the WMA Recommended Practices, which you can find on the WMA technology page.
| |
You can find a reference implementation of the Wireless Messaging API 2.0 that you can use to test your programs in the J2ME Wireless Toolkit technology page.
| |
This article has covered how to send "mobile-originated" messages from the handset, and how to receive "mobile-terminated" WMA messages sent from a handset or a server. To send messages to a handset from a server you need access to the network provider's SMSC and MMSC servers. Access to these servers is considered privileged and typically you need to go through third-party messaging vendors. These vendors already have relationships with the major carriers such as AT&T (Cingular Blue), Boost Mobile, Cingular (Orange), Nextel, T-Mobile, Sprint, and Verizon, as well as with second-tier carriers such as Alltel, Western Wireless, Leap, Cincinnati Bell, and Dobson. These vendors provide access to the carrier's SMSC and MMSC through exposed APIs that use HTTP and other protocols, allowing your server application to send messages to given handsets, receive messages from handsets, and use premium services such as short-codes.
Also remember that, if you need to send a message to a particular application on a handset, you must set the appropriate port or application ID for the message; if you don't, the message will be delivered to the handset's default viewer. How you set port number or application ID on an outgoing message will depend on the messaging service you use; the vendor may provide APIs or you may have to build the raw message in binary form. For GSM SMS, the port number resides in the
TP-User-Data/User-Data-Header field, while for MMS the application ID resides in the MMS
Content-type header.
| |
The Wireless Messaging API provides a very flexible messaging API that is protocol- and device-independent. The reference implementation allows you to start writing and testing WMA-based applications today. WMA's flexible design ensures that it can easily be extended in the future, and it already supports the most common message types: text, binary, and multimedia. The WMA allows developers to create neat applications that incorporate messaging, such as SMS, email, and rich multimedia, as well as push behavior and consumption of short messages.
| |
| |
I would like to thank Gary Adams of Sun Microsystems, for his feedback to this article.
| |
C. Enrique Ortiz is a software architect and developer, and a wireless mobility technologist and writer. He is author or co-author of many publications, and has been an active participant in the wireless Java community and in various J2ME expert groups. Enrique holds a B.S. in Computer Science from the University of Puerto Rico and has more than 15 years of software engineering, product development, and management experience.
