Home | History | Annotate | Download | only in eas
      1 package com.android.exchange.eas;
      2 
      3 import android.content.ContentUris;
      4 import android.content.ContentValues;
      5 import android.content.Context;
      6 import android.content.Entity;
      7 import android.provider.CalendarContract;
      8 import android.text.TextUtils;
      9 
     10 import com.android.emailcommon.mail.Address;
     11 import com.android.emailcommon.mail.MeetingInfo;
     12 import com.android.emailcommon.mail.PackedString;
     13 import com.android.emailcommon.provider.Account;
     14 import com.android.emailcommon.provider.EmailContent;
     15 import com.android.emailcommon.provider.Mailbox;
     16 import com.android.emailcommon.service.EmailServiceConstants;
     17 import com.android.emailcommon.utility.Utility;
     18 import com.android.exchange.CommandStatusException;
     19 import com.android.exchange.EasResponse;
     20 import com.android.exchange.adapter.MeetingResponseParser;
     21 import com.android.exchange.adapter.Serializer;
     22 import com.android.exchange.adapter.Tags;
     23 import com.android.exchange.utility.CalendarUtilities;
     24 import com.android.mail.providers.UIProvider;
     25 import com.android.mail.utils.LogUtils;
     26 
     27 import org.apache.http.HttpEntity;
     28 import org.apache.http.HttpStatus;
     29 
     30 import java.io.IOException;
     31 import java.security.cert.CertificateException;
     32 import java.text.ParseException;
     33 
     34 public class EasSendMeetingResponse extends EasOperation {
     35     public final static int RESULT_OK = 1;
     36 
     37     private final static String TAG = LogUtils.TAG;
     38 
     39     /** Projection for getting the server id for a mailbox. */
     40     private static final String[] MAILBOX_SERVER_ID_PROJECTION = {
     41             EmailContent.MailboxColumns.SERVER_ID };
     42     private static final int MAILBOX_SERVER_ID_COLUMN = 0;
     43 
     44     /** EAS protocol values for UserResponse. */
     45     private static final int EAS_RESPOND_ACCEPT = 1;
     46     private static final int EAS_RESPOND_TENTATIVE = 2;
     47     private static final int EAS_RESPOND_DECLINE = 3;
     48     /** Value to use if we get a UI response value that we can't handle. */
     49     private static final int EAS_RESPOND_UNKNOWN = -1;
     50 
     51     private final EmailContent.Message mMessage;
     52     private final int mMeetingResponse;
     53     private int mEasResponse;
     54 
     55     public EasSendMeetingResponse(final Context context, final long accountId,
     56                                   final EmailContent.Message message, final int meetingResponse) {
     57         super(context, accountId);
     58         mMessage = message;
     59         mMeetingResponse = meetingResponse;
     60     }
     61 
     62     /**
     63      * Translate from {@link com.android.mail.providers.UIProvider.MessageOperations} constants to
     64      * EAS values. They're currently identical but this is for future-proofing.
     65      * @param messageOperationResponse The response value that came from the UI.
     66      * @return The EAS protocol value to use.
     67      */
     68     private static int messageOperationResponseToUserResponse(final int messageOperationResponse) {
     69         switch (messageOperationResponse) {
     70             case UIProvider.MessageOperations.RESPOND_ACCEPT:
     71                 return EAS_RESPOND_ACCEPT;
     72             case UIProvider.MessageOperations.RESPOND_TENTATIVE:
     73                 return EAS_RESPOND_TENTATIVE;
     74             case UIProvider.MessageOperations.RESPOND_DECLINE:
     75                 return EAS_RESPOND_DECLINE;
     76         }
     77         return EAS_RESPOND_UNKNOWN;
     78     }
     79 
     80     @Override
     81     protected String getCommand() {
     82         return "MeetingResponse";
     83     }
     84 
     85     @Override
     86     protected HttpEntity getRequestEntity() throws IOException {
     87         mEasResponse = messageOperationResponseToUserResponse(mMeetingResponse);
     88         if (mEasResponse == EAS_RESPOND_UNKNOWN) {
     89             LogUtils.e(TAG, "Bad response value: %d", mMeetingResponse);
     90             return null;
     91         }
     92         final Account account = Account.restoreAccountWithId(mContext, mMessage.mAccountKey);
     93         if (account == null) {
     94             LogUtils.e(TAG, "Could not load account %d for message %d", mMessage.mAccountKey,
     95                     mMessage.mId);
     96             return null;
     97         }
     98         final String mailboxServerId = Utility.getFirstRowString(mContext,
     99                 ContentUris.withAppendedId(Mailbox.CONTENT_URI, mMessage.mMailboxKey),
    100                 MAILBOX_SERVER_ID_PROJECTION, null, null, null, MAILBOX_SERVER_ID_COLUMN);
    101         if (mailboxServerId == null) {
    102             LogUtils.e(TAG, "Could not load mailbox %d for message %d", mMessage.mMailboxKey,
    103                     mMessage.mId);
    104             return null;
    105         }
    106         final HttpEntity response;
    107         try {
    108              response = makeResponse(mMessage, mailboxServerId, mEasResponse);
    109         } catch (CertificateException e) {
    110             LogUtils.e(TAG, e, "CertficateException");
    111             return null;
    112         }
    113         return response;
    114     }
    115 
    116     private HttpEntity makeResponse(final EmailContent.Message msg, final String mailboxServerId,
    117                                     final int easResponse)
    118             throws IOException, CertificateException {
    119         final Serializer s = new Serializer();
    120         s.start(Tags.MREQ_MEETING_RESPONSE).start(Tags.MREQ_REQUEST);
    121         s.data(Tags.MREQ_USER_RESPONSE, Integer.toString(easResponse));
    122         s.data(Tags.MREQ_COLLECTION_ID, mailboxServerId);
    123         s.data(Tags.MREQ_REQ_ID, msg.mServerId);
    124         s.end().end().done();
    125         return makeEntity(s);
    126     }
    127 
    128     @Override
    129     protected int handleResponse(final EasResponse response)
    130             throws IOException, CommandStatusException {
    131         final int status = response.getStatus();
    132         if (status == HttpStatus.SC_OK) {
    133             if (!response.isEmpty()) {
    134                 // TODO: Improve the parsing to actually handle error statuses.
    135                 new MeetingResponseParser(response.getInputStream()).parse();
    136 
    137                 if (mMessage.mMeetingInfo != null) {
    138                     final PackedString meetingInfo = new PackedString(mMessage.mMeetingInfo);
    139                     final String responseRequested =
    140                             meetingInfo.get(MeetingInfo.MEETING_RESPONSE_REQUESTED);
    141                     // If there's no tag, or a non-zero tag, we send the response mail
    142                     if (!"0".equals(responseRequested)) {
    143                         sendMeetingResponseMail(meetingInfo, mEasResponse);
    144                     }
    145                 }
    146             }
    147         } else if (response.isAuthError()) {
    148             // TODO: Handle this gracefully.
    149             //throw new EasAuthenticationException();
    150         } else {
    151             LogUtils.e(TAG, "Meeting response request failed, code: %d", status);
    152             throw new IOException();
    153         }
    154         return RESULT_OK;
    155     }
    156 
    157 
    158     private void sendMeetingResponseMail(final PackedString meetingInfo, final int response) {
    159         // This will come as "First Last" <box (at) server.blah>, so we use Address to
    160         // parse it into parts; we only need the email address part for the ics file
    161         final Address[] addrs = Address.parse(meetingInfo.get(MeetingInfo.MEETING_ORGANIZER_EMAIL));
    162         // It shouldn't be possible, but handle it anyway
    163         if (addrs.length != 1) return;
    164         final String organizerEmail = addrs[0].getAddress();
    165 
    166         final String dtStamp = meetingInfo.get(MeetingInfo.MEETING_DTSTAMP);
    167         final String dtStart = meetingInfo.get(MeetingInfo.MEETING_DTSTART);
    168         final String dtEnd = meetingInfo.get(MeetingInfo.MEETING_DTEND);
    169         if (TextUtils.isEmpty(dtStamp) || TextUtils.isEmpty(dtStart) || TextUtils.isEmpty(dtEnd)) {
    170             LogUtils.w(TAG, "blank dtStamp %s dtStart %s dtEnd %s", dtStamp, dtStart, dtEnd);
    171             return;
    172         }
    173 
    174         // What we're doing here is to create an Entity that looks like an Event as it would be
    175         // stored by CalendarProvider
    176         final ContentValues entityValues = new ContentValues(6);
    177         final Entity entity = new Entity(entityValues);
    178 
    179         // Fill in times, location, title, and organizer
    180         entityValues.put("DTSTAMP",
    181                 CalendarUtilities.convertEmailDateTimeToCalendarDateTime(dtStamp));
    182         try {
    183             entityValues.put(CalendarContract.Events.DTSTART,
    184                     Utility.parseEmailDateTimeToMillis(dtStart));
    185             entityValues.put(CalendarContract.Events.DTEND,
    186                     Utility.parseEmailDateTimeToMillis(dtEnd));
    187         } catch (ParseException e) {
    188              LogUtils.w(TAG, "Parse error for DTSTART/DTEND tags.", e);
    189         }
    190         entityValues.put(CalendarContract.Events.EVENT_LOCATION,
    191                 meetingInfo.get(MeetingInfo.MEETING_LOCATION));
    192         entityValues.put(CalendarContract.Events.TITLE, meetingInfo.get(MeetingInfo.MEETING_TITLE));
    193         entityValues.put(CalendarContract.Events.TITLE, meetingInfo.get(MeetingInfo.MEETING_TITLE));
    194         entityValues.put(CalendarContract.Events.ORGANIZER, organizerEmail);
    195 
    196         // Add ourselves as an attendee, using our account email address
    197         final ContentValues attendeeValues = new ContentValues(2);
    198         attendeeValues.put(CalendarContract.Attendees.ATTENDEE_RELATIONSHIP,
    199                 CalendarContract.Attendees.RELATIONSHIP_ATTENDEE);
    200         attendeeValues.put(CalendarContract.Attendees.ATTENDEE_EMAIL, mAccount.mEmailAddress);
    201         entity.addSubValue(CalendarContract.Attendees.CONTENT_URI, attendeeValues);
    202 
    203         // Add the organizer
    204         final ContentValues organizerValues = new ContentValues(2);
    205         organizerValues.put(CalendarContract.Attendees.ATTENDEE_RELATIONSHIP,
    206                 CalendarContract.Attendees.RELATIONSHIP_ORGANIZER);
    207         organizerValues.put(CalendarContract.Attendees.ATTENDEE_EMAIL, organizerEmail);
    208         entity.addSubValue(CalendarContract.Attendees.CONTENT_URI, organizerValues);
    209 
    210         // Create a message from the Entity we've built.  The message will have fields like
    211         // to, subject, date, and text filled in.  There will also be an "inline" attachment
    212         // which is in iCalendar format
    213         final int flag;
    214         switch(response) {
    215             case EmailServiceConstants.MEETING_REQUEST_ACCEPTED:
    216                 flag = EmailContent.Message.FLAG_OUTGOING_MEETING_ACCEPT;
    217                 break;
    218             case EmailServiceConstants.MEETING_REQUEST_DECLINED:
    219                 flag = EmailContent.Message.FLAG_OUTGOING_MEETING_DECLINE;
    220                 break;
    221             case EmailServiceConstants.MEETING_REQUEST_TENTATIVE:
    222             default:
    223                 flag = EmailContent.Message.FLAG_OUTGOING_MEETING_TENTATIVE;
    224                 break;
    225         }
    226         final EmailContent.Message outgoingMsg =
    227                 CalendarUtilities.createMessageForEntity(mContext, entity, flag,
    228                         meetingInfo.get(MeetingInfo.MEETING_UID), mAccount);
    229         // Assuming we got a message back (we might not if the event has been deleted), send it
    230         if (outgoingMsg != null) {
    231             sendMessage(mAccount, outgoingMsg);
    232         }
    233     }
    234 }
    235