Merge "Make sendMeetingResponse and Autodiscover an EasOperation" into ub-mail-master
diff --git a/src/com/android/exchange/service/EasAutoDiscover.java b/src/com/android/exchange/eas/EasAutoDiscover.java
similarity index 61%
rename from src/com/android/exchange/service/EasAutoDiscover.java
rename to src/com/android/exchange/eas/EasAutoDiscover.java
index 026e227..fd30087 100644
--- a/src/com/android/exchange/service/EasAutoDiscover.java
+++ b/src/com/android/exchange/eas/EasAutoDiscover.java
@@ -1,20 +1,20 @@
-package com.android.exchange.service;
+package com.android.exchange.eas;
 
 import android.content.Context;
 import android.net.Uri;
 import android.os.Bundle;
 import android.util.Xml;
 
-import com.android.emailcommon.mail.MessagingException;
 import com.android.emailcommon.provider.Account;
 import com.android.emailcommon.provider.HostAuth;
 import com.android.emailcommon.service.EmailServiceProxy;
+import com.android.exchange.CommandStatusException;
 import com.android.exchange.Eas;
 import com.android.exchange.EasResponse;
 import com.android.mail.utils.LogUtils;
 
+import org.apache.http.HttpEntity;
 import org.apache.http.HttpStatus;
-import org.apache.http.client.methods.HttpPost;
 import org.apache.http.entity.StringEntity;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -23,18 +23,19 @@
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
-import java.net.URI;
-import java.security.cert.CertificateException;
 
