• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
EasSendMeetingResponse(final Context context, final long accountId, final EmailContent.Message message, final int meetingResponse)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      */
messageOperationResponseToUserResponse(final int messageOperationResponse)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
getCommand()81     protected String getCommand() {
82         return "MeetingResponse";
83     }
84 
85     @Override
getRequestEntity()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 
makeResponse(final EmailContent.Message msg, final String mailboxServerId, final int easResponse)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
handleResponse(final EasResponse response)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 
sendMeetingResponseMail(final PackedString meetingInfo, final int response)158     private void sendMeetingResponseMail(final PackedString meetingInfo, final int response) {
159         // This will come as "First Last" <box@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