/*
* MessageScreen.java
*
* Copyright � 1998-2011 Research In Motion Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Note: For the sake of simplicity, this sample application may not leverage
* resource bundles and resource strings. However, it is STRONGLY recommended
* that application developers make use of the localization features available
* within the BlackBerry development platform to ensure a seamless application
* experience across a variety of languages and geographies. For more information
* on localizing your application, please refer to the BlackBerry Java Development
* Environment Development Guide associated with this release.
*/
package com.rim.samples.device.blackberrymaildemo;
import java.util.Date;
import java.util.Vector;
import javax.microedition.pim.Contact;
import net.rim.blackberry.api.mail.Address;
import net.rim.blackberry.api.mail.BodyPart;
import net.rim.blackberry.api.mail.Message;
import net.rim.blackberry.api.mail.MessagingException;
import net.rim.blackberry.api.mail.MimeBodyPart;
import net.rim.blackberry.api.mail.Multipart;
import net.rim.blackberry.api.mail.PDAPContactAttachmentPart;
import net.rim.blackberry.api.mail.ServiceConfiguration;
import net.rim.blackberry.api.mail.SupportedAttachmentPart;
import net.rim.blackberry.api.mail.TextBodyPart;
import net.rim.blackberry.api.mail.Transport;
import net.rim.blackberry.api.mail.UnsupportedAttachmentPart;
import net.rim.device.api.ui.Field;
import net.rim.device.api.ui.component.EditField;
import net.rim.device.api.ui.component.RichTextField;
import net.rim.device.api.ui.component.SeparatorField;
import net.rim.device.api.ui.component.TextField;
import net.rim.device.api.ui.container.MainScreen;
import net.rim.device.api.util.IntHashtable;
/**
* The MessageScreen class allows a user to view a selected message and edit the
* message if the screen is marked editable. It manages the different TextFields
* using a hashtable where the type of information that is held in a given
* TextField is the key while the value is a Vector of TextFields associated
* with that information type. This class supports displaying plain text, Mime,
* supported and unsupported attachments and pdap contacts.
*/
public class MessageScreen extends MainScreen {
// Constants
public final static String NO_SUBJECT = "<No Subject>";
public final static String UNKNOWN_NAME = "<?>";
// Message.RecipientType.TO, Message.RecipientType.CC,
// Message.RecipientType.BCC
// start from 0, and since we share the same IntHashtable with HEADER_KEYS,
// ensure
// the keys don't overlap (don't use values 0, 1, 2 here).
protected final static int SUBJECT = 40000;
protected final static int BODY = 40001;
protected final static int INFO = 40002;
protected final static int[] HEADER_KEYS = { Message.RecipientType.TO,
Message.RecipientType.CC, Message.RecipientType.BCC };
protected final static String[] HEADER_NAMES = { "To: ", "Cc: ", "Bcc: " };
private final static int MAX_CHARS = 128;
protected IntHashtable _fieldTable;
protected Message _message;
private final boolean _editable;
/**
* Creates a new MessageScreen object
*
* @param message
* The message to display
* @param editable
* True is the message is editable, otherwise false
*/
public MessageScreen(final Message message, final boolean editable) {
_fieldTable = new IntHashtable();
_editable = editable;
// Set the message and display its subject as the title if the
// message exists.
_message = message;
if (_message != null) {
setTitle(_message.getSubject());
}
displayMessage();
}
/**
* Displays the message
*/
void displayMessage() {
displayMessageInformation();
add(new SeparatorField());
displayHeader();
add(new SeparatorField());
displayMessageBody();
}
/**
* Displays information about the message's send and recieve properties
*/
protected void displayMessageInformation() {
// Add a field describing the source service
final ServiceConfiguration sc =
_message.getFolder().getStore().getServiceConfiguration();
final EditField service =
new EditField("Service: ", sc.getName(), MAX_CHARS,
Field.READONLY | Field.NON_FOCUSABLE);
addTextFieldToTableAndScreen(service, INFO);
// Add the folder field
final EditField folder =
new EditField("Folder: ", _message.getFolder().getName(),
MAX_CHARS, Field.READONLY | Field.NON_FOCUSABLE);
addTextFieldToTableAndScreen(folder, INFO);
// Add the status of the message
final String statusString = getStatusString(_message);
final EditField status =
new EditField("Status: ", statusString, MAX_CHARS,
Field.READONLY | Field.NON_FOCUSABLE);
addTextFieldToTableAndScreen(status, INFO);
}
/**
* Displays information about the destination and source of the message as
* well as its subject.
*/
protected void displayHeader() {
// Assign the appropriate EditField style property
final long editableStyle = _editable ? Field.EDITABLE : Field.READONLY;
// Display the headers (To:, Cc:, Bcc:)
for (int key = 0; key < HEADER_KEYS.length; key++) {
try {
final Address[] addresses =
_message.getRecipients(HEADER_KEYS[key]);
for (int index = 0; index < addresses.length; index++) {
// Retrieve the name
String name = addresses[index].getName();
if (name == null || name.length() == 0) {
name = addresses[index].getAddr();
}
// Create the edit field, associate the address to the field
// and add it to the screen and collection of fields.
final EditField headerField =
new EditField(HEADER_NAMES[key], name,
TextField.DEFAULT_MAXCHARS, editableStyle);
headerField.setCookie(addresses[index]);
addTextFieldToTableAndScreen(headerField, HEADER_KEYS[key]);
}
} catch (final MessagingException e) {
BlackBerryMailDemo
.errorDialog("Error: could not retrieve message header.");
close();
}
}
// Display the 'Sent' date if it is available
final Date sent = _message.getSentDate();
if (sent != null) {
final EditField sentDate =
new EditField("Sent: ", Util.getDateAsString(sent),
TextField.DEFAULT_MAXCHARS, Field.READONLY
| Field.NON_FOCUSABLE);
// Change the label to "Saved: " if the message hasn't been sent yet
if (_message.getStatus() == Message.Status.TX_COMPOSING) {
sentDate.setLabel("Saved: ");
}
add(sentDate);
}
// Display the 'Received' date if the message was an inbound message
final Date recieved = _message.getSentDate();
if (_message.isInbound() && recieved != null) {
final EditField sentDate =
new EditField("Recieved: ", Util.getDateAsString(recieved),
TextField.DEFAULT_MAXCHARS, Field.READONLY
| Field.NON_FOCUSABLE);
add(sentDate);
}
// If the message was received, retrieve and display who sent the
// message
if (_message.isInbound()) {
try {
final Address from = _message.getFrom();
String name = from.getName();
if (name == null || name.length() == 0) {
name = from.getAddr();
}
final EditField fromField =
new EditField("From: ", name,
TextField.DEFAULT_MAXCHARS, editableStyle);
fromField.setCookie(from);
add(fromField);
} catch (final MessagingException e) {
BlackBerryMailDemo
.errorDialog("Error: could not retrieve message sender.");
close();
}
}
// Display the subject field
String subject = _message.getSubject();
if (subject == null) {
subject = NO_SUBJECT;
}
final EditField subjectField =
new EditField("Subject: ", subject, TextField.DEFAULT_MAXCHARS,
editableStyle);
addTextFieldToTableAndScreen(subjectField, SUBJECT);
}
/**
* Displays the message body
*/
protected void displayMessageBody() {
// Retrieve the parent of the message body
final Object obj = _message.getContent();
Multipart parent = null;
if (obj instanceof MimeBodyPart || obj instanceof TextBodyPart) {
final BodyPart bp = (BodyPart) obj;
parent = bp.getParent();
} else {
parent = (Multipart) obj;
}
// Display the message body
final String mpType = parent.getContentType();
if (mpType
.equals(BodyPart.ContentType.TYPE_MULTIPART_ALTERNATIVE_STRING)
|| mpType
.equals(BodyPart.ContentType.TYPE_MULTIPART_MIXED_STRING)) {
displayMultipart(parent);
}
// Ensure there is at least one body field if nothing was displayed
final Vector bodyVector = (Vector) _fieldTable.get(BODY);
if (bodyVector == null || bodyVector.size() == 0) {
if (_editable) {
addTextFieldToTableAndScreen(new EditField("", ""), BODY);
} else {
addTextFieldToTableAndScreen(new RichTextField(""), BODY);
}
}
}
/**
* Processes a multi-part message by displaying its body parts. Text body
* parts are displayed before attachments and if a multi body part is
* encountered, then it is processed through recursion by calling this
* method on it.
*
* @param multipart
* The multi-part to display
*/
protected void displayMultipart(final Multipart multipart) {
// This vector stores fields which are to be displayed only after all
// of the body fields are displayed. (Attachments and Contacts).
final Vector delayedFields = new Vector();
// Process each part of the multi-part, taking the appropriate action
// depending on the part's type. This loop should: display text and
// html body parts, recursively display multi-parts and store
// attachments and contacts to display later.
for (int index = 0; index < multipart.getCount(); index++) {
final BodyPart bodyPart = multipart.getBodyPart(index);
// If this body part is text then display all of it
if (bodyPart instanceof TextBodyPart) {
final TextBodyPart textBodyPart = (TextBodyPart) bodyPart;
// If there are missing parts of the text, try to retrieve the
// rest of it.
if (textBodyPart.hasMore()) {
try {
Transport.more(textBodyPart, true);
} catch (final Exception e) {
BlackBerryMailDemo
.errorDialog("Transport.more(BodyPart, boolean) threw "
+ e.toString());
}
}
final String plainText = (String) textBodyPart.getContent();
// Display the plain text, using an EditField if the message is
// editable or a RichTextField if it is not editable. Note: this
// does not add any empty fields.
if (plainText.length() != 0) {
if (_editable) {
addTextFieldToTableAndScreen(new EditField("",
plainText), BODY);
} else {
addTextFieldToTableAndScreen(new RichTextField(
plainText), BODY);
}
}
} else if (bodyPart instanceof MimeBodyPart) {
final MimeBodyPart mimeBodyPart = (MimeBodyPart) bodyPart;
// If the content is text then display it
final String contentType = mimeBodyPart.getContentType();
if (contentType
.startsWith(BodyPart.ContentType.TYPE_TEXT_HTML_STRING)) {
final Object obj = mimeBodyPart.getContent();
if (obj != null) {
final String htmlText = new String((byte[]) obj);
addTextFieldToTableAndScreen(
new RichTextField(htmlText), BODY);
}
} else if (contentType
.equals(BodyPart.ContentType.TYPE_MULTIPART_ALTERNATIVE_STRING)) {
// If the body part is a multi-part and it has the the
// content type of TYPE_MULTIPART_ALTERNATIVE_STRING, then
// recursively display the multi-part.
final Object obj = mimeBodyPart.getContent();
if (obj instanceof Multipart) {
final Multipart childMultipart = (Multipart) obj;
final String childMultipartType =
childMultipart.getContentType();
if (childMultipartType
.equals(BodyPart.ContentType.TYPE_MULTIPART_ALTERNATIVE_STRING)) {
displayMultipart(childMultipart);
}
}
}
} else if (bodyPart instanceof SupportedAttachmentPart
|| bodyPart instanceof UnsupportedAttachmentPart) {
// Extract the content type and name from the attachments
final String contentType = bodyPart.getContentType();
String name;
if (bodyPart instanceof UnsupportedAttachmentPart) {
final UnsupportedAttachmentPart uap =
(UnsupportedAttachmentPart) bodyPart;
name = uap.getName();
} else // The bodyPart is a SupportedAttachmentPart
{
final SupportedAttachmentPart sap =
(SupportedAttachmentPart) bodyPart;
name = sap.getName();
}
// Format the content type and name to display and store
// the field.
final StringBuffer sb =
new StringBuffer(contentType.length() + name.length()
+ 2);
sb.append(contentType);
sb.append('[');
sb.append(name);
sb.append(']');
delayedFields.addElement(new RichTextField(sb.toString()));
} else if (bodyPart instanceof PDAPContactAttachmentPart) {
final Contact contact = (Contact) bodyPart.getContent();
// Build the contact name
final StringBuffer sb = new StringBuffer("Contact: ");
if (contact.countValues(Contact.NAME) > 0) {
final String[] name =
contact.getStringArray(Contact.NAME, 0);
if (name[Contact.NAME_PREFIX] != null) {
sb.append(name[Contact.NAME_PREFIX]);
sb.append(' ');
}
if (name[Contact.NAME_GIVEN] != null) {
sb.append(name[Contact.NAME_GIVEN]);
sb.append(' ');
}
if (name[Contact.NAME_FAMILY] != null) {
sb.append(name[Contact.NAME_FAMILY]);
}
// Trim the last space of the name if it exists
final int lastChar = sb.length() - 1;
if (sb.charAt(lastChar) == ' ') {
sb.deleteCharAt(lastChar);
}
} else {
sb.append(UNKNOWN_NAME);
}
// Create the contact attachment field and store it
final RichTextField contactAttachment =
new RichTextField(sb.toString());
contactAttachment.setCookie(contact);
delayedFields.addElement(contactAttachment);
}
}
// Now that the body parts have been displayed, display the queued
// fields while separating them by inserting a separator field.
for (int index = 0; index < delayedFields.size(); index++) {
add(new SeparatorField());
addTextFieldToTableAndScreen((TextField) delayedFields
.elementAt(index), BODY);
}
}
/**
* Compiles the status of a message into a readable string.
*
* @param message
* The message whose status is to be compiled into a string
* @return The string displaying the status of the message
*/
public static String getStatusString(final Message message) {
final StringBuffer statusStrBuffer = new StringBuffer();
// Add any errors to the status string if it applies
final int status = message.getStatus();
if (status == Message.Status.RX_ERROR) {
statusStrBuffer.append("RX ERROR, ");
}
if (status == Message.Status.TX_GENERAL_FAILURE) {
statusStrBuffer.append("TX GENERAL FAILURE, ");
}
if (status == Message.Status.TX_ERROR) {
statusStrBuffer.append("TX ERROR, ");
}
// Use the flags to add any message statuses
final int flags = message.getFlags();
if (0 != (flags & Message.Flag.OPENED)) {
statusStrBuffer.append("Opened, ");
}
if (0 != (flags & Message.Flag.SAVED)) {
statusStrBuffer.append("Saved, ");
}
if (0 != (flags & Message.Flag.FILED)) {
statusStrBuffer.append("Filed, ");
}
// Check if the message has a high or low priority
final byte messagePriority = message.getPriority();
if (messagePriority == Message.Priority.HIGH) {
statusStrBuffer.append("High Priority, ");
} else if (messagePriority == Message.Priority.LOW) {
statusStrBuffer.append("Low Priority, ");
}
// If there are any characters in the status string then delete the last
// two characters (i.e. ", ").
if (statusStrBuffer.length() > 0) {
statusStrBuffer.delete(statusStrBuffer.length() - 2,
statusStrBuffer.length());
}
return statusStrBuffer.toString();
}
/**
* Add a new field to the hashtable of TextFields and to the screen
*
* @param field
* The field to add
* @param type
* The type of field to add
*/
protected void addTextFieldToTableAndScreen(final TextField field,
final int type) {
Vector fieldsByType = (Vector) _fieldTable.get(type);
// If the vector of fields associated with the type is not made yet,
// initialize one and put it into the fields collection.
if (fieldsByType == null) {
fieldsByType = new Vector(1);
_fieldTable.put(type, fieldsByType);
}
fieldsByType.addElement(field);
add(field);
}
/**
* @see net.rim.device.api.ui.Screen#onClose()
*/
public boolean onClose() {
// If the message status is "received", mark it "read"
if (_message != null
&& _message.getStatus() == Message.Status.RX_RECEIVED) {
_message.setStatus(Message.Status.TX_READ, Message.Status.TX_ERROR);
_message.setFlag(Message.Flag.OPENED, true);
}
return super.onClose();
}
}