-/**
- * Performs Autodiscover for Exchange servers. This feature tries to find all the configuration
- * options needed based on just a username and password.
- */
-public class EasAutoDiscover extends EasServerConnection {
-    private static final String TAG = Eas.LOG_TAG;
+public class EasAutoDiscover extends EasOperation {
+
+    public final static int RESULT_OK = 1;
+    public final static int RESULT_SC_UNAUTHORIZED = RESULT_OP_SPECIFIC_ERROR_RESULT - 0;
+    public final static int RESULT_REDIRECT = RESULT_OP_SPECIFIC_ERROR_RESULT - 1;
+    public final static int RESULT_BAD_RESPONSE = RESULT_OP_SPECIFIC_ERROR_RESULT - 2;
+    public final static int RESULT_FATAL_SERVER_ERROR = RESULT_OP_SPECIFIC_ERROR_RESULT - 3;
+
+    private final static String TAG = LogUtils.TAG;
 
     private static final String AUTO_DISCOVER_SCHEMA_PREFIX =
-        "http://schemas.microsoft.com/exchange/autodiscover/mobilesync/";
+            "http://schemas.microsoft.com/exchange/autodiscover/mobilesync/";
     private static final String AUTO_DISCOVER_PAGE = "/autodiscover/autodiscover.xml";
 
     // Set of string constants for parsing the autodiscover response.
@@ -53,95 +54,56 @@
     private static final String ELEMENT_NAME_RESPONSE = "Response";
     private static final String ELEMENT_NAME_AUTODISCOVER = "Autodiscover";
 
-    public EasAutoDiscover(final Context context, final String username, final String password) {
-        super(context, new Account(), new HostAuth());
-        mHostAuth.mLogin = username;
-        mHostAuth.mPassword = password;
-        mHostAuth.mFlags = HostAuth.FLAG_AUTHENTICATE | HostAuth.FLAG_SSL;
+    private final String mUri;
+    private final String mUsername;
+    private final String mPassword;
+    private HostAuth mHostAuth;
+    private String mRedirectUri;
+
+    public EasAutoDiscover(final Context context, final String uri, final String username,
+                           final String password) {
+        // We don't actually need an account or a hostAuth, but the EasServerConnection requires
+        // one. Just create dummy values.
+        super(context, -1);
+        mUri = uri;
+        mUsername = username;
+        mPassword = password;
+        mHostAuth = new HostAuth();
+        mHostAuth.mLogin = mUsername;
+        mHostAuth.mPassword = mPassword;
         mHostAuth.mPort = 443;
+        mHostAuth.mProtocol = Eas.PROTOCOL;
+        mHostAuth.mFlags = HostAuth.FLAG_SSL | HostAuth.FLAG_AUTHENTICATE;
+        setAccount(new Account(), mHostAuth);
     }
 
-    /**
-     * Do all the work of autodiscovery.
-     * @return A {@link Bundle} with the host information if autodiscovery succeeded. If we failed
-     *     due to an authentication failure, we return a {@link Bundle} with no host info but with
-     *     an appropriate error code. Otherwise, we return null.
-     */
-    public Bundle doAutodiscover() {
-        final String domain = getDomain();
-        if (domain == null) {
-            return null;
-        }
-
-        final StringEntity entity = buildRequestEntity();
-        if (entity == null) {
-            return null;
-        }
-        try {
-            final HttpPost post = makePost("https://" + domain + AUTO_DISCOVER_PAGE, entity,
-                    "text/xml", false);
-            final EasResponse resp = getResponse(post, domain);
-            if (resp == null) {
-                return null;
-            }
-
-            try {
-                // resp is either an authentication error, or a good response.
-                final int code = resp.getStatus();
-                if (code == HttpStatus.SC_UNAUTHORIZED) {
-                    final Bundle bundle = new Bundle(1);
-                    bundle.putInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE,
-                            MessagingException.AUTODISCOVER_AUTHENTICATION_FAILED);
-                    return bundle;
-                } else {
-                    final HostAuth hostAuth = parseAutodiscover(resp);
-                    if (hostAuth != null) {
-                        // Fill in the rest of the HostAuth
-                        // We use the user name and password that were successful during
-                        // the autodiscover process
-                        hostAuth.mLogin = mHostAuth.mLogin;
-                        hostAuth.mPassword = mHostAuth.mPassword;
-                        // Note: there is no way we can auto-discover the proper client
-                        // SSL certificate to use, if one is needed.
-                        hostAuth.mPort = 443;
-                        hostAuth.mProtocol = Eas.PROTOCOL;
-                        hostAuth.mFlags = HostAuth.FLAG_SSL | HostAuth.FLAG_AUTHENTICATE;
-                        final Bundle bundle = new Bundle(2);
-                        bundle.putParcelable(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_HOST_AUTH,
-                                hostAuth);
-                        bundle.putInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE,
-                                MessagingException.NO_ERROR);
-                        return bundle;
-                    }
-                }
-            } finally {
-                resp.close();
-            }
-        } catch (final IllegalArgumentException e) {
-            // This happens when the domain is malformatted.
-            // TODO: Fix sanitizing of the domain -- we try to in UI but apparently not correctly.
-            LogUtils.e(TAG, "ISE with domain: %s", domain);
-        }
-        return null;
+    protected String getRequestUri() {
+        return mUri;
     }
 
-    /**
-     * Get the domain of our account.
-     * @return The domain of the email address.
-     */
-    private String getDomain() {
-        final int amp = mHostAuth.mLogin.indexOf('@');
+    public static String getDomain(final String login) {
+        final int amp = login.indexOf('@');
         if (amp < 0) {
             return null;
         }
-        return mHostAuth.mLogin.substring(amp + 1);
+        return login.substring(amp + 1);
     }
 
-    /**
-     * Create the payload of the request.
-     * @return A {@link StringEntity} for the request XML.
-     */
-    private StringEntity buildRequestEntity() {
+    public static String createUri(final String domain) {
+        return "https://" + domain + AUTO_DISCOVER_PAGE;
+    }
+
+    public static String createAlternateUri(final String domain) {
+        return "https://autodiscover." + domain + AUTO_DISCOVER_PAGE;
+    }
+
+    @Override
+    protected String getCommand() {
+        return null;
+    }
+
+    @Override
+    protected HttpEntity getRequestEntity() throws IOException, MessageInvalidException {
         try {
             final XmlSerializer s = Xml.newSerializer();
             final ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
@@ -150,7 +112,7 @@
             s.startTag(null, "Autodiscover");
             s.attribute(null, "xmlns", AUTO_DISCOVER_SCHEMA_PREFIX + "requestschema/2006");
             s.startTag(null, "Request");
-            s.startTag(null, "EMailAddress").text(mHostAuth.mLogin).endTag(null, "EMailAddress");
+            s.startTag(null, "EMailAddress").text(mUsername).endTag(null, "EMailAddress");
             s.startTag(null, "AcceptableResponseSchema");
             s.text(AUTO_DISCOVER_SCHEMA_PREFIX + "responseschema/2006");
             s.endTag(null, "AcceptableResponseSchema");
@@ -163,76 +125,65 @@
         } catch (final IllegalArgumentException e) {
         } catch (final IllegalStateException e) {
         }
-
         return null;
     }
 
-    /**
-     * Perform all requests necessary and get the server response. If the post fails or is
-     * redirected, we alter the post and retry.
-     * @param post The initial {@link HttpPost} for this request.
-     * @param domain The domain for our account.
-     * @return If this request succeeded or has an unrecoverable authentication error, an
-     *     {@link EasResponse} with the details. For other errors, we return null.
-     */
-    private EasResponse getResponse(final HttpPost post, final String domain) {
-        EasResponse resp = doPost(post, true);
-        if (resp == null) {
-            LogUtils.d(TAG, "Error in autodiscover, trying aternate address");
-            post.setURI(URI.create("https://autodiscover." + domain + AUTO_DISCOVER_PAGE));
-            resp = doPost(post, true);
-        }
-        return resp;
+    public String getRedirectUri() {
+        return mRedirectUri;
     }
 
-    /**
-     * Perform one attempt to get autodiscover information. Redirection and some authentication
-     * errors are handled by recursively calls with modified host information.
-     * @param post The {@link HttpPost} for this request.
-     * @param canRetry Whether we can retry after an authentication failure.
-     * @return If this request succeeded or has an unrecoverable authentication error, an
-     *     {@link EasResponse} with the details. For other errors, we return null.
-     */
-    private EasResponse doPost(final HttpPost post, final boolean canRetry) {
-        final EasResponse resp;
-        try {
-            resp = executePost(post);
-        } catch (final IOException e) {
-            return null;
-        } catch (final CertificateException e) {
-            // TODO: Raise this error to the user or something
-            return null;
-        }
+    @Override
+    protected int handleResponse(final EasResponse response) throws
+            IOException, CommandStatusException {
+        // resp is either an authentication error, or a good response.
+        final int code = response.getStatus();
 
-        final int code = resp.getStatus();
-
-        if (resp.isRedirectError()) {
-            final String loc = resp.getRedirectAddress();
+        if (response.isRedirectError()) {
+            final String loc = response.getRedirectAddress();
             if (loc != null && loc.startsWith("http")) {
                 LogUtils.d(TAG, "Posting autodiscover to redirect: " + loc);
-                redirectHostAuth(loc);
-                post.setURI(URI.create(loc));
-                return doPost(post, canRetry);
+                mRedirectUri = loc;
+                return RESULT_REDIRECT;
+            } else {
+                LogUtils.w(TAG, "Invalid redirect %s", loc);
+                return RESULT_FATAL_SERVER_ERROR;
             }
-            return null;
         }
 
         if (code == HttpStatus.SC_UNAUTHORIZED) {
-            if (canRetry && mHostAuth.mLogin.contains("@")) {
-                // Try again using the bare user name
-                final int atSignIndex = mHostAuth.mLogin.indexOf('@');
-                mHostAuth.mLogin = mHostAuth.mLogin.substring(0, atSignIndex);
-                LogUtils.d(TAG, "401 received; trying username: %s", mHostAuth.mLogin);
-                resetAuthorization(post);
-                return doPost(post, false);
-            }
+            LogUtils.w(TAG, "Autodiscover received SC_UNAUTHORIZED");
+            return RESULT_SC_UNAUTHORIZED;
         } else if (code != HttpStatus.SC_OK) {
             // We'll try the next address if this doesn't work
             LogUtils.d(TAG, "Bad response code when posting autodiscover: %d", code);
-            return null;
+            return RESULT_BAD_RESPONSE;
+        } else {
+            mHostAuth = parseAutodiscover(response);
+            if (mHostAuth != null) {
+                // Fill in the rest of the HostAuth
+                // We use the user name and password that were successful during
+                // the autodiscover process
+                mHostAuth.mLogin = mUsername;
+                mHostAuth.mPassword = mPassword;
+                // Note: there is no way we can auto-discover the proper client
+                // SSL certificate to use, if one is needed.
+                mHostAuth.mPort = 443;
+                mHostAuth.mProtocol = Eas.PROTOCOL;
+                mHostAuth.mFlags = HostAuth.FLAG_SSL | HostAuth.FLAG_AUTHENTICATE;
+                return RESULT_OK;
+            } else {
+                return RESULT_HARD_DATA_FAILURE;
+            }
         }
+    }
 
-        return resp;
+    public Bundle getResultBundle() {
+        final Bundle bundle = new Bundle(2);
+        bundle.putParcelable(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_HOST_AUTH,
+                mHostAuth);
+        bundle.putInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE,
+                RESULT_OK);
+        return bundle;
     }
 
     /**
diff --git a/src/com/android/exchange/eas/EasOperation.java b/src/com/android/exchange/eas/EasOperation.java
index aa0ad14..259a540 100644
--- a/src/com/android/exchange/eas/EasOperation.java
+++ b/src/com/android/exchange/eas/EasOperation.java
@@ -41,6 +41,7 @@
 import com.android.exchange.service.EasServerConnection;
 import com.android.mail.providers.UIProvider;
 import com.android.mail.utils.LogUtils;
+import com.google.common.annotations.VisibleForTesting;
 
 import org.apache.http.HttpEntity;
 import org.apache.http.client.methods.HttpUriRequest;
@@ -160,6 +161,11 @@
         }
     }
 
+    @VisibleForTesting
+    public void replaceEasServerConnection(EasServerConnection connection) {
+        mConnection = connection;
+    }
+
     /**
      * Constructor which defers loading of account and connection info.
      * @param context
@@ -230,6 +236,19 @@
         return (mAccount != null);
     }
 
+    /**
+     * Sets the account. This is for use in cases where the account is not available upon
+     * construction. This will also create the EasServerConnection.
+     * @param account
+     * @param hostAuth
+     */
+    protected void setAccount(final Account account, final HostAuth hostAuth) {
+        mAccount = account;
+        if (mAccount != null) {
+            mConnection = new EasServerConnection(mContext, mAccount, hostAuth);
+        }
+    }
+
     public final long getAccountId() {
         return mAccountId;
     }
@@ -466,8 +485,10 @@
         if (requestUri == null) {
             return mConnection.makeOptions();
         }
-        return mConnection.makePost(requestUri, getRequestEntity(),
+
+        HttpUriRequest req = mConnection.makePost(requestUri, getRequestEntity(),
                 getRequestContentType(), addPolicyKeyHeaderToRequest());
+        return req;
     }
 
     /**
@@ -488,7 +509,7 @@
      * Build the {@link HttpEntity} which is used to construct the POST. Typically this function
      * will build the Exchange request using a {@link Serializer} and then call {@link #makeEntity}.
      * If the subclass is not using a POST, then it should override this to return null.
-     * @return The {@link HttpEntity} to pass to {@link EasServerConnection#makePost}.
+     * @return The {@link HttpEntity} to pass to {@link com.android.exchange.service.EasServerConnection#makePost}.
      * @throws IOException
      */
     protected abstract HttpEntity getRequestEntity() throws IOException, MessageInvalidException;
diff --git a/src/com/android/exchange/eas/EasSendMeetingResponse.java b/src/com/android/exchange/eas/EasSendMeetingResponse.java
new file mode 100644
index 0000000..7897bb5
--- /dev/null
+++ b/src/com/android/exchange/eas/EasSendMeetingResponse.java
@@ -0,0 +1,234 @@
+package com.android.exchange.eas;
+
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Entity;
+import android.provider.CalendarContract;
+import android.text.TextUtils;
+
+import com.android.emailcommon.mail.Address;
+import com.android.emailcommon.mail.MeetingInfo;
+import com.android.emailcommon.mail.PackedString;
+import com.android.emailcommon.provider.Account;
+import com.android.emailcommon.provider.EmailContent;
+import com.android.emailcommon.provider.Mailbox;
+import com.android.emailcommon.service.EmailServiceConstants;
+import com.android.emailcommon.utility.Utility;
+import com.android.exchange.CommandStatusException;
+import com.android.exchange.EasResponse;
+import com.android.exchange.adapter.MeetingResponseParser;
+import com.android.exchange.adapter.Serializer;
+import com.android.exchange.adapter.Tags;
+import com.android.exchange.utility.CalendarUtilities;
+import com.android.mail.providers.UIProvider;
+import com.android.mail.utils.LogUtils;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpStatus;
+
+import java.io.IOException;
+import java.security.cert.CertificateException;
+import java.text.ParseException;
+
+public class EasSendMeetingResponse extends EasOperation {
+    public final static int RESULT_OK = 1;
+
+    private final static String TAG = LogUtils.TAG;
+
+    /** Projection for getting the server id for a mailbox. */
+    private static final String[] MAILBOX_SERVER_ID_PROJECTION = {
+            EmailContent.MailboxColumns.SERVER_ID };
+    private static final int MAILBOX_SERVER_ID_COLUMN = 0;
+
+    /** EAS protocol values for UserResponse. */
+    private static final int EAS_RESPOND_ACCEPT = 1;
+    private static final int EAS_RESPOND_TENTATIVE = 2;
+    private static final int EAS_RESPOND_DECLINE = 3;
+    /** Value to use if we get a UI response value that we can't handle. */
+    private static final int EAS_RESPOND_UNKNOWN = -1;
+
+    private final EmailContent.Message mMessage;
+    private final int mMeetingResponse;
+    private int mEasResponse;
+
+    public EasSendMeetingResponse(final Context context, final long accountId,
+                                  final EmailContent.Message message, final int meetingResponse) {
+        super(context, accountId);
+        mMessage = message;
+        mMeetingResponse = meetingResponse;
+    }
+
+    /**
+     * Translate from {@link com.android.mail.providers.UIProvider.MessageOperations} constants to
+     * EAS values. They're currently identical but this is for future-proofing.
+     * @param messageOperationResponse The response value that came from the UI.
+     * @return The EAS protocol value to use.
+     */
+    private static int messageOperationResponseToUserResponse(final int messageOperationResponse) {
+        switch (messageOperationResponse) {
+            case UIProvider.MessageOperations.RESPOND_ACCEPT:
+                return EAS_RESPOND_ACCEPT;
+            case UIProvider.MessageOperations.RESPOND_TENTATIVE:
+                return EAS_RESPOND_TENTATIVE;
+            case UIProvider.MessageOperations.RESPOND_DECLINE:
+                return EAS_RESPOND_DECLINE;
+        }
+        return EAS_RESPOND_UNKNOWN;
+    }
+
+    @Override
+    protected String getCommand() {
+        return "MeetingResponse";
+    }
+
+    @Override
+    protected HttpEntity getRequestEntity() throws IOException {
+        mEasResponse = messageOperationResponseToUserResponse(mMeetingResponse);
+        if (mEasResponse == EAS_RESPOND_UNKNOWN) {
+            LogUtils.e(TAG, "Bad response value: %d", mMeetingResponse);
+            return null;
+        }
+        final Account account = Account.restoreAccountWithId(mContext, mMessage.mAccountKey);
+        if (account == null) {
+            LogUtils.e(TAG, "Could not load account %d for message %d", mMessage.mAccountKey,
+                    mMessage.mId);
+            return null;
+        }
+        final String mailboxServerId = Utility.getFirstRowString(mContext,
+                ContentUris.withAppendedId(Mailbox.CONTENT_URI, mMessage.mMailboxKey),
+                MAILBOX_SERVER_ID_PROJECTION, null, null, null, MAILBOX_SERVER_ID_COLUMN);
+        if (mailboxServerId == null) {
+            LogUtils.e(TAG, "Could not load mailbox %d for message %d", mMessage.mMailboxKey,
+                    mMessage.mId);
+            return null;
+        }
+        final HttpEntity response;
+        try {
+             response = makeResponse(mMessage, mailboxServerId, mEasResponse);
+        } catch (CertificateException e) {
+            LogUtils.e(TAG, e, "CertficateException");
+            return null;
+        }
+        return response;
+    }
+
+    private HttpEntity makeResponse(final EmailContent.Message msg, final String mailboxServerId,
+                                    final int easResponse)
+            throws IOException, CertificateException {
+        final Serializer s = new Serializer();
+        s.start(Tags.MREQ_MEETING_RESPONSE).start(Tags.MREQ_REQUEST);
+        s.data(Tags.MREQ_USER_RESPONSE, Integer.toString(easResponse));
+        s.data(Tags.MREQ_COLLECTION_ID, mailboxServerId);
+        s.data(Tags.MREQ_REQ_ID, msg.mServerId);
+        s.end().end().done();
+        return makeEntity(s);
+    }
+
+    @Override
+    protected int handleResponse(final EasResponse response)
+            throws IOException, CommandStatusException {
+        final int status = response.getStatus();
+        if (status == HttpStatus.SC_OK) {
+            if (!response.isEmpty()) {
+                // TODO: Improve the parsing to actually handle error statuses.
+                new MeetingResponseParser(response.getInputStream()).parse();
+
+                if (mMessage.mMeetingInfo != null) {
+                    final PackedString meetingInfo = new PackedString(mMessage.mMeetingInfo);
+                    final String responseRequested =
+                            meetingInfo.get(MeetingInfo.MEETING_RESPONSE_REQUESTED);
+                    // If there's no tag, or a non-zero tag, we send the response mail
+                    if (!"0".equals(responseRequested)) {
+                        sendMeetingResponseMail(meetingInfo, mEasResponse);
+                    }
+                }
+            }
+        } else if (response.isAuthError()) {
+            // TODO: Handle this gracefully.
+            //throw new EasAuthenticationException();
+        } else {
+            LogUtils.e(TAG, "Meeting response request failed, code: %d", status);
+            throw new IOException();
+        }
+        return RESULT_OK;
+    }
+
+
+    private void sendMeetingResponseMail(final PackedString meetingInfo, final int response) {
+        // This will come as "First Last" <box@server.blah>, so we use Address to
+        // parse it into parts; we only need the email address part for the ics file
+        final Address[] addrs = Address.parse(meetingInfo.get(MeetingInfo.MEETING_ORGANIZER_EMAIL));
+        // It shouldn't be possible, but handle it anyway
+        if (addrs.length != 1) return;
+        final String organizerEmail = addrs[0].getAddress();
+
+        final String dtStamp = meetingInfo.get(MeetingInfo.MEETING_DTSTAMP);
+        final String dtStart = meetingInfo.get(MeetingInfo.MEETING_DTSTART);
+        final String dtEnd = meetingInfo.get(MeetingInfo.MEETING_DTEND);
+        if (TextUtils.isEmpty(dtStamp) || TextUtils.isEmpty(dtStart) || TextUtils.isEmpty(dtEnd)) {
+            LogUtils.w(TAG, "blank dtStamp %s dtStart %s dtEnd %s", dtStamp, dtStart, dtEnd);
+            return;
+        }
+
+        // What we're doing here is to create an Entity that looks like an Event as it would be
+        // stored by CalendarProvider
+        final ContentValues entityValues = new ContentValues(6);
+        final Entity entity = new Entity(entityValues);
+
+        // Fill in times, location, title, and organizer
+        entityValues.put("DTSTAMP",
+                CalendarUtilities.convertEmailDateTimeToCalendarDateTime(dtStamp));
+        try {
+            entityValues.put(CalendarContract.Events.DTSTART,
+                    Utility.parseEmailDateTimeToMillis(dtStart));
+            entityValues.put(CalendarContract.Events.DTEND,
+                    Utility.parseEmailDateTimeToMillis(dtEnd));
+        } catch (ParseException e) {
+             LogUtils.w(TAG, "Parse error for DTSTART/DTEND tags.", e);
+        }
+        entityValues.put(CalendarContract.Events.EVENT_LOCATION,
+                meetingInfo.get(MeetingInfo.MEETING_LOCATION));
+        entityValues.put(CalendarContract.Events.TITLE, meetingInfo.get(MeetingInfo.MEETING_TITLE));
+        entityValues.put(CalendarContract.Events.TITLE, meetingInfo.get(MeetingInfo.MEETING_TITLE));
+        entityValues.put(CalendarContract.Events.ORGANIZER, organizerEmail);
+
+        // Add ourselves as an attendee, using our account email address
+        final ContentValues attendeeValues = new ContentValues(2);
+        attendeeValues.put(CalendarContract.Attendees.ATTENDEE_RELATIONSHIP,
+                CalendarContract.Attendees.RELATIONSHIP_ATTENDEE);
+        attendeeValues.put(CalendarContract.Attendees.ATTENDEE_EMAIL, mAccount.mEmailAddress);
+        entity.addSubValue(CalendarContract.Attendees.CONTENT_URI, attendeeValues);
+
+        // Add the organizer
+        final ContentValues organizerValues = new ContentValues(2);
+        organizerValues.put(CalendarContract.Attendees.ATTENDEE_RELATIONSHIP,
+                CalendarContract.Attendees.RELATIONSHIP_ORGANIZER);
+        organizerValues.put(CalendarContract.Attendees.ATTENDEE_EMAIL, organizerEmail);
+        entity.addSubValue(CalendarContract.Attendees.CONTENT_URI, organizerValues);
+
+        // Create a message from the Entity we've built.  The message will have fields like
+        // to, subject, date, and text filled in.  There will also be an "inline" attachment
+        // which is in iCalendar format
+        final int flag;
+        switch(response) {
+            case EmailServiceConstants.MEETING_REQUEST_ACCEPTED:
+                flag = EmailContent.Message.FLAG_OUTGOING_MEETING_ACCEPT;
+                break;
+            case EmailServiceConstants.MEETING_REQUEST_DECLINED:
+                flag = EmailContent.Message.FLAG_OUTGOING_MEETING_DECLINE;
+                break;
+            case EmailServiceConstants.MEETING_REQUEST_TENTATIVE:
+            default:
+                flag = EmailContent.Message.FLAG_OUTGOING_MEETING_TENTATIVE;
+                break;
+        }
+        final EmailContent.Message outgoingMsg =
+                CalendarUtilities.createMessageForEntity(mContext, entity, flag,
+                        meetingInfo.get(MeetingInfo.MEETING_UID), mAccount);
+        // Assuming we got a message back (we might not if the event has been deleted), send it
+        if (outgoingMsg != null) {
+            sendMessage(mAccount, outgoingMsg);
+        }
+    }
+}
diff --git a/src/com/android/exchange/service/EasMeetingResponder.java b/src/com/android/exchange/service/EasMeetingResponder.java
deleted file mode 100644
index f42f583..0000000
--- a/src/com/android/exchange/service/EasMeetingResponder.java
+++ /dev/null
@@ -1,240 +0,0 @@
-package com.android.exchange.service;
-
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Entity;
-import android.provider.CalendarContract.Attendees;
-import android.provider.CalendarContract.Events;
-import android.text.TextUtils;
-
-import com.android.emailcommon.mail.Address;
-import com.android.emailcommon.mail.MeetingInfo;
-import com.android.emailcommon.mail.PackedString;
-import com.android.emailcommon.provider.Account;
-import com.android.emailcommon.provider.EmailContent.MailboxColumns;
-import com.android.emailcommon.provider.EmailContent.Message;
-import com.android.emailcommon.provider.Mailbox;
-import com.android.emailcommon.service.EmailServiceConstants;
-import com.android.emailcommon.utility.Utility;
-import com.android.exchange.Eas;
-import com.android.exchange.EasResponse;
-import com.android.exchange.adapter.MeetingResponseParser;
-import com.android.exchange.adapter.Serializer;
-import com.android.exchange.adapter.Tags;
-import com.android.exchange.utility.CalendarUtilities;
-import com.android.mail.providers.UIProvider;
-import com.android.mail.utils.LogUtils;
-
-import org.apache.http.HttpStatus;
-
-import java.io.IOException;
-import java.security.cert.CertificateException;
-import java.text.ParseException;
-
-/**
- * Responds to a meeting request, both notifying the EAS server and sending email.
- */
-public class EasMeetingResponder extends EasServerConnection {
-
-    private static final String TAG = Eas.LOG_TAG;
-
-    /** Projection for getting the server id for a mailbox. */
-    private static final String[] MAILBOX_SERVER_ID_PROJECTION = { MailboxColumns.SERVER_ID };
-    private static final int MAILBOX_SERVER_ID_COLUMN = 0;
-
-    /** EAS protocol values for UserResponse. */
-    private static final int EAS_RESPOND_ACCEPT = 1;
-    private static final int EAS_RESPOND_TENTATIVE = 2;
-    private static final int EAS_RESPOND_DECLINE = 3;
-
-    /** Value to use if we get a UI response value that we can't handle. */
-    private static final int EAS_RESPOND_UNKNOWN = -1;
-
-    private EasMeetingResponder(final Context context, final Account account) {
-        super(context, account);
-    }
-
-    /**
-     * Translate from {@link UIProvider.MessageOperations} constants to EAS values.
-     * They're currently identical but this is for future-proofing.
-     * @param messageOperationResponse The response value that came from the UI.
-     * @return The EAS protocol value to use.
-     */
-    private static int messageOperationResponseToUserResponse(final int messageOperationResponse) {
-        switch (messageOperationResponse) {
-            case UIProvider.MessageOperations.RESPOND_ACCEPT:
-                return EAS_RESPOND_ACCEPT;
-            case UIProvider.MessageOperations.RESPOND_TENTATIVE:
-                return EAS_RESPOND_TENTATIVE;
-            case UIProvider.MessageOperations.RESPOND_DECLINE:
-                return EAS_RESPOND_DECLINE;
-        }
-        return EAS_RESPOND_UNKNOWN;
-    }
-
-    /**
-     * Send the response to both the EAS server and as email (if appropriate).
-     * @param context Our {@link Context}.
-     * @param messageId The db id for the message containing the meeting request.
-     * @param response The UI's value for the user's response to the meeting.
-     */
-    public static void sendMeetingResponse(final Context context, final long messageId,
-            final int response) {
-        final int easResponse = messageOperationResponseToUserResponse(response);
-        if (easResponse == EAS_RESPOND_UNKNOWN) {
-            LogUtils.e(TAG, "Bad response value: %d", response);
-            return;
-        }
-        final Message msg = Message.restoreMessageWithId(context, messageId);
-        if (msg == null) {
-            LogUtils.d(TAG, "Could not load message %d", messageId);
-            return;
-        }
-        final Account account = Account.restoreAccountWithId(context, msg.mAccountKey);
-        if (account == null) {
-            LogUtils.e(TAG, "Could not load account %d for message %d", msg.mAccountKey, msg.mId);
-            return;
-        }
-        final String mailboxServerId = Utility.getFirstRowString(context,
-                ContentUris.withAppendedId(Mailbox.CONTENT_URI, msg.mMailboxKey),
-                MAILBOX_SERVER_ID_PROJECTION, null, null, null, MAILBOX_SERVER_ID_COLUMN);
-        if (mailboxServerId == null) {
-            LogUtils.e(TAG, "Could not load mailbox %d for message %d", msg.mMailboxKey, msg.mId);
-            return;
-        }
-
-        final EasMeetingResponder responder = new EasMeetingResponder(context, account);
-        try {
-            responder.sendResponse(msg, mailboxServerId, easResponse);
-        } catch (final IOException e) {
-            LogUtils.e(TAG, "IOException: %s", e.getMessage());
-        } catch (final CertificateException e) {
-            LogUtils.e(TAG, "CertificateException: %s", e.getMessage());
-        }
-    }
-
-    /**
-     * Send an email response to a meeting invitation.
-     * @param meetingInfo The meeting info that was extracted from the invitation message.
-     * @param response The EAS value for the user's response to the meeting.
-     */
-    private void sendMeetingResponseMail(final PackedString meetingInfo, final int response) {
-        // This will come as "First Last" <box@server.blah>, so we use Address to
-        // parse it into parts; we only need the email address part for the ics file
-        final Address[] addrs = Address.parse(meetingInfo.get(MeetingInfo.MEETING_ORGANIZER_EMAIL));
-        // It shouldn't be possible, but handle it anyway
-        if (addrs.length != 1) return;
-        final String organizerEmail = addrs[0].getAddress();
-
-        final String dtStamp = meetingInfo.get(MeetingInfo.MEETING_DTSTAMP);
-        final String dtStart = meetingInfo.get(MeetingInfo.MEETING_DTSTART);
-        final String dtEnd = meetingInfo.get(MeetingInfo.MEETING_DTEND);
-        if (TextUtils.isEmpty(dtStamp) || TextUtils.isEmpty(dtStart) ||
-                TextUtils.isEmpty(dtEnd)) {
-            return;
-        }
-
-        // What we're doing here is to create an Entity that looks like an Event as it would be
-        // stored by CalendarProvider
-        final ContentValues entityValues = new ContentValues(6);
-        final Entity entity = new Entity(entityValues);
-
-        // Fill in times, location, title, and organizer
-        entityValues.put("DTSTAMP",
-                CalendarUtilities.convertEmailDateTimeToCalendarDateTime(dtStamp));
-        try {
-            entityValues.put(Events.DTSTART, Utility.parseEmailDateTimeToMillis(dtStart));
-            entityValues.put(Events.DTEND, Utility.parseEmailDateTimeToMillis(dtEnd));
-        } catch (ParseException e) {
-            LogUtils.w(TAG, "Parse error for DTSTART/DTEND tags.", e);
-            return;
-        }
-        entityValues.put(Events.EVENT_LOCATION, meetingInfo.get(MeetingInfo.MEETING_LOCATION));
-        entityValues.put(Events.TITLE, meetingInfo.get(MeetingInfo.MEETING_TITLE));
-        entityValues.put(Events.ORGANIZER, organizerEmail);
-
-        // Add ourselves as an attendee, using our account email address
-        final ContentValues attendeeValues = new ContentValues(2);
-        attendeeValues.put(Attendees.ATTENDEE_RELATIONSHIP,
-                Attendees.RELATIONSHIP_ATTENDEE);
-        attendeeValues.put(Attendees.ATTENDEE_EMAIL, mAccount.mEmailAddress);
-        entity.addSubValue(Attendees.CONTENT_URI, attendeeValues);
-
-        // Add the organizer
-        final ContentValues organizerValues = new ContentValues(2);
-        organizerValues.put(Attendees.ATTENDEE_RELATIONSHIP,
-                Attendees.RELATIONSHIP_ORGANIZER);
-        organizerValues.put(Attendees.ATTENDEE_EMAIL, organizerEmail);
-        entity.addSubValue(Attendees.CONTENT_URI, organizerValues);
-
-        // Create a message from the Entity we've built.  The message will have fields like
-        // to, subject, date, and text filled in.  There will also be an "inline" attachment
-        // which is in iCalendar format
-        final int flag;
-        switch(response) {
-            case EmailServiceConstants.MEETING_REQUEST_ACCEPTED:
-                flag = Message.FLAG_OUTGOING_MEETING_ACCEPT;
-                break;
-            case EmailServiceConstants.MEETING_REQUEST_DECLINED:
-                flag = Message.FLAG_OUTGOING_MEETING_DECLINE;
-                break;
-            case EmailServiceConstants.MEETING_REQUEST_TENTATIVE:
-            default:
-                flag = Message.FLAG_OUTGOING_MEETING_TENTATIVE;
-                break;
-        }
-        final Message outgoingMsg =
-            CalendarUtilities.createMessageForEntity(mContext, entity, flag,
-                    meetingInfo.get(MeetingInfo.MEETING_UID), mAccount);
-        // Assuming we got a message back (we might not if the event has been deleted), send it
-        if (outgoingMsg != null) {
-            sendMessage(mAccount, outgoingMsg);
-        }
-    }
-
-    /**
-     * Send the response to the EAS server, and also via email if requested.
-     * @param msg The email message for the meeting invitation.
-     * @param mailboxServerId The server id for the mailbox that msg is in.
-     * @param response The EAS value for the user's response.
-     * @throws IOException
-     */
-    private void sendResponse(final Message msg, final String mailboxServerId, final int response)
-            throws IOException, CertificateException {
-        final Serializer s = new Serializer();
-        s.start(Tags.MREQ_MEETING_RESPONSE).start(Tags.MREQ_REQUEST);
-        s.data(Tags.MREQ_USER_RESPONSE, Integer.toString(response));
-        s.data(Tags.MREQ_COLLECTION_ID, mailboxServerId);
-        s.data(Tags.MREQ_REQ_ID, msg.mServerId);
-        s.end().end().done();
-        final EasResponse resp = sendHttpClientPost("MeetingResponse", s.toByteArray());
-        try {
-            final int status = resp.getStatus();
-            if (status == HttpStatus.SC_OK) {
-                if (!resp.isEmpty()) {
-                    // TODO: Improve the parsing to actually handle error statuses.
-                    new MeetingResponseParser(resp.getInputStream()).parse();
-
-                    if (msg.mMeetingInfo != null) {
-                        final PackedString meetingInfo = new PackedString(msg.mMeetingInfo);
-                        final String responseRequested =
-                                meetingInfo.get(MeetingInfo.MEETING_RESPONSE_REQUESTED);
-                        // If there's no tag, or a non-zero tag, we send the response mail
-                        if (!"0".equals(responseRequested)) {
-                            sendMeetingResponseMail(meetingInfo, response);
-                        }
-                    }
-                }
-            } else if (resp.isAuthError()) {
-                // TODO: Handle this gracefully.
-                //throw new EasAuthenticationException();
-            } else {
-                LogUtils.e(TAG, "Meeting response request failed, code: %d", status);
-                throw new IOException();
-            }
-        } finally {
-            resp.close();
-        }
-    }
-}
diff --git a/src/com/android/exchange/service/EasServerConnection.java b/src/com/android/exchange/service/EasServerConnection.java
index 2a91c41..e7f741b 100644
--- a/src/com/android/exchange/service/EasServerConnection.java
+++ b/src/com/android/exchange/service/EasServerConnection.java
@@ -137,7 +137,7 @@
     private EmailClientConnectionManager mClientConnectionManager;
 
     public EasServerConnection(final Context context, final Account account,
-            final HostAuth hostAuth) {
+                               final HostAuth hostAuth) {
         mContext = context;
         mHostAuth = hostAuth;
         mAccount = account;
@@ -145,10 +145,6 @@
         setProtocolVersion(account.mProtocolVersion);
     }
 
-    public EasServerConnection(final Context context, final Account account) {
-        this(context, account, HostAuth.restoreHostAuthWithId(context, account.mHostAuthKeyRecv));
-    }
-
     protected EmailClientConnectionManager getClientConnectionManager()
         throws CertificateException {
         final EmailClientConnectionManager connManager =
diff --git a/src/com/android/exchange/service/EasService.java b/src/com/android/exchange/service/EasService.java
index 6b64af1..fd7b17d 100644
--- a/src/com/android/exchange/service/EasService.java
+++ b/src/com/android/exchange/service/EasService.java
@@ -31,15 +31,18 @@
 import com.android.emailcommon.provider.EmailContent;
 import com.android.emailcommon.provider.HostAuth;
 import com.android.emailcommon.provider.Mailbox;
+import com.android.emailcommon.service.EmailServiceProxy;
 import com.android.emailcommon.service.IEmailService;
 import com.android.emailcommon.service.IEmailServiceCallback;
 import com.android.emailcommon.service.SearchParams;
 import com.android.emailcommon.service.ServiceProxy;
 import com.android.exchange.Eas;
+import com.android.exchange.eas.EasAutoDiscover;
 import com.android.exchange.eas.EasFolderSync;
 import com.android.exchange.eas.EasLoadAttachment;
 import com.android.exchange.eas.EasOperation;
 import com.android.exchange.eas.EasSearch;
+import com.android.exchange.eas.EasSendMeetingResponse;
 import com.android.mail.utils.LogUtils;
 
 import java.util.HashSet;
@@ -123,13 +126,66 @@
 
         @Override
         public void sendMeetingResponse(final long messageId, final int response) {
-            LogUtils.d(TAG, "IEmailService.sendMeetingResponse: %d, %d", messageId, response);
+            EmailContent.Message msg = EmailContent.Message.restoreMessageWithId(EasService.this,
+                    messageId);
+            if (msg == null) {
+                LogUtils.e(TAG, "Could not load message %d in sendMeetingResponse", messageId);
+                return;
+            }
+
+            final EasSendMeetingResponse operation = new EasSendMeetingResponse(EasService.this,
+                    msg.mAccountKey, msg, response);
+            doOperation(operation, "IEmailService.sendMeetingResponse");
         }
 
         @Override
         public Bundle autoDiscover(final String username, final String password) {
-            LogUtils.d(TAG, "IEmailService.autoDiscover");
-            return null;
+            final String domain = EasAutoDiscover.getDomain(username);
+            final String uri = EasAutoDiscover.createUri(domain);
+            final Bundle result = autoDiscoverInternal(uri, username, password, true);
+            final int resultCode = result.getInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE);
+            if (resultCode == EasAutoDiscover.RESULT_BAD_RESPONSE) {
+                // Try the alternate uri
+                final String alternateUri = EasAutoDiscover.createAlternateUri(domain);
+                return autoDiscoverInternal(alternateUri, username, password, true);
+            } else {
+                return result;
+            }
+        }
+
+        private Bundle autoDiscoverInternal(final String uri, final String username,
+                                            final String password, final boolean canRetry) {
+            final EasAutoDiscover op = new EasAutoDiscover(EasService.this, uri, username, password);
+            final int result = op.performOperation();
+            if (result == EasAutoDiscover.RESULT_REDIRECT) {
+                // Try again recursively with the new uri. TODO we should limit the number of redirects.
+                final String redirectUri = op.getRedirectUri();
+                return autoDiscoverInternal(redirectUri, username, password, canRetry);
+            } else if (result == EasAutoDiscover.RESULT_SC_UNAUTHORIZED) {
+                if (canRetry && username.contains("@")) {
+                    // Try again using the bare user name
+                    final int atSignIndex = username.indexOf('@');
+                    final String bareUsername = username.substring(0, atSignIndex);
+                    LogUtils.d(TAG, "%d received; trying username: %s", result, atSignIndex);
+                    // Try again recursively, but this time don't allow retries for username.
+                    return autoDiscoverInternal(uri, bareUsername, password, false);
+                } else {
+                    // Either we're already on our second try or the username didn't have an "@"
+                    // to begin with. Either way, failure.
+                    final Bundle bundle = new Bundle(1);
+                    bundle.putInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE,
+                            EasAutoDiscover.RESULT_OTHER_FAILURE);
+                    return bundle;
+                }
+            } else if (result != EasAutoDiscover.RESULT_OK) {
+                // Return failure, we'll try again with an alternate address
+                final Bundle bundle = new Bundle(1);
+                bundle.putInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE,
+                        EasAutoDiscover.RESULT_BAD_RESPONSE);
+                return bundle;
+            }
+            // Success.
+            return op.getResultBundle();
         }
 
         @Override
@@ -140,6 +196,7 @@
         @Override
         public void deleteAccountPIMData(final String emailAddress) {
             LogUtils.d(TAG, "IEmailService.deleteAccountPIMData");
+            // TODO: remove this, move it completely to Email code.
         }
     };
 
diff --git a/src/com/android/exchange/service/EmailSyncAdapterService.java b/src/com/android/exchange/service/EmailSyncAdapterService.java
index bb18bb6..6534b8f 100644
--- a/src/com/android/exchange/service/EmailSyncAdapterService.java
+++ b/src/com/android/exchange/service/EmailSyncAdapterService.java
@@ -52,6 +52,7 @@
 import com.android.emailcommon.provider.EmailContent.SyncColumns;
 import com.android.emailcommon.provider.HostAuth;
 import com.android.emailcommon.provider.Mailbox;
+import com.android.emailcommon.service.EmailServiceProxy;
 import com.android.emailcommon.service.EmailServiceStatus;
 import com.android.emailcommon.service.IEmailService;
 import com.android.emailcommon.service.IEmailServiceCallback;
@@ -60,9 +61,10 @@
 import com.android.emailcommon.utility.IntentUtilities;
 import com.android.emailcommon.utility.Utility;
 import com.android.exchange.Eas;
-import com.android.exchange.R.drawable;
-import com.android.exchange.R.string;
+import com.android.exchange.R;
 import com.android.exchange.adapter.PingParser;
+import com.android.exchange.eas.EasAutoDiscover;
+import com.android.exchange.eas.EasSendMeetingResponse;
 import com.android.exchange.eas.EasSyncContacts;
 import com.android.exchange.eas.EasSyncCalendar;
 import com.android.exchange.eas.EasFolderSync;
@@ -395,11 +397,55 @@
 
         @Override
         public Bundle autoDiscover(final String username, final String password) {
-            LogUtils.d(TAG, "IEmailService.autoDiscover");
-            return new EasAutoDiscover(EmailSyncAdapterService.this, username, password)
-                    .doAutodiscover();
+            final String domain = EasAutoDiscover.getDomain(username);
+            final String uri = EasAutoDiscover.createUri(domain);
+            final Bundle result = autoDiscoverInternal(uri, username, password, true);
+            final int resultCode = result.getInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE);
+            if (resultCode == EasAutoDiscover.RESULT_BAD_RESPONSE) {
+                // Try the alternate uri
+                final String alternateUri = EasAutoDiscover.createAlternateUri(domain);
+                return autoDiscoverInternal(alternateUri, username, password, true);
+            } else {
+                return result;
+            }
         }
 
+        private Bundle autoDiscoverInternal(final String uri, final String username,
+                                            final String password, final boolean canRetry) {
+            EasAutoDiscover op = new EasAutoDiscover(EmailSyncAdapterService.this, uri, username, password);
+            final int result = op.performOperation();
+            if (result == EasAutoDiscover.RESULT_REDIRECT) {
+                // Try again recursively with the new uri. TODO we should limit the number of redirects.
+                String redirectUri = op.getRedirectUri();
+                return autoDiscoverInternal(redirectUri, username, password, canRetry);
+            } else if (result == EasAutoDiscover.RESULT_SC_UNAUTHORIZED) {
+                if (canRetry && username.contains("@")) {
+                    // Try again using the bare user name
+                    final int atSignIndex = username.indexOf('@');
+                    final String bareUsername = username.substring(0, atSignIndex);
+                    LogUtils.d(TAG, "%d received; trying username: %s", result, atSignIndex);
+                    // Try again recursively, but this time don't allow retries for username.
+                    return autoDiscoverInternal(uri, bareUsername, password, false);
+                } else {
+                    // Either we're already on our second try or the username didn't have an "@"
+                    // to begin with. Either way, failure.
+                    final Bundle bundle = new Bundle(1);
+                    bundle.putInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE,
+                            EasAutoDiscover.RESULT_OTHER_FAILURE);
+                    return bundle;
+                }
+            } else if (result != EasAutoDiscover.RESULT_OK) {
+                // Return failure, we'll try again with an alternate address
+                final Bundle bundle = new Bundle(1);
+                bundle.putInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE,
+                        EasAutoDiscover.RESULT_BAD_RESPONSE);
+                return bundle;
+
+            } else {
+                // Success.
+                return op.getResultBundle();
+            }
+        }
         @Override
         public void updateFolderList(final long accountId) {
             LogUtils.d(TAG, "IEmailService.updateFolderList: %d", accountId);
@@ -443,8 +489,14 @@
         @Override
         public void sendMeetingResponse(final long messageId, final int response) {
             LogUtils.d(TAG, "IEmailService.sendMeetingResponse: %d, %d", messageId, response);
-            EasMeetingResponder.sendMeetingResponse(EmailSyncAdapterService.this, messageId,
-                    response);
+            final EmailContent.Message msg = EmailContent.Message.restoreMessageWithId(
+                    EmailSyncAdapterService.this, messageId);
+            final EasOperation op = new EasSendMeetingResponse(EmailSyncAdapterService.this,
+                    msg.mAccountKey, msg, response);
+            final int result = op.performOperation();
+            if (result != EasSendMeetingResponse.RESULT_OK) {
+                LogUtils.w(TAG, "Unexpected result %d from sendMeetingResponse", result);
+            }
         }
 
         /**
@@ -932,10 +984,10 @@
                 0);
 
         final Notification notification = new Builder(this)
-                .setContentTitle(this.getString(string.auth_error_notification_title))
+                .setContentTitle(this.getString(R.string.auth_error_notification_title))
                 .setContentText(this.getString(
-                        string.auth_error_notification_text, accountName))
-                .setSmallIcon(drawable.stat_notify_auth)
+                        R.string.auth_error_notification_text, accountName))
+                .setSmallIcon(R.drawable.stat_notify_auth)
                 .setContentIntent(pendingIntent)
                 .setAutoCancel(true)
                 .build();
diff --git a/src/com/android/exchange/utility/CalendarUtilities.java b/src/com/android/exchange/utility/CalendarUtilities.java
index 43aeb6f..176f598 100644
--- a/src/com/android/exchange/utility/CalendarUtilities.java
+++ b/src/com/android/exchange/utility/CalendarUtilities.java
@@ -1725,8 +1725,8 @@
      * Create a Message for an (Event) Entity
      * @param entity the Entity for the Event (as might be retrieved by CalendarProvider)
      * @param messageFlag the Message.FLAG_XXX constant indicating the type of email to be sent
-     * @param the unique id of this Event, or null if it can be retrieved from the Event
-     * @param the user's account
+     * @param uid the unique id of this Event, or null if it can be retrieved from the Event
+     * @param account the user's account
      * @return a Message with many fields pre-filled (more later)
      */
     static public Message createMessageForEntity(Context context, Entity entity,