Difference between revisions of "Teams Winter 2011/team1/BlackBerry/Add Send Email Option"

From CDOT Wiki
Jump to: navigation, search
Line 1: Line 1:
== Adding Send Email Functionality ==
+
== 9. Adding Send Email Functionality ==
  
9.1 Lets first add the api which we will need for the email functionality
+
9.1. Add the Send email option to the menu and implement it:
<source lang="java">
+
<source lang="java">
  import net.rim.blackberry.api.mail;
+
// Send email to student
</source>
+
        ImageMenuItem emailItem = new ImageMenuItem("Email Student", 400, 4, MENU_EMAIL);
 +
        emailItem.setCommand(new Command(new CommandHandler()
 +
        {
 +
            public void execute(ReadOnlyCommandMetadata metadata, Object context)
 +
            {
 +
                Student student = (Student) _keywordFilterField.getSelectedElement();
  
9.2 In order to add send email functionality to our application, we must have the appropriate menu option in our application.
+
                if (student != null)
 +
                {
 +
                    try
 +
                    {
 +
                        ComposeScreen composeScreen = new ComposeScreen(null, _store, student.getEmail());
 +
                        pushScreen(composeScreen);
 +
                    }
 +
                    catch(Exception ex)
 +
                    {
 +
                        Dialog.alert("composing screen: " + ex.getMessage());
 +
                    }
  
<source lang="java">
+
                }
// Email selected student
+
            }
MenuItem emailStudent = new MenuItem(new StringProvider("Email Student"), 500, 5);
+
        }));
editItem.setCommand(new Command(new CommandHandler(){
+
        _mainScreen.addMenuItem(emailItem);
 +
</source>
 +
9.2 Add  four new Screen classes and a helper class to the project:
 +
* <code>ComposeScreen.java</code> - start screen where email is composed
 +
* <code>MessageScreen.java</code> - screen showing the email
 +
* <code>MessagesViewScreen.java</code> - to view the created email
 +
* <code>MessageListField.java</code> - handles the drawing of the list of messages
 +
* <code>FoldersViewScreen.java</code> - view emails in the Outbox folder
 +
The main functionality is taken from the Sample provided with blackberry plug-in: <code>BlackBerryMailDemo</code><br/>
 +
9.3. Following is the modified code to accommodate the application:<br/>
 +
<code>ComposeScreen.java</code>
 +
<source lang="java">
 +
  package cs.ecl.team1.project;
  
public void execute(ReadOnlyCommandMetadata metadata, Object context) {
+
  import java.util.*;
// TODO Auto-generated method stub
+
  import net.rim.blackberry.api.mail.*;
 +
  import net.rim.device.api.command.*;
 +
  import net.rim.device.api.ui.component.*;
 +
  import net.rim.device.api.ui.*;
 +
  import net.rim.device.api.util.*;
  
}
 
}
 
));
 
  
</source>
+
  /**
 +
  * The ComposeScreen is a screen which displays either a new or saved message.
 +
  * It adds the functionality of saving and sending messages to its parent class,
 +
  * MessageScreen.
 +
  */
 +
public final class ComposeScreen extends MessageScreen
 +
 +
    private static final int FIRST = 0;
 +
    private static final int SEND_MENU_ITEM_INDEX = 0;
 +
 
 +
    private Store _store;
 +
    private MessagesViewScreen _messagesViewScreen; 
  
9.3 Now lets get the students email address and name
+
 
<source lang="java">
+
    /**
  Student student = (Student) _keywordFilterField.getSelectedElement();
+
    * Creates a new ComposeScreen object
  String studentEmail = student.getEmail();
+
    * @param message A message in the process of being composed, or null if a new message is to be composed
  String studentName = student.getname();
+
    * @param store The message store for this application
</source>
+
    */
 +
 
 +
    public ComposeScreen(Message message, Store store, String email)
 +
    {
 +
        super(message, true, email);  
 +
        _store = store;
 +
        _messagesViewScreen = new MessagesViewScreen();    
 +
     
 +
        // If a new message is to be created, indicate this in the title
 +
        if( message == null )
 +
        {
 +
            setTitle("New Message");
 +
        }
  
9.4 Now we must set up a new message and content. Here we create a new message in the SENT folder of the device and fill it with a predefined body
+
        // Create and add menu items specific to the Compose action (addTo,
<source lang="java">
+
        // addBcc, addCc, etc...).
  Store store = Session.getDefaultInstance().getStore();
+
        AddHeaderFieldAction addToMenuItem = new AddHeaderFieldAction(Message.RecipientType.TO, "Add To: ", "To: ");
  Folder sentFolder = store.list(Folder.SENT);
+
        AddHeaderFieldAction addCcMenuItem = new AddHeaderFieldAction(Message.RecipientType.CC, "Add Cc: ", "Cc: ");
  Message msg = new Message(sentFolder);
+
        AddHeaderFieldAction addBccMenuItem = new AddHeaderFieldAction(Message.RecipientType.BCC, "Add Bcc: ", "Bcc: ");
                 
+
     
                     
 
  // Set up recipients
 
  Address recipients = new Address(studentEmail, studentName);
 
  //Add the recipient to the message
 
  msg.addRecipients(Message.RecipientType.TO, recipients);
 
  //set a subject for the message
 
  msg.setSubject(“Team 1 test email”);
 
  //sets the body of the message
 
  msg.setContent(“This is a test email sent to: ” + studentName);
 
  //sets priority
 
  msg.setPriority(Message.Priority.HIGH);
 
  //send the message
 
  Transport.send(msg);
 
</source>
 
  
9.5 Lets catch any exceptions and notify the user if the message was sent.
+
        // MenuItem to save a message
<source lang="java">
+
        MenuItem saveMenuItem = new MenuItem(new StringProvider("Save Message"), 0x230020, 1);
   catch (Exception me) {
+
        saveMenuItem.setCommand(new Command(new CommandHandler()
     System.err.println(me);
+
        {
  }
+
            /**
               
+
            * @see net.rim.device.api.command.CommandHandler#execute(ReadOnlyCommandMetadata, Object)
  Dialog.alert(studentName + " has been emailed!");
+
            */
</source>
+
            public void execute(ReadOnlyCommandMetadata metadata, Object context)
 +
            {      // If the save is completed, then discard this screen         
 +
                if( onSave() )
 +
                {
 +
                    close();
 +
                }
 +
                else
 +
                // If the message could not be saved, alert the user
 +
                {
 +
                    UiApplication.getUiApplication().invokeLater(new Runnable()
 +
                    {
 +
                        public void run()
 +
                        {
 +
                            Dialog.alert("Message could not be saved");
 +
                        }
 +
                    });
 +
                }
 +
            }
 +
        }));
 +
     
 +
        // MenuItem to send a message
 +
        MenuItem sendMenuItem = new MenuItem(new StringProvider("Send Message"), 0x230010, 0);
 +
        sendMenuItem.setCommand(new Command(new CommandHandler()
 +
        {
 +
            /**
 +
            * @see net.rim.device.api.command.CommandHandler#execute(ReadOnlyCommandMetadata, Object)
 +
            */
 +
            public void execute(ReadOnlyCommandMetadata metadata, Object context)
 +
            {      // If the save is completed, then discard this screen         
 +
                try
 +
                {
 +
                    _message = getMessage();
 +
                    if( _message != null )
 +
                    {
 +
                        // Send the message
 +
                        Transport.send(_message);
 +
                     
 +
                        UiApplication.getUiApplication().pushScreen(_messagesViewScreen);
 +
                        close();
 +
                    }
 +
                }
 +
              catch( MessagingException e )
 +
                {             
 +
                    ViewStudentApp.errorDialog("Transport.send(Message) threw " + e.toString());
 +
                }
 +
            }
 +
        }));
 +
 
 +
        addMenuItem(sendMenuItem);
 +
        addMenuItem(saveMenuItem);
 +
        addMenuItem(addToMenuItem);
 +
        addMenuItem(addCcMenuItem);
 +
        addMenuItem(addBccMenuItem);
 +
    }
 +
 
 +
 
 +
    /**
 +
    * Overrides MessageScreen.displayMessage(). The message's 'sent' properties
 +
    * are not displayed since the message is still in the process of editing.
 +
    */
 +
    void displayMessage(String email)
 +
    {
 +
        // If the message does not exist then compose a new message
 +
        if( _message == null )
 +
        {
 +
            // Add a To line
 +
            EditField toField = new EditField("To: ", email, 40, BasicEditField.FILTER_EMAIL);         
 +
            addTextFieldToTableAndScreen(toField, Message.RecipientType.TO);
 +
 
 +
            // Add a subject line
 +
            EditField subjectField = new EditField("Subject: ", "");
 +
            addTextFieldToTableAndScreen(subjectField, SUBJECT);
 +
 
 +
            // Add a separator between the body and the headers
 +
            add(new SeparatorField());
 +
 
 +
            // Add a body field
 +
            EditField bodyField = new EditField();
 +
            addTextFieldToTableAndScreen(bodyField, BODY);
 +
        }
 +
        else
 +
        // The message exists so display it
 +
        {
 +
            displayHeader();
 +
            add(new SeparatorField());
 +
            displayMessageBody();
 +
        }
 +
    } 
 +
 
 +
 
 +
    /**
 +
    * Gets a message for sending or saving
 +
    * @return A new message
 +
    */
 +
    Message getMessage()
 +
    {
 +
        // Find an outbox folder and use it to construct a new message   
 +
        Folder outbox = _store.findFolder("Outbox")[ FIRST ];     
 +
        Message message = new Message(outbox);
 +
 
 +
        // Add all the current headers
 +
        for( int keyNo = 0; keyNo < HEADER_KEYS.length; keyNo++ )
 +
        {
 +
            Vector fieldsByType = (Vector) _fieldTable.get(HEADER_KEYS[ keyNo ]);
 +
 
 +
            if( fieldsByType != null )
 +
            {
 +
                // Build a vector of all the addresses
 +
                Vector addressVector = new Vector();
 +
                int size = fieldsByType.size();
 +
                for( int fieldNo = 0; fieldNo < size; fieldNo++ )
 +
                {
 +
                    TextField addressField = (TextField) fieldsByType.elementAt(fieldNo);
 +
 
 +
                    // Try to create a new address object wrapping the email
 +
                    // address and add it to the address vector.
 +
                    try
 +
                    {
 +
                        addressVector.addElement(new Address(addressField.getText(), ""));
 +
                    }
 +
                    catch( AddressException e ) // Invalid address
 +
                    {
 +
                        ViewStudentApp.errorDialog("Address(String, String) threw " + e.toString());
 +
                    }
 +
                }
 +
 
 +
                // Dump the vector of addresses into an array to send the message
 +
                Address[] addresses = new Address[ addressVector.size() ];
 +
                addressVector.copyInto(addresses);
 +
 
 +
                // Try to add the addresses to the message's list of recipients
 +
                try
 +
                {
 +
                    message.addRecipients(HEADER_KEYS[ keyNo ], addresses);
 +
                }
 +
                catch( MessagingException e )
 +
                {                 
 +
                    ViewStudentApp.errorDialog("Message#addRecipients(int, Address[]) threw " + e.toString());
 +
                }
 +
            }
 +
        }
 +
 
 +
        // Add the subject
 +
        Vector subjectFields = (Vector) _fieldTable.get(SUBJECT);
 +
        TextField subjectField = (TextField) subjectFields.elementAt(FIRST);
 +
 
 +
        if( subjectFields != null && subjectFields.size() > 0 )
 +
        {
 +
            message.setSubject(subjectField.getText());
 +
        }
 +
 
 +
        // Add the body by adding all the body fields into one multipart
 +
        Vector bodyFields = (Vector) _fieldTable.get(BODY);
 +
        if( bodyFields != null )
 +
        {
 +
            int size = bodyFields.size();
 +
            Multipart content = new Multipart();
 +
            for( int fieldNo = 0; fieldNo < size; fieldNo++ )
 +
            {
 +
                TextField body = (TextField) bodyFields.elementAt(fieldNo);
 +
                content.addBodyPart(new TextBodyPart(content, body.getText()));
 +
            }
 +
            try
 +
            {
 +
                message.setContent(content);
 +
            }
 +
            catch( MessagingException e )
 +
            {             
 +
                ViewStudentApp.errorDialog("Message#setContent(Object) threw " + e.toString());
 +
            }
 +
        }
 +
        else
 +
        {
 +
            ViewStudentApp.errorDialog("Error: no body field available");
 +
            return null;
 +
        }
 +
 
 +
        // Set the date
 +
        message.setSentDate(Calendar.getInstance().getTime());
 +
 
 +
        return message;
 +
    }
 +
 
 +
 
 +
    /**
 +
    * @see net.rim.device.api.ui.Screen#onSave()
 +
    */
 +
    protected boolean onSave()
 +
    {
 +
        // Save the message to the outbox
 +
        try
 +
        {
 +
            Message newMessage = getMessage();
 +
            if( newMessage != null )
 +
            {
 +
                // Retrieve an outbox to save the message in
 +
                Store store = Session.waitForDefaultSession().getStore();
 +
                Folder[] allOutboxFolders = store.list(Folder.OUTBOX);
 +
 
 +
                Folder outbox = null;             
 +
                for( int i = allOutboxFolders.length - 1; i >= 0 ; --i )
 +
                {
 +
                    if( allOutboxFolders[ i ].getParent().getName().startsWith("Mailbox") )
 +
                    {
 +
                        outbox = allOutboxFolders[ i ];
 +
                        break;
 +
                    }
 +
                }
 +
 
 +
                // Save the new message and replace the old one if it exists
 +
                outbox.appendMessage(newMessage);
 +
                if( _message != null )
 +
                {
 +
                    outbox.deleteMessage(_message, true);
 +
                }
 +
                _message = newMessage;
 +
 
 +
                // Set the status to composing and flag that it has been saved
 +
                _message.setStatus(Message.Status.TX_COMPOSING, Message.Status.TX_ERROR);
 +
                _message.setFlag(Message.Flag.SAVED, true);
 +
 
 +
                return true;
 +
            }
 +
 
 +
            return false;
 +
        }
 +
        catch( MessagingException e )
 +
        {
 +
            return false;
 +
        }
 +
    }
 +
 
 +
 
 +
    /**
 +
    * Make "Send" the default menu item.
 +
    *
 +
    * @see net.rim.device.api.ui.container.MainScreen#makeMenu(Menu,int)
 +
    */
 +
    protected void makeMenu(Menu menu, int instance)
 +
    {
 +
        super.makeMenu(menu, instance);
 +
 
 +
      //  menu.setDefault(SEND_MENU_ITEM_INDEX);
 +
    }
 +
 
 +
 
 +
    /**
 +
    * This class is responsible for adding the various header fields to the
 +
    * compose screen (To, Bcc, CC).
 +
    */
 +
    private final class AddHeaderFieldAction extends MenuItem
 +
    {
 +
        private String _fieldLabelText;
 +
        private int _headerType;
 +
 
 +
        /**
 +
        * Constructs a menu item which adds a header of a specified type to the
 +
        * compose screen.
 +
        *
 +
        * @param headerType One of the Message.RecipientType fields
 +
        * @param menuItemText String to use for the menu item
 +
        * @param fieldLabelText String to use for the label of this field
 +
        */
 +
        AddHeaderFieldAction(int headerType, String menuItemText, String fieldLabelText)
 +
        {
 +
            super(new StringProvider(menuItemText), 0x240010, 2);
 +
 
 +
            _fieldLabelText = fieldLabelText;
 +
            _headerType = headerType;
 +
            this.setCommand(new Command(new CommandHandler()
 +
            {
 +
                /**
 +
                * Adds a new header field to the message. The field is placed so that
 +
                * the header types are grouped together and the most recently added one
 +
                * is closest to the bottom.       
 +
                * @see net.rim.device.api.command.CommandHandler#execute(ReadOnlyCommandMetadata, Object)
 +
                */
 +
                public void execute(ReadOnlyCommandMetadata metadata, Object context)
 +
                {
 +
                    EditField newField = new EditField(_fieldLabelText, "");
 +
 
 +
                    // Find out where the last field of this type was added to the
 +
                    // screen.
 +
                    Vector fieldsByType = (Vector) _fieldTable.get(_headerType);
 +
                    int lastInsertedIndex;
 +
                    if( fieldsByType == null )
 +
                    {
 +
                        // If a field of _headerType was not made yet, then create the
 +
                        // vector which contains all of the fields of _headerType.
 +
                        fieldsByType = new Vector();
 +
                        _fieldTable.put(_headerType, fieldsByType);
 +
                        lastInsertedIndex = getIndexForNewFieldType();
 +
                    }
 +
                    else
 +
                    {
 +
                        lastInsertedIndex = getIndexOfLastFieldOfType(_headerType);
 +
                    }
 +
 
 +
                    // Add the new field to both the screen and the vector keeping track
 +
                    // of all the fields of the same type.
 +
                    ComposeScreen.this.insert(newField, lastInsertedIndex + 1);
 +
                    fieldsByType.addElement(newField);
 +
                    newField.setFocus();
 +
                }
 +
            }));
 +
        }
 +
 
 +
     
 +
        /**
 +
        * Given the existing field type of this instance of the class,
 +
        * determine where to place new header fields in the screen.
 +
        *
 +
        * @return The index at which to insert the new header field
 +
        */
 +
        private int getIndexForNewFieldType()
 +
        {
 +
            // Note: we don't handle TO here since there ALWAYS must be one TO
 +
            // field.
 +
            switch( _headerType )
 +
            {
 +
                // Find the last TO field and use the next index as the
 +
                // insertion point.
 +
                case Message.RecipientType.CC:
 +
                    return getIndexOfLastFieldOfType(Message.RecipientType.TO);
 +
 
 +
                    // Try to find the last CC field and use it as the next
 +
                    // insertion point. If no CC field exists then find the last
 +
                    // TO field and use its index as the insertion point.
 +
                case Message.RecipientType.BCC:
 +
                    int index = getIndexOfLastFieldOfType(Message.RecipientType.CC);
 +
                    if( index == -1 )
 +
                        return getIndexOfLastFieldOfType(Message.RecipientType.TO);
 +
                    return index;
 +
 
 +
                default:
 +
                    throw new IllegalStateException("Mail Demo: Unrecognized recipient type");
 +
            }
 +
        }
 +
 
 +
     
 +
        /**
 +
        * Retrieves the index of the last field added of a specified type.
 +
        *
 +
        * @param type The type of header field to retrieve the last index of
 +
        * @return The index of the most recently added field of the specified
 +
        *        type, -1 if a field of the specified type has not been added
 +
        *        yet.
 +
        */
 +
        private int getIndexOfLastFieldOfType(int type)
 +
        {
 +
            Vector fields = (Vector) _fieldTable.get(type);
 +
            if( fields == null )
 +
            {
 +
                return -1;
 +
            }
 +
 
 +
            Field field = (Field) fields.lastElement();
 +
 
 +
            return field.getIndex();
 +
        }
 +
    }
 +
}
 +
 
 +
</source>
 +
<code>MessageScreen.java</code>
 +
<source lang="java">
 +
   package cs.ecl.team1.project;
 +
 
 +
  import net.rim.device.api.ui.container.*;
 +
  import net.rim.device.api.ui.component.*;
 +
  import net.rim.device.api.util.IntHashtable;
 +
  import javax.microedition.pim.Contact;
 +
  import java.util.*;
 +
 
 +
  import net.rim.blackberry.api.mail.*;
 +
 
 +
  /**
 +
  * 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 = "<?>";
 +
 
 +
    protected final static int SUBJECT = 0;
 +
    protected final static int BODY = 1;
 +
    protected final static int INFO = 2;
 +
    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 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(Message message, boolean editable, String email)
 +
    {
 +
        _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(email);
 +
    }
 +
 
 +
 
 +
    /**
 +
    * Displays the message
 +
    */
 +
    void displayMessage(String email)
 +
    {
 +
        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
 +
        ServiceConfiguration sc = _message.getFolder().getStore().getServiceConfiguration();
 +
        EditField service = new EditField("Service: ", sc.getName(), MAX_CHARS, EditField.READONLY | EditField.NON_FOCUSABLE);
 +
        addTextFieldToTableAndScreen(service, INFO);
 +
 
 +
        // Add the folder field
 +
        EditField folder = new EditField("Folder: ", _message.getFolder().getName(), MAX_CHARS, EditField.READONLY
 +
                | EditField.NON_FOCUSABLE);
 +
        addTextFieldToTableAndScreen(folder, INFO);
 +
 
 +
        // Add the status of the message
 +
        String statusString = getStatusString(_message);
 +
        EditField status = new EditField("Status: ", statusString, MAX_CHARS, EditField.READONLY | EditField.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
 +
        long editableStyle = _editable ? EditField.EDITABLE : EditField.READONLY;
 +
 
 +
        // Display the headers (To:, Cc:, Bcc:)
 +
        for( int key = 0; key < HEADER_KEYS.length; key++ )
 +
        {
 +
            try
 +
            {
 +
                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.
 +
                    EditField headerField = new EditField(HEADER_NAMES[ key ], name, EditField.DEFAULT_MAXCHARS, editableStyle);
 +
                    headerField.setCookie(addresses[ index ]);
 +
 
 +
                    addTextFieldToTableAndScreen(headerField, HEADER_KEYS[ key ]);
 +
                }
 +
            }
 +
            catch( MessagingException e )
 +
            {             
 +
                ViewStudentApp.errorDialog("Error: could not retrieve message header.");             
 +
                close();
 +
            }
 +
        }
 +
 
 +
        // Display the 'Sent' date if it is available
 +
        Date sent = _message.getSentDate();
 +
        if( sent != null )
 +
        {
 +
            EditField sentDate = new EditField("Sent: ", Util.getDateAsString(sent), EditField.DEFAULT_MAXCHARS, EditField.READONLY
 +
                    | EditField.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 subject field
 +
        String subject = _message.getSubject();
 +
        if( subject == null )
 +
            subject = NO_SUBJECT;
 +
 
 +
        EditField subjectField = new EditField("Subject: ", subject, EditField.DEFAULT_MAXCHARS, editableStyle);
 +
        addTextFieldToTableAndScreen(subjectField, SUBJECT);
 +
    }
 +
 
 +
    /**
 +
    * Displays the message body
 +
    */
 +
    protected void displayMessageBody()
 +
    {
 +
        // Retrieve the parent of the message body
 +
        Object obj = _message.getContent();
 +
        Multipart parent = null;
 +
        if( obj instanceof MimeBodyPart || obj instanceof TextBodyPart )
 +
        {
 +
            BodyPart bp = (BodyPart) obj;
 +
            parent = bp.getParent();
 +
        }
 +
        else
 +
        {
 +
            parent = (Multipart) obj;
 +
        }
 +
 
 +
        // Display the message body
 +
        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
 +
        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
 +
    * @param editable True if this multi-part is editable
 +
    */
 +
     protected void displayMultipart(Multipart multipart)
 +
    {
 +
        // This vector stores fields which are to be displayed only after all
 +
        // of the body fields are displayed. (Attachments and Contacts).
 +
        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++ )
 +
        {
 +
            BodyPart bodyPart = multipart.getBodyPart(index);
 +
 
 +
            // If this body part is text then display all of it
 +
            if( bodyPart instanceof TextBodyPart )
 +
            {
 +
                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( Exception e )
 +
                    {
 +
                        ViewStudentApp.errorDialog("Transport.more(BodyPart, boolean) threw " + e.toString());
 +
                    }
 +
                }
 +
                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 )
 +
            {
 +
                MimeBodyPart mimeBodyPart = (MimeBodyPart) bodyPart;
 +
 
 +
                // If the content is text then display it
 +
                String contentType = mimeBodyPart.getContentType();
 +
                if( contentType.startsWith(BodyPart.ContentType.TYPE_TEXT_HTML_STRING) )
 +
                {
 +
                    Object obj = mimeBodyPart.getContent();
 +
                    if( obj != null )
 +
                    {
 +
                        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.
 +
                    Object obj = mimeBodyPart.getContent();
 +
                    if( obj instanceof Multipart )
 +
                    {
 +
                        Multipart childMultipart = (Multipart) obj;
 +
                        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
 +
                String contentType = bodyPart.getContentType();
 +
                String name;
 +
                if( bodyPart instanceof UnsupportedAttachmentPart )
 +
                {
 +
                    UnsupportedAttachmentPart uap = (UnsupportedAttachmentPart) bodyPart;
 +
                    name = uap.getName();
 +
                }
 +
                else // The bodyPart is a SupportedAttachmentPart
 +
                {
 +
                    SupportedAttachmentPart sap = (SupportedAttachmentPart) bodyPart;
 +
                    name = sap.getName();
 +
                }
 +
 
 +
                // Format the content type and name to display and store
 +
                // the field.
 +
                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 )
 +
            {
 +
                Contact contact = (Contact) bodyPart.getContent();
 +
 
 +
                // Build the contact name
 +
                StringBuffer sb = new StringBuffer("Contact: ");
 +
                if( contact.countValues(Contact.NAME) > 0 )
 +
                {
 +
                    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
 +
                    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
 +
                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(Message message)
 +
    { 
 +
        StringBuffer statusStrBuffer = new StringBuffer();
 +
 
 +
        // Add any errors to the status string if it applies
 +
        int status = message.getStatus();
 +
        if( status == Message.Status.RX_ERROR )
 +
        {
 +
            statusStrBuffer.append("RX ERROR, ");
 +
        }
 +
 
 +
        if( status == Message.Status.TX_GENERAL_FAILURE )
 +
        {
 +
            statusStrBuffer.append("RX ERROR, ");
 +
        }
 +
 
 +
        if( status == Message.Status.TX_ERROR )
 +
        {
 +
            statusStrBuffer.append("TX ERROR, ");
 +
        }
 +
 
 +
        // Use the flags to add any message statuses
 +
        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
 +
        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 if there are any characters to delete. Should be
 +
        // either ", " or "  ".
 +
        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(TextField field, 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();
 +
    }
 +
}
 +
</source>
 +
Other classes stay unchanged.<br/>
 +
9.4. Run the application, select a user from the list, and select Email Student from the menu:<br/>
 +
[[Image: BB_email1.png | 300px]]
 +
[[Image: BB_email2.png | 300px]]<br/>
 +
9.5. Type in the subject and some text in the body, and select Send Email from the menu:<br/>
 +
[[Image: BB_email3.png | 300px]]<br/>
 +
9.6. The messageList screen is displayed showing the created email. Select Folder View from the menu to see the Outbox folder:<br/>
 +
[[Image: BB_email4.png | 300px]]
 +
[[Image: BB_email5.png | 300px]]

Revision as of 12:17, 11 April 2011

9. Adding Send Email Functionality

9.1. Add the Send email option to the menu and implement it:

 // Send email to student
        ImageMenuItem emailItem = new ImageMenuItem("Email Student", 400, 4, MENU_EMAIL);
        emailItem.setCommand(new Command(new CommandHandler()
        {
            public void execute(ReadOnlyCommandMetadata metadata, Object context)
            {
                Student student = (Student) _keywordFilterField.getSelectedElement();

                if (student != null)
                { 
                    try
                    {
                        ComposeScreen composeScreen = new ComposeScreen(null, _store, student.getEmail());
                        pushScreen(composeScreen);
                    }
                    catch(Exception ex)
                    {
                        Dialog.alert("composing screen: " + ex.getMessage());
                    }

                }
            }
        }));
        _mainScreen.addMenuItem(emailItem);

9.2 Add four new Screen classes and a helper class to the project:

  • ComposeScreen.java - start screen where email is composed
  • MessageScreen.java - screen showing the email
  • MessagesViewScreen.java - to view the created email
  • MessageListField.java - handles the drawing of the list of messages
  • FoldersViewScreen.java - view emails in the Outbox folder

The main functionality is taken from the Sample provided with blackberry plug-in: BlackBerryMailDemo
9.3. Following is the modified code to accommodate the application:
ComposeScreen.java

  package cs.ecl.team1.project;

  import java.util.*;
  import net.rim.blackberry.api.mail.*;
  import net.rim.device.api.command.*;
  import net.rim.device.api.ui.component.*;
  import net.rim.device.api.ui.*;
  import net.rim.device.api.util.*;


  /**
  * The ComposeScreen is a screen which displays either a new or saved message.
  * It adds the functionality of saving and sending messages to its parent class,
  * MessageScreen.
  */
public final class ComposeScreen extends MessageScreen
{   
    private static final int FIRST = 0;
    private static final int SEND_MENU_ITEM_INDEX = 0;
   
    private Store _store;
    private MessagesViewScreen _messagesViewScreen;   

   
    /**
     * Creates a new ComposeScreen object
     * @param message A message in the process of being composed, or null if a new message is to be composed
     * @param store The message store for this application
     */ 
   
    public ComposeScreen(Message message, Store store, String email)
    {
        super(message, true, email);    
        _store = store;
        _messagesViewScreen = new MessagesViewScreen();     
       
        // If a new message is to be created, indicate this in the title
        if( message == null )
        {
            setTitle("New Message");
        }

        // Create and add menu items specific to the Compose action (addTo,
        // addBcc, addCc, etc...).
        AddHeaderFieldAction addToMenuItem = new AddHeaderFieldAction(Message.RecipientType.TO, "Add To: ", "To: ");
        AddHeaderFieldAction addCcMenuItem = new AddHeaderFieldAction(Message.RecipientType.CC, "Add Cc: ", "Cc: ");
        AddHeaderFieldAction addBccMenuItem = new AddHeaderFieldAction(Message.RecipientType.BCC, "Add Bcc: ", "Bcc: ");
       

        // MenuItem to save a message
        MenuItem saveMenuItem = new MenuItem(new StringProvider("Save Message"), 0x230020, 1);
        saveMenuItem.setCommand(new Command(new CommandHandler()
        {
            /**
             * @see net.rim.device.api.command.CommandHandler#execute(ReadOnlyCommandMetadata, Object)
             */
            public void execute(ReadOnlyCommandMetadata metadata, Object context)
            {       // If the save is completed, then discard this screen           
                if( onSave() )
                {
                    close();
                }
                else
                // If the message could not be saved, alert the user
                {
                    UiApplication.getUiApplication().invokeLater(new Runnable()
                    {
                        public void run()
                        {
                            Dialog.alert("Message could not be saved");
                        }
                    });
                }
            }
        }));
       
        // MenuItem to send a message
        MenuItem sendMenuItem = new MenuItem(new StringProvider("Send Message"), 0x230010, 0);
        sendMenuItem.setCommand(new Command(new CommandHandler()
        {
            /**
             * @see net.rim.device.api.command.CommandHandler#execute(ReadOnlyCommandMetadata, Object)
             */
            public void execute(ReadOnlyCommandMetadata metadata, Object context)
            {       // If the save is completed, then discard this screen           
                try
                {
                    _message = getMessage();
                    if( _message != null )
                    {
                        // Send the message
                        Transport.send(_message);
                       
                        UiApplication.getUiApplication().pushScreen(_messagesViewScreen);
                        close();
                    }
                 }
               catch( MessagingException e )
                {               
                    ViewStudentApp.errorDialog("Transport.send(Message) threw " + e.toString());
                }
            }
        }));

        addMenuItem(sendMenuItem);
        addMenuItem(saveMenuItem);
        addMenuItem(addToMenuItem);
        addMenuItem(addCcMenuItem);
        addMenuItem(addBccMenuItem);
    }

   
    /**
     * Overrides MessageScreen.displayMessage(). The message's 'sent' properties
     * are not displayed since the message is still in the process of editing.
     */
    void displayMessage(String email)
    {
        // If the message does not exist then compose a new message
        if( _message == null )
        {
            // Add a To line
            EditField toField = new EditField("To: ", email, 40, BasicEditField.FILTER_EMAIL);           
            addTextFieldToTableAndScreen(toField, Message.RecipientType.TO);

            // Add a subject line
            EditField subjectField = new EditField("Subject: ", "");
            addTextFieldToTableAndScreen(subjectField, SUBJECT);

            // Add a separator between the body and the headers
            add(new SeparatorField());

            // Add a body field
            EditField bodyField = new EditField();
            addTextFieldToTableAndScreen(bodyField, BODY);
        }
        else
        // The message exists so display it
        {
            displayHeader();
            add(new SeparatorField());
            displayMessageBody();
        }
    }   

   
    /**
     * Gets a message for sending or saving
     * @return A new message
     */
    Message getMessage()
    {
        // Find an outbox folder and use it to construct a new message     
        Folder outbox = _store.findFolder("Outbox")[ FIRST ];       
        Message message = new Message(outbox);

        // Add all the current headers
        for( int keyNo = 0; keyNo < HEADER_KEYS.length; keyNo++ )
        {
            Vector fieldsByType = (Vector) _fieldTable.get(HEADER_KEYS[ keyNo ]);

            if( fieldsByType != null )
            {
                // Build a vector of all the addresses
                Vector addressVector = new Vector();
                int size = fieldsByType.size();
                for( int fieldNo = 0; fieldNo < size; fieldNo++ )
                {
                    TextField addressField = (TextField) fieldsByType.elementAt(fieldNo);

                    // Try to create a new address object wrapping the email
                    // address and add it to the address vector.
                    try
                    {
                        addressVector.addElement(new Address(addressField.getText(), ""));
                    }
                    catch( AddressException e ) // Invalid address
                    {
                        ViewStudentApp.errorDialog("Address(String, String) threw " + e.toString());
                    }
                }

                // Dump the vector of addresses into an array to send the message
                Address[] addresses = new Address[ addressVector.size() ];
                addressVector.copyInto(addresses);

                // Try to add the addresses to the message's list of recipients
                try
                {
                    message.addRecipients(HEADER_KEYS[ keyNo ], addresses);
                }
                catch( MessagingException e )
                {                   
                    ViewStudentApp.errorDialog("Message#addRecipients(int, Address[]) threw " + e.toString());
                }
            }
        }

        // Add the subject
        Vector subjectFields = (Vector) _fieldTable.get(SUBJECT);
        TextField subjectField = (TextField) subjectFields.elementAt(FIRST);

        if( subjectFields != null && subjectFields.size() > 0 )
        {
            message.setSubject(subjectField.getText());
        }

        // Add the body by adding all the body fields into one multipart
        Vector bodyFields = (Vector) _fieldTable.get(BODY);
        if( bodyFields != null )
        {
            int size = bodyFields.size();
            Multipart content = new Multipart();
            for( int fieldNo = 0; fieldNo < size; fieldNo++ )
            {
                TextField body = (TextField) bodyFields.elementAt(fieldNo);
                content.addBodyPart(new TextBodyPart(content, body.getText()));
            }
            try
            {
                message.setContent(content);
            }
            catch( MessagingException e )
            {               
                ViewStudentApp.errorDialog("Message#setContent(Object) threw " + e.toString());
            }
        }
        else
        {
            ViewStudentApp.errorDialog("Error: no body field available");
            return null;
        }

        // Set the date
        message.setSentDate(Calendar.getInstance().getTime());

        return message;
    }

   
    /**
     * @see net.rim.device.api.ui.Screen#onSave()
     */
    protected boolean onSave()
    {
        // Save the message to the outbox
        try
        {
            Message newMessage = getMessage();
            if( newMessage != null )
            {
                // Retrieve an outbox to save the message in
                Store store = Session.waitForDefaultSession().getStore();
                Folder[] allOutboxFolders = store.list(Folder.OUTBOX);

                Folder outbox = null;               
                for( int i = allOutboxFolders.length - 1; i >= 0 ; --i )
                {
                    if( allOutboxFolders[ i ].getParent().getName().startsWith("Mailbox") )
                    {
                        outbox = allOutboxFolders[ i ];
                        break;
                    }
                }

                // Save the new message and replace the old one if it exists
                outbox.appendMessage(newMessage);
                if( _message != null )
                {
                    outbox.deleteMessage(_message, true);
                }
                _message = newMessage;

                // Set the status to composing and flag that it has been saved
                _message.setStatus(Message.Status.TX_COMPOSING, Message.Status.TX_ERROR);
                _message.setFlag(Message.Flag.SAVED, true);

                return true;
            }

            return false;
        }
        catch( MessagingException e )
        {
            return false;
        }
    }

   
    /**
     * Make "Send" the default menu item.
     *
     * @see net.rim.device.api.ui.container.MainScreen#makeMenu(Menu,int)
     */
    protected void makeMenu(Menu menu, int instance)
    {
        super.makeMenu(menu, instance);

      //  menu.setDefault(SEND_MENU_ITEM_INDEX);
    }

   
    /**
     * This class is responsible for adding the various header fields to the
     * compose screen (To, Bcc, CC).
     */
    private final class AddHeaderFieldAction extends MenuItem
    {
        private String _fieldLabelText;
        private int _headerType;

        /**
         * Constructs a menu item which adds a header of a specified type to the
         * compose screen.
         *
         * @param headerType One of the Message.RecipientType fields
         * @param menuItemText String to use for the menu item
         * @param fieldLabelText String to use for the label of this field
         */
        AddHeaderFieldAction(int headerType, String menuItemText, String fieldLabelText)
        {
            super(new StringProvider(menuItemText), 0x240010, 2);

            _fieldLabelText = fieldLabelText;
            _headerType = headerType;
            this.setCommand(new Command(new CommandHandler()
            {
                /**
                 * Adds a new header field to the message. The field is placed so that
                 * the header types are grouped together and the most recently added one
                 * is closest to the bottom.        
                 * @see net.rim.device.api.command.CommandHandler#execute(ReadOnlyCommandMetadata, Object)
                 */
                public void execute(ReadOnlyCommandMetadata metadata, Object context)
                {
                    EditField newField = new EditField(_fieldLabelText, "");

                    // Find out where the last field of this type was added to the
                    // screen.
                    Vector fieldsByType = (Vector) _fieldTable.get(_headerType);
                    int lastInsertedIndex;
                    if( fieldsByType == null )
                    {
                        // If a field of _headerType was not made yet, then create the
                        // vector which contains all of the fields of _headerType.
                        fieldsByType = new Vector();
                        _fieldTable.put(_headerType, fieldsByType);
                        lastInsertedIndex = getIndexForNewFieldType();
                    }
                    else
                    {
                        lastInsertedIndex = getIndexOfLastFieldOfType(_headerType);
                    }

                    // Add the new field to both the screen and the vector keeping track
                    // of all the fields of the same type.
                    ComposeScreen.this.insert(newField, lastInsertedIndex + 1);
                    fieldsByType.addElement(newField);
                    newField.setFocus();
                }
            }));
        }

       
        /**
         * Given the existing field type of this instance of the class,
         * determine where to place new header fields in the screen.
         *
         * @return The index at which to insert the new header field
         */
        private int getIndexForNewFieldType()
        {
            // Note: we don't handle TO here since there ALWAYS must be one TO
            // field.
            switch( _headerType )
            {
                // Find the last TO field and use the next index as the
                // insertion point.
                case Message.RecipientType.CC:
                    return getIndexOfLastFieldOfType(Message.RecipientType.TO);

                    // Try to find the last CC field and use it as the next
                    // insertion point. If no CC field exists then find the last
                    // TO field and use its index as the insertion point.
                case Message.RecipientType.BCC:
                    int index = getIndexOfLastFieldOfType(Message.RecipientType.CC);
                    if( index == -1 )
                        return getIndexOfLastFieldOfType(Message.RecipientType.TO);
                    return index;

                default:
                    throw new IllegalStateException("Mail Demo: Unrecognized recipient type");
            }
        }

       
        /**
         * Retrieves the index of the last field added of a specified type.
         *
         * @param type The type of header field to retrieve the last index of
         * @return The index of the most recently added field of the specified
         *         type, -1 if a field of the specified type has not been added
         *         yet.
         */
        private int getIndexOfLastFieldOfType(int type)
        {
            Vector fields = (Vector) _fieldTable.get(type);
            if( fields == null )
            {
                return -1;
            }

            Field field = (Field) fields.lastElement();

            return field.getIndex();
        }
    }
 }

MessageScreen.java

  package cs.ecl.team1.project;

  import net.rim.device.api.ui.container.*;
  import net.rim.device.api.ui.component.*;
  import net.rim.device.api.util.IntHashtable;
  import javax.microedition.pim.Contact;
  import java.util.*;

  import net.rim.blackberry.api.mail.*;

  /**
   * 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 = "<?>";

    protected final static int SUBJECT = 0;
    protected final static int BODY = 1;
    protected final static int INFO = 2;
    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 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(Message message, boolean editable, String email)
    {
        _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(email);
    }

   
    /**
     * Displays the message
     */
    void displayMessage(String email)
    {
        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
        ServiceConfiguration sc = _message.getFolder().getStore().getServiceConfiguration();
        EditField service = new EditField("Service: ", sc.getName(), MAX_CHARS, EditField.READONLY | EditField.NON_FOCUSABLE);
        addTextFieldToTableAndScreen(service, INFO);

        // Add the folder field
        EditField folder = new EditField("Folder: ", _message.getFolder().getName(), MAX_CHARS, EditField.READONLY
                | EditField.NON_FOCUSABLE);
        addTextFieldToTableAndScreen(folder, INFO);

        // Add the status of the message
        String statusString = getStatusString(_message);
        EditField status = new EditField("Status: ", statusString, MAX_CHARS, EditField.READONLY | EditField.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
        long editableStyle = _editable ? EditField.EDITABLE : EditField.READONLY;

        // Display the headers (To:, Cc:, Bcc:)
        for( int key = 0; key < HEADER_KEYS.length; key++ )
        {
            try
            {
                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.
                    EditField headerField = new EditField(HEADER_NAMES[ key ], name, EditField.DEFAULT_MAXCHARS, editableStyle);
                    headerField.setCookie(addresses[ index ]);

                    addTextFieldToTableAndScreen(headerField, HEADER_KEYS[ key ]);
                }
            }
            catch( MessagingException e )
            {               
                ViewStudentApp.errorDialog("Error: could not retrieve message header.");               
                close();
            }
        }

        // Display the 'Sent' date if it is available
        Date sent = _message.getSentDate();
        if( sent != null )
        {
            EditField sentDate = new EditField("Sent: ", Util.getDateAsString(sent), EditField.DEFAULT_MAXCHARS, EditField.READONLY
                    | EditField.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 subject field
        String subject = _message.getSubject();
        if( subject == null )
            subject = NO_SUBJECT;

        EditField subjectField = new EditField("Subject: ", subject, EditField.DEFAULT_MAXCHARS, editableStyle);
        addTextFieldToTableAndScreen(subjectField, SUBJECT);
    }

    /**
     * Displays the message body
     */
    protected void displayMessageBody()
    {
        // Retrieve the parent of the message body
        Object obj = _message.getContent();
        Multipart parent = null;
        if( obj instanceof MimeBodyPart || obj instanceof TextBodyPart )
        {
            BodyPart bp = (BodyPart) obj;
            parent = bp.getParent();
        }
        else
        {
            parent = (Multipart) obj;
        }

        // Display the message body
        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
        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
     * @param editable True if this multi-part is editable
     */
    protected void displayMultipart(Multipart multipart)
    {
        // This vector stores fields which are to be displayed only after all
        // of the body fields are displayed. (Attachments and Contacts).
        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++ )
        {
            BodyPart bodyPart = multipart.getBodyPart(index);

            // If this body part is text then display all of it
            if( bodyPart instanceof TextBodyPart )
            {
                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( Exception e )
                    {
                        ViewStudentApp.errorDialog("Transport.more(BodyPart, boolean) threw " + e.toString());
                    }
                }
                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 )
            {
                MimeBodyPart mimeBodyPart = (MimeBodyPart) bodyPart;

                // If the content is text then display it
                String contentType = mimeBodyPart.getContentType();
                if( contentType.startsWith(BodyPart.ContentType.TYPE_TEXT_HTML_STRING) )
                {
                    Object obj = mimeBodyPart.getContent();
                    if( obj != null )
                    {
                        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.
                    Object obj = mimeBodyPart.getContent();
                    if( obj instanceof Multipart )
                    {
                        Multipart childMultipart = (Multipart) obj;
                        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
                String contentType = bodyPart.getContentType();
                String name;
                if( bodyPart instanceof UnsupportedAttachmentPart )
                {
                    UnsupportedAttachmentPart uap = (UnsupportedAttachmentPart) bodyPart;
                    name = uap.getName();
                }
                else // The bodyPart is a SupportedAttachmentPart
                {
                    SupportedAttachmentPart sap = (SupportedAttachmentPart) bodyPart;
                    name = sap.getName();
                }

                // Format the content type and name to display and store
                // the field.
                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 )
            {
                Contact contact = (Contact) bodyPart.getContent();

                // Build the contact name
                StringBuffer sb = new StringBuffer("Contact: ");
                if( contact.countValues(Contact.NAME) > 0 )
                {
                    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
                    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
                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(Message message)
    {  
        StringBuffer statusStrBuffer = new StringBuffer();

        // Add any errors to the status string if it applies
        int status = message.getStatus();
        if( status == Message.Status.RX_ERROR )
        {
            statusStrBuffer.append("RX ERROR, ");
        }

        if( status == Message.Status.TX_GENERAL_FAILURE )
        {
            statusStrBuffer.append("RX ERROR, ");
        }

        if( status == Message.Status.TX_ERROR )
        {
            statusStrBuffer.append("TX ERROR, ");
        }

        // Use the flags to add any message statuses
        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
        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 if there are any characters to delete. Should be
        // either ", " or "  ".
        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(TextField field, 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();
    }
 }

Other classes stay unchanged.
9.4. Run the application, select a user from the list, and select Email Student from the menu:
BB email1.png BB email2.png
9.5. Type in the subject and some text in the body, and select Send Email from the menu:
BB email3.png
9.6. The messageList screen is displayed showing the created email. Select Folder View from the menu to see the Outbox folder:
BB email4.png BB email5.png