Sync jb-dev to jb-ub-mail Exchange

* For convenience in Eclipse

Change-Id: I99da4238ed83f29c33bd9116faa66edc5c1429a7
diff --git a/exchange2/src/com/android/exchange/EasSyncService.java b/exchange2/src/com/android/exchange/EasSyncService.java
index f1e544d..657bc84 100644
--- a/exchange2/src/com/android/exchange/EasSyncService.java
+++ b/exchange2/src/com/android/exchange/EasSyncService.java
@@ -166,6 +166,7 @@
     private boolean mSsl = true;
     private boolean mTrustSsl = false;
     private String mClientCertAlias = null;
+    private int mPort;
 
     public ContentResolver mContentResolver;
     // Whether or not the sync service is valid (usable)
@@ -387,10 +388,7 @@
         svc.mUserName = ha.mLogin;
         svc.mPassword = ha.mPassword;
         try {
-            svc.setConnectionParameters(
-                    (ha.mFlags & HostAuth.FLAG_SSL) != 0,
-                    (ha.mFlags & HostAuth.FLAG_TRUST_ALL) != 0,
-                    ha.mClientCertAlias);
+            svc.setConnectionParameters(ha);
             svc.mDeviceId = ExchangeService.getDeviceId(context);
         } catch (IOException e) {
             return null;
@@ -441,10 +439,7 @@
             mUserName = hostAuth.mLogin;
             mPassword = hostAuth.mPassword;
 
-            setConnectionParameters(
-                    hostAuth.shouldUseSsl(),
-                    hostAuth.shouldTrustAllServerCerts(),
-                    hostAuth.mClientCertAlias);
+            setConnectionParameters(hostAuth);
             mDeviceId = ExchangeService.getDeviceId(context);
             mAccount = new Account();
             mAccount.mEmailAddress = hostAuth.mLogin;
@@ -705,6 +700,10 @@
             // Initialize the user name and password
             mUserName = userName;
             mPassword = password;
+            // Port is always 443 and SSL is used
+            mPort = 443;
+            mSsl = true;
+
             // Make sure the authentication string is recreated and cached
             cacheAuthUserAndBaseUriStrings();
 
@@ -1215,26 +1214,23 @@
         }
     }
 
-    protected void setConnectionParameters(
-            boolean useSsl, boolean trustAllServerCerts, String clientCertAlias)
-            throws CertificateException {
-
-        EmailClientConnectionManager connManager = getClientConnectionManager();
-
-        mSsl = useSsl;
-        mTrustSsl = trustAllServerCerts;
-        mClientCertAlias = clientCertAlias;
+    protected void setConnectionParameters(HostAuth hostAuth) throws CertificateException {
+        mSsl = hostAuth.shouldUseSsl();
+        mTrustSsl = hostAuth.shouldTrustAllServerCerts();
+        mClientCertAlias = hostAuth.mClientCertAlias;
+        mPort = hostAuth.mPort;
 
         // Register the new alias, if needed.
         if (mClientCertAlias != null) {
             // Ensure that the connection manager knows to use the proper client certificate
             // when establishing connections for this service.
-            connManager.registerClientCert(mContext, mClientCertAlias, mTrustSsl);
+            EmailClientConnectionManager connManager = getClientConnectionManager();
+            connManager.registerClientCert(mContext, hostAuth);
         }
     }
 
     private EmailClientConnectionManager getClientConnectionManager() {
-        return ExchangeService.getClientConnectionManager();
+        return ExchangeService.getClientConnectionManager(mSsl, mPort);
     }
 
     private HttpClient getHttpClient(int timeout) {
@@ -1668,14 +1664,14 @@
 
             // Start with the default timeout
             int timeout = COMMAND_TIMEOUT;
-            if (!syncKey.equals("0")) {
-                // EAS doesn't allow GetChanges in an initial sync; sending other options
-                // appears to cause the server to delay its response in some cases, and this delay
-                // can be long enough to result in an IOException and total failure to sync.
-                // Therefore, we don't send any options with the initial sync.
-                // Set the truncation amount, body preference, lookback, etc.
-                target.sendSyncOptions(mProtocolVersionDouble, s);
-            } else {
+            boolean initialSync = syncKey.equals("0");
+            // EAS doesn't allow GetChanges in an initial sync; sending other options
+            // appears to cause the server to delay its response in some cases, and this delay
+            // can be long enough to result in an IOException and total failure to sync.
+            // Therefore, we don't send any options with the initial sync.
+            // Set the truncation amount, body preference, lookback, etc.
+            target.sendSyncOptions(mProtocolVersionDouble, s, initialSync);
+            if (initialSync) {
                 // Use enormous timeout for initial sync, which empirically can take a while longer
                 timeout = 120*SECONDS;
             }
@@ -1805,10 +1801,7 @@
         mPassword = ha.mPassword;
 
         try {
-            setConnectionParameters(
-                    (ha.mFlags & HostAuth.FLAG_SSL) != 0,
-                    (ha.mFlags & HostAuth.FLAG_TRUST_ALL) != 0,
-                    ha.mClientCertAlias);
+            setConnectionParameters(ha);
         } catch (CertificateException e) {
             userLog("Couldn't retrieve certificate for connection");
             try {
diff --git a/exchange2/src/com/android/exchange/ExchangeService.java b/exchange2/src/com/android/exchange/ExchangeService.java
index 0107d55..b1ae3b7 100644
--- a/exchange2/src/com/android/exchange/ExchangeService.java
+++ b/exchange2/src/com/android/exchange/ExchangeService.java
@@ -220,8 +220,9 @@
     private static Thread sServiceThread = null;
     // Cached unique device id
     private static String sDeviceId = null;
-    // ConnectionManager that all EAS threads can use
-    private static EmailClientConnectionManager sClientConnectionManager = null;
+    // HashMap of ConnectionManagers that all EAS threads can use (by ssl/port pair)
+    private static HashMap<Integer, EmailClientConnectionManager> sClientConnectionManagers =
+            new HashMap<Integer, EmailClientConnectionManager>();
     // Count of ClientConnectionManager shutdowns
     private static volatile int sClientConnectionManagerShutdownCount = 0;
 
@@ -1263,8 +1264,12 @@
         }
     };
 
-    static public synchronized EmailClientConnectionManager getClientConnectionManager() {
-        if (sClientConnectionManager == null) {
+    static public synchronized EmailClientConnectionManager getClientConnectionManager(boolean ssl,
+            int port) {
+        // We'll use a different connection manager for each ssl/port pair
+        int key = (ssl ? 0x10000 : 0) + port;
+        EmailClientConnectionManager mgr = sClientConnectionManagers.get(key);
+        if (mgr == null) {
             // After two tries, kill the process.  Most likely, this will happen in the background
             // The service will restart itself after about 5 seconds
             if (sClientConnectionManagerShutdownCount > MAX_CLIENT_CONNECTION_MANAGER_SHUTDOWNS) {
@@ -1274,19 +1279,20 @@
             HttpParams params = new BasicHttpParams();
             params.setIntParameter(ConnManagerPNames.MAX_TOTAL_CONNECTIONS, 25);
             params.setParameter(ConnManagerPNames.MAX_CONNECTIONS_PER_ROUTE, sConnPerRoute);
-            sClientConnectionManager = EmailClientConnectionManager.newInstance(params);
+            mgr = EmailClientConnectionManager.newInstance(params, ssl, port);
+            log("Creating connection manager for port " + port + ", ssl: " + ssl);
+            sClientConnectionManagers.put(key, mgr);
         }
         // Null is a valid return result if we get an exception
-        return sClientConnectionManager;
+        return mgr;
     }
 
     static private synchronized void shutdownConnectionManager() {
-        if (sClientConnectionManager != null) {
-            log("Shutting down ClientConnectionManager");
-            sClientConnectionManager.shutdown();
-            sClientConnectionManagerShutdownCount++;
-            sClientConnectionManager = null;
+        log("Shutting down ClientConnectionManagers");
+        for (EmailClientConnectionManager mgr: sClientConnectionManagers.values()) {
+            mgr.shutdown();
         }
+        sClientConnectionManagers.clear();
     }
 
     public static void stopAccountSyncs(long acctId) {
diff --git a/exchange2/src/com/android/exchange/adapter/AbstractSyncAdapter.java b/exchange2/src/com/android/exchange/adapter/AbstractSyncAdapter.java
index 52400c4..04a67d0 100644
--- a/exchange2/src/com/android/exchange/adapter/AbstractSyncAdapter.java
+++ b/exchange2/src/com/android/exchange/adapter/AbstractSyncAdapter.java
@@ -71,7 +71,8 @@
     public abstract void cleanup();
     public abstract boolean isSyncable();
     // Add sync options (filter, body type - html vs plain, and truncation)
-    public abstract void sendSyncOptions(Double protocolVersion, Serializer s) throws IOException;
+    public abstract void sendSyncOptions(Double protocolVersion, Serializer s, boolean initialSync)
+            throws IOException;
     /**
      * Delete all records of this class in this account
      */
diff --git a/exchange2/src/com/android/exchange/adapter/AccountSyncAdapter.java b/exchange2/src/com/android/exchange/adapter/AccountSyncAdapter.java
index cf54ed5..e56a7d3 100644
--- a/exchange2/src/com/android/exchange/adapter/AccountSyncAdapter.java
+++ b/exchange2/src/com/android/exchange/adapter/AccountSyncAdapter.java
@@ -40,6 +40,7 @@
     }
 
     @Override
-    public void sendSyncOptions(Double protocolVersion, Serializer s) throws IOException {
+    public void sendSyncOptions(Double protocolVersion, Serializer s, boolean initialSync)
+            throws IOException {
     }
 }
diff --git a/exchange2/src/com/android/exchange/adapter/CalendarSyncAdapter.java b/exchange2/src/com/android/exchange/adapter/CalendarSyncAdapter.java
index 4df45a9..c21be00 100644
--- a/exchange2/src/com/android/exchange/adapter/CalendarSyncAdapter.java
+++ b/exchange2/src/com/android/exchange/adapter/CalendarSyncAdapter.java
@@ -223,8 +223,11 @@
     }
 
     @Override
-    public void sendSyncOptions(Double protocolVersion, Serializer s) throws IOException  {
-        setPimSyncOptions(protocolVersion, Eas.FILTER_2_WEEKS, s);
+    public void sendSyncOptions(Double protocolVersion, Serializer s, boolean initialSync)
+            throws IOException  {
+        if (!initialSync) {
+            setPimSyncOptions(protocolVersion, Eas.FILTER_2_WEEKS, s);
+        }
     }
 
     @Override
diff --git a/exchange2/src/com/android/exchange/adapter/ContactsSyncAdapter.java b/exchange2/src/com/android/exchange/adapter/ContactsSyncAdapter.java
index 1b21d11..c0c3a48 100644
--- a/exchange2/src/com/android/exchange/adapter/ContactsSyncAdapter.java
+++ b/exchange2/src/com/android/exchange/adapter/ContactsSyncAdapter.java
@@ -58,13 +58,17 @@
 import android.util.Base64;
 import android.util.Log;
 
+import com.android.emailcommon.utility.Utility;
 import com.android.exchange.CommandStatusException;
 import com.android.exchange.Eas;
 import com.android.exchange.EasSyncService;
+import com.android.exchange.utility.CalendarUtilities;
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
 
 /**
  * Sync adapter for EAS Contacts
@@ -144,8 +148,73 @@
     }
 
     @Override
-    public void sendSyncOptions(Double protocolVersion, Serializer s) throws IOException  {
-        setPimSyncOptions(protocolVersion, null, s);
+    public void sendSyncOptions(Double protocolVersion, Serializer s, boolean initialSync)
+            throws IOException  {
+        if (initialSync) {
+            // These are the tags we support for upload; whenever we add/remove support
+            // (in addData), we need to update this list
+            s.start(Tags.SYNC_SUPPORTED);
+            s.tag(Tags.CONTACTS_FIRST_NAME);
+            s.tag(Tags.CONTACTS_LAST_NAME);
+            s.tag(Tags.CONTACTS_MIDDLE_NAME);
+            s.tag(Tags.CONTACTS_SUFFIX);
+            s.tag(Tags.CONTACTS_COMPANY_NAME);
+            s.tag(Tags.CONTACTS_JOB_TITLE);
+            s.tag(Tags.CONTACTS_EMAIL1_ADDRESS);
+            s.tag(Tags.CONTACTS_EMAIL2_ADDRESS);
+            s.tag(Tags.CONTACTS_EMAIL3_ADDRESS);
+            s.tag(Tags.CONTACTS_BUSINESS2_TELEPHONE_NUMBER);
+            s.tag(Tags.CONTACTS_BUSINESS_TELEPHONE_NUMBER);
+            s.tag(Tags.CONTACTS2_MMS);
+            s.tag(Tags.CONTACTS_BUSINESS_FAX_NUMBER);
+            s.tag(Tags.CONTACTS2_COMPANY_MAIN_PHONE);
+            s.tag(Tags.CONTACTS_HOME_FAX_NUMBER);
+            s.tag(Tags.CONTACTS_HOME_TELEPHONE_NUMBER);
+            s.tag(Tags.CONTACTS_HOME2_TELEPHONE_NUMBER);
+            s.tag(Tags.CONTACTS_MOBILE_TELEPHONE_NUMBER);
+            s.tag(Tags.CONTACTS_CAR_TELEPHONE_NUMBER);
+            s.tag(Tags.CONTACTS_RADIO_TELEPHONE_NUMBER);
+            s.tag(Tags.CONTACTS_PAGER_NUMBER);
+            s.tag(Tags.CONTACTS_ASSISTANT_TELEPHONE_NUMBER);
+            s.tag(Tags.CONTACTS2_IM_ADDRESS);
+            s.tag(Tags.CONTACTS2_IM_ADDRESS_2);
+            s.tag(Tags.CONTACTS2_IM_ADDRESS_3);
+            s.tag(Tags.CONTACTS_BUSINESS_ADDRESS_CITY);
+            s.tag(Tags.CONTACTS_BUSINESS_ADDRESS_COUNTRY);
+            s.tag(Tags.CONTACTS_BUSINESS_ADDRESS_POSTAL_CODE);
+            s.tag(Tags.CONTACTS_BUSINESS_ADDRESS_STATE);
+            s.tag(Tags.CONTACTS_BUSINESS_ADDRESS_STREET);
+            s.tag(Tags.CONTACTS_HOME_ADDRESS_CITY);
+            s.tag(Tags.CONTACTS_HOME_ADDRESS_COUNTRY);
+            s.tag(Tags.CONTACTS_HOME_ADDRESS_POSTAL_CODE);
+            s.tag(Tags.CONTACTS_HOME_ADDRESS_STATE);
+            s.tag(Tags.CONTACTS_HOME_ADDRESS_STREET);
+            s.tag(Tags.CONTACTS_OTHER_ADDRESS_CITY);
+            s.tag(Tags.CONTACTS_OTHER_ADDRESS_COUNTRY);
+            s.tag(Tags.CONTACTS_OTHER_ADDRESS_POSTAL_CODE);
+            s.tag(Tags.CONTACTS_OTHER_ADDRESS_STATE);
+            s.tag(Tags.CONTACTS_OTHER_ADDRESS_STREET);
+            s.tag(Tags.CONTACTS_YOMI_COMPANY_NAME);
+            s.tag(Tags.CONTACTS_YOMI_FIRST_NAME);
+            s.tag(Tags.CONTACTS_YOMI_LAST_NAME);
+            s.tag(Tags.CONTACTS2_NICKNAME);
+            s.tag(Tags.CONTACTS_ASSISTANT_NAME);
+            s.tag(Tags.CONTACTS2_MANAGER_NAME);
+            s.tag(Tags.CONTACTS_SPOUSE);
+            s.tag(Tags.CONTACTS_DEPARTMENT);
+            s.tag(Tags.CONTACTS_TITLE);
+            s.tag(Tags.CONTACTS_OFFICE_LOCATION);
+            s.tag(Tags.CONTACTS2_CUSTOMER_ID);
+            s.tag(Tags.CONTACTS2_GOVERNMENT_ID);
+            s.tag(Tags.CONTACTS2_ACCOUNT_NAME);
+            s.tag(Tags.CONTACTS_ANNIVERSARY);
+            s.tag(Tags.CONTACTS_BIRTHDAY);
+            s.tag(Tags.CONTACTS_WEBPAGE);
+            s.tag(Tags.CONTACTS_PICTURE);
+            s.end(); // SYNC_SUPPORTED
+        } else {
+            setPimSyncOptions(protocolVersion, null, s);
+        }
     }
 
     @Override
@@ -294,11 +363,13 @@
             }
         }
 
+        @Override
         public void addValues(RowBuilder builder) {
             builder.withValue(Email.DATA, email);
             builder.withValue(Email.DISPLAY_NAME, displayName);
         }
 
+        @Override
         public boolean isSameAs(int type, String value) {
             return email.equalsIgnoreCase(value);
         }
@@ -311,10 +382,12 @@
             im = _im;
         }
 
+        @Override
         public void addValues(RowBuilder builder) {
             builder.withValue(Im.DATA, im);
         }
 
+        @Override
         public boolean isSameAs(int type, String value) {
             return im.equalsIgnoreCase(value);
         }
@@ -329,11 +402,13 @@
             type = _type;
         }
 
+        @Override
         public void addValues(RowBuilder builder) {
             builder.withValue(Im.DATA, phone);
             builder.withValue(Phone.TYPE, type);
         }
 
+        @Override
         public boolean isSameAs(int _type, String value) {
             return type == _type && phone.equalsIgnoreCase(value);
         }
@@ -352,7 +427,6 @@
 
         public void addData(String serverId, ContactOperations ops, Entity entity)
                 throws IOException {
-            String fileAs = null;
             String prefix = null;
             String firstName = null;
             String lastName = null;
@@ -390,9 +464,6 @@
                     case Tags.CONTACTS_MIDDLE_NAME:
                         middleName = getValue();
                         break;
-                    case Tags.CONTACTS_FILE_AS:
-                        fileAs = getValue();
-                        break;
                     case Tags.CONTACTS_SUFFIX:
                         suffix = getValue();
                         break;
@@ -568,11 +639,6 @@
                         categoriesParser(ops, entity);
                         break;
 
-                    case Tags.CONTACTS_COMPRESSED_RTF:
-                        // We don't use this, and it isn't necessary to upload, so we'll ignore it
-                        skipTag();
-                        break;
-
                     default:
                         skipTag();
                 }
@@ -593,7 +659,7 @@
             }
 
             ops.addName(entity, prefix, firstName, lastName, middleName, suffix, name,
-                    yomiFirstName, yomiLastName, fileAs);
+                    yomiFirstName, yomiLastName);
             ops.addBusiness(entity, business);
             ops.addPersonal(entity, personal);
 
@@ -1202,14 +1268,21 @@
             if (cv != null && cvCompareString(cv, Event.START_DATE, birthday)) {
                 return;
             }
-            builder.withValue(Event.START_DATE, birthday);
+            long millis = Utility.parseEmailDateTimeToMillis(birthday);
+            GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
+            cal.setTimeInMillis(millis);
+            if (cal.get(GregorianCalendar.HOUR_OF_DAY) >= 12) {
+                cal.add(GregorianCalendar.DATE, 1);
+            }
+            String realBirthday = CalendarUtilities.calendarToBirthdayString(cal);
+            builder.withValue(Event.START_DATE, realBirthday);
             builder.withValue(Event.TYPE, Event.TYPE_BIRTHDAY);
             add(builder.build());
         }
 
         public void addName(Entity entity, String prefix, String givenName, String familyName,
                 String middleName, String suffix, String displayName, String yomiFirstName,
-                String yomiLastName, String fileAs) {
+                String yomiLastName) {
             RowBuilder builder = untypedRowBuilder(entity, StructuredName.CONTENT_ITEM_TYPE);
             ContentValues cv = builder.cv;
             if (cv != null && cvCompareString(cv, StructuredName.GIVEN_NAME, givenName) &&
@@ -1218,7 +1291,6 @@
                     cvCompareString(cv, StructuredName.PREFIX, prefix) &&
                     cvCompareString(cv, StructuredName.PHONETIC_GIVEN_NAME, yomiFirstName) &&
                     cvCompareString(cv, StructuredName.PHONETIC_FAMILY_NAME, yomiLastName) &&
-                    //cvCompareString(cv, StructuredName.DISPLAY_NAME, fileAs) &&
                     cvCompareString(cv, StructuredName.SUFFIX, suffix)) {
                 return;
             }
@@ -1229,7 +1301,6 @@
             builder.withValue(StructuredName.PHONETIC_GIVEN_NAME, yomiFirstName);
             builder.withValue(StructuredName.PHONETIC_FAMILY_NAME, yomiLastName);
             builder.withValue(StructuredName.PREFIX, prefix);
-            //builder.withValue(StructuredName.DISPLAY_NAME, fileAs);
             add(builder.build());
         }
 
@@ -1601,12 +1672,6 @@
         sendStringData(s, cv, StructuredName.PHONETIC_GIVEN_NAME, Tags.CONTACTS_YOMI_FIRST_NAME);
         sendStringData(s, cv, StructuredName.PHONETIC_FAMILY_NAME, Tags.CONTACTS_YOMI_LAST_NAME);
         sendStringData(s, cv, StructuredName.PREFIX, Tags.CONTACTS_TITLE);
-        if (cv.containsKey(StructuredName.DISPLAY_NAME)) {
-            displayName = cv.getAsString(StructuredName.DISPLAY_NAME);
-            if (!TextUtils.isEmpty(displayName)) {
-                s.data(Tags.CONTACTS_FILE_AS, displayName);
-            }
-        }
         return displayName;
     }
 
diff --git a/exchange2/src/com/android/exchange/adapter/EmailSyncAdapter.java b/exchange2/src/com/android/exchange/adapter/EmailSyncAdapter.java
index 73199f8..b419f23 100644
--- a/exchange2/src/com/android/exchange/adapter/EmailSyncAdapter.java
+++ b/exchange2/src/com/android/exchange/adapter/EmailSyncAdapter.java
@@ -25,6 +25,9 @@
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.RemoteException;
+import android.provider.CalendarContract.Events;
+import android.text.Html;
+import android.text.SpannedString;
 import android.text.TextUtils;
 import android.util.Base64;
 import android.util.Log;
@@ -52,6 +55,7 @@
 import com.android.emailcommon.service.SyncWindow;
 import com.android.emailcommon.utility.AttachmentUtilities;
 import com.android.emailcommon.utility.ConversionUtilities;
+import com.android.emailcommon.utility.TextUtilities;
 import com.android.emailcommon.utility.Utility;
 import com.android.exchange.CommandStatusException;
 import com.android.exchange.Eas;
@@ -193,8 +197,9 @@
     }
 
     @Override
-    public void sendSyncOptions(Double protocolVersion, Serializer s)
+    public void sendSyncOptions(Double protocolVersion, Serializer s, boolean initialSync)
             throws IOException  {
+        if (initialSync) return;
         mFetchRequestList.clear();
         // Find partially loaded messages; this should typically be a rare occurrence
         Cursor c = mContext.getContentResolver().query(Message.CONTENT_URI,
@@ -606,6 +611,39 @@
             if (atts.size() > 0) {
                 msg.mAttachments = atts;
             }
+
+            if ((msg.mFlags & Message.FLAG_INCOMING_MEETING_MASK) != 0) {
+                String text = TextUtilities.makeSnippetFromHtmlText(
+                        msg.mText != null ? msg.mText : msg.mHtml);
+                if (TextUtils.isEmpty(text)) {
+                    // Create text for this invitation
+                    String meetingInfo = msg.mMeetingInfo;
+                    if (!TextUtils.isEmpty(meetingInfo)) {
+                        PackedString ps = new PackedString(meetingInfo);
+                        ContentValues values = new ContentValues();
+                        putFromMeeting(ps, MeetingInfo.MEETING_LOCATION, values,
+                                Events.EVENT_LOCATION);
+                        String dtstart = ps.get(MeetingInfo.MEETING_DTSTART);
+                        if (!TextUtils.isEmpty(dtstart)) {
+                            long startTime = Utility.parseEmailDateTimeToMillis(dtstart);
+                            values.put(Events.DTSTART, startTime);
+                        }
+                        putFromMeeting(ps, MeetingInfo.MEETING_ALL_DAY, values,
+                                Events.ALL_DAY);
+                        msg.mText = CalendarUtilities.buildMessageTextFromEntityValues(
+                                mContext, values, null);
+                        msg.mHtml = Html.toHtml(new SpannedString(msg.mText));
+                    }
+                }
+            }
+        }
+
+        private void putFromMeeting(PackedString ps, String field, ContentValues values,
+                String column) {
+            String val = ps.get(field);
+            if (!TextUtils.isEmpty(val)) {
+                values.put(column, val);
+            }
         }
 
         /**
@@ -647,6 +685,11 @@
                     case Tags.EMAIL_RESPONSE_REQUESTED:
                         packedString.put(MeetingInfo.MEETING_RESPONSE_REQUESTED, getValue());
                         break;
+                    case Tags.EMAIL_ALL_DAY_EVENT:
+                        if (getValueInt() == 1) {
+                            packedString.put(MeetingInfo.MEETING_ALL_DAY, getValue());
+                        }
+                        break;
                     default:
                         skipTag();
                 }
diff --git a/exchange2/src/com/android/exchange/adapter/FolderSyncParser.java b/exchange2/src/com/android/exchange/adapter/FolderSyncParser.java
index 3b0bc7c..2c213a4 100644
--- a/exchange2/src/com/android/exchange/adapter/FolderSyncParser.java
+++ b/exchange2/src/com/android/exchange/adapter/FolderSyncParser.java
@@ -32,7 +32,6 @@
 import com.android.emailcommon.provider.Mailbox;
 import com.android.emailcommon.service.SyncWindow;
 import com.android.emailcommon.utility.AttachmentUtilities;
-import com.android.emailcommon.utility.EmailAsyncTask;
 import com.android.emailcommon.utility.Utility;
 import com.android.exchange.CommandStatusException;
 import com.android.exchange.CommandStatusException.CommandStatus;
@@ -47,7 +46,6 @@
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
-import java.util.concurrent.ExecutionException;
 
 /**
  * Parse the result of a FolderSync command
@@ -428,11 +426,14 @@
         // Mailbox, so we'll have to query the database
         if (parent == null) {
             mBindArguments[0] = Long.toString(mAccount.mId);
-            mBindArguments[1] = mailbox.mParentServerId;
-            long parentId = Utility.getFirstRowInt(mContext, Mailbox.CONTENT_URI,
-                    EmailContent.ID_PROJECTION,
-                    MailboxColumns.ACCOUNT_KEY + "=? AND " + MailboxColumns.SERVER_ID + "=?",
-                    mBindArguments, null, EmailContent.ID_PROJECTION_COLUMN, -1);
+            long parentId = -1;
+            if (mailbox.mParentServerId != null) {
+                mBindArguments[1] = mailbox.mParentServerId;
+                parentId = Utility.getFirstRowInt(mContext, Mailbox.CONTENT_URI,
+                        EmailContent.ID_PROJECTION,
+                        MailboxColumns.ACCOUNT_KEY + "=? AND " + MailboxColumns.SERVER_ID + "=?",
+                        mBindArguments, null, EmailContent.ID_PROJECTION_COLUMN, -1);
+            }
             if (parentId != -1) {
                 // Get the parent from the database
                 parent = Mailbox.restoreMailboxWithId(mContext, parentId);
@@ -540,6 +541,7 @@
 
     public void changesParser(final ArrayList<ContentProviderOperation> ops,
             final boolean initialSync) throws IOException {
+
         // Array of added mailboxes
         final ArrayList<Mailbox> addMailboxes = new ArrayList<Mailbox>();
 
@@ -562,89 +564,77 @@
                 skipTag();
         }
 
-        EmailAsyncTask<?, ?, ?> task =
-                EmailAsyncTask.runAsyncParallel(new Runnable() {
-                    @Override
-                    public void run() {
-                        // Synchronize on the parser to prevent this being run concurrently
-                        // (an extremely unlikely event, but nonetheless possible)
-                        synchronized (FolderSyncParser.this) {
-                            // Mailboxes that we known contain email
-                            ArrayList<Mailbox> validMailboxes = new ArrayList<Mailbox>();
-                            // Mailboxes that we're unsure about
-                            ArrayList<Mailbox> userMailboxes = new ArrayList<Mailbox>();
+        // Synchronize on the parser to prevent this being run concurrently
+        // (an extremely unlikely event, but nonetheless possible)
+        synchronized (FolderSyncParser.this) {
+            // Mailboxes that we known contain email
+            ArrayList<Mailbox> validMailboxes = new ArrayList<Mailbox>();
+            // Mailboxes that we're unsure about
+            ArrayList<Mailbox> userMailboxes = new ArrayList<Mailbox>();
 
-                            // Maps folder serverId to mailbox (used to validate user mailboxes)
-                            HashMap<String, Mailbox> mailboxMap = new HashMap<String, Mailbox>();
-                            for (Mailbox mailbox : addMailboxes) {
-                                mailboxMap.put(mailbox.mServerId, mailbox);
-                            }
-
-                            int mailboxCommitCount = 0;
-                            for (Mailbox mailbox : addMailboxes) {
-                                // And add the mailbox to the proper list
-                                if (type == USER_MAILBOX_TYPE) {
-                                    userMailboxes.add(mailbox);
-                                } else {
-                                    validMailboxes.add(mailbox);
-                                }
-                                // On initial sync, we commit what we have every 20 mailboxes
-                                if (initialSync && (++mailboxCommitCount == MAILBOX_COMMIT_SIZE)) {
-                                    if (!commitMailboxes(validMailboxes, userMailboxes, mailboxMap,
-                                            ops)) {
-                                        mService.stop();
-                                        return;
-                                    }
-                                    // Clear our arrays to prepare for more
-                                    userMailboxes.clear();
-                                    validMailboxes.clear();
-                                    ops.clear();
-                                    mailboxCommitCount = 0;
-                                }
-                            }
-                            // Commit the sync key and mailboxes
-                            ContentValues cv = new ContentValues();
-                            cv.put(AccountColumns.SYNC_KEY, mAccount.mSyncKey);
-                            ops.add(ContentProviderOperation
-                                    .newUpdate(
-                                            ContentUris.withAppendedId(Account.CONTENT_URI,
-                                                    mAccount.mId))
-                                            .withValues(cv).build());
-                            if (!commitMailboxes(validMailboxes, userMailboxes, mailboxMap, ops)) {
-                                mService.stop();
-                                return;
-                            }
-                            String accountSelector = Mailbox.ACCOUNT_KEY + "=" + mAccount.mId;
-                            // For new boxes, setup the parent key and flags
-                            if (mFixupUninitializedNeeded) {
-                                MailboxUtilities.fixupUninitializedParentKeys(mContext,
-                                        accountSelector);
-                            }
-                            // For modified parents, reset the flags (and children's parent key)
-                            for (String parentServerId: mParentFixupsNeeded) {
-                                Cursor c = mContentResolver.query(Mailbox.CONTENT_URI,
-                                        Mailbox.CONTENT_PROJECTION, Mailbox.PARENT_SERVER_ID + "=?",
-                                        new String[] {parentServerId}, null);
-                                try {
-                                    if (c.moveToFirst()) {
-                                        MailboxUtilities.setFlagsAndChildrensParentKey(mContext, c,
-                                                accountSelector);
-                                    }
-                                } finally {
-                                    c.close();
-                                }
-                            }
-
-                            // Signal completion of mailbox changes
-                            MailboxUtilities.endMailboxChanges(mContext, mAccount.mId);
-                        }
-                    }});
-        // Make this synchronous if in a unit test
-        if (mInUnitTest) {
-            try {
-                task.get();
-            } catch (Exception e) {
+            // Maps folder serverId to mailbox (used to validate user mailboxes)
+            HashMap<String, Mailbox> mailboxMap = new HashMap<String, Mailbox>();
+            for (Mailbox mailbox : addMailboxes) {
+                mailboxMap.put(mailbox.mServerId, mailbox);
             }
+
+            int mailboxCommitCount = 0;
+            for (Mailbox mailbox : addMailboxes) {
+                // And add the mailbox to the proper list
+                if (mailbox.mType == Mailbox.TYPE_UNKNOWN) {
+                    userMailboxes.add(mailbox);
+                } else {
+                    validMailboxes.add(mailbox);
+                }
+                // On initial sync, we commit what we have every 20 mailboxes
+                if (initialSync && (++mailboxCommitCount == MAILBOX_COMMIT_SIZE)) {
+                    if (!commitMailboxes(validMailboxes, userMailboxes, mailboxMap,
+                            ops)) {
+                        mService.stop();
+                        return;
+                    }
+                    // Clear our arrays to prepare for more
+                    userMailboxes.clear();
+                    validMailboxes.clear();
+                    ops.clear();
+                    mailboxCommitCount = 0;
+                }
+            }
+            // Commit the sync key and mailboxes
+            ContentValues cv = new ContentValues();
+            cv.put(AccountColumns.SYNC_KEY, mAccount.mSyncKey);
+            ops.add(ContentProviderOperation
+                    .newUpdate(
+                            ContentUris.withAppendedId(Account.CONTENT_URI,
+                                    mAccount.mId))
+                            .withValues(cv).build());
+            if (!commitMailboxes(validMailboxes, userMailboxes, mailboxMap, ops)) {
+                mService.stop();
+                return;
+            }
+            String accountSelector = Mailbox.ACCOUNT_KEY + "=" + mAccount.mId;
+            // For new boxes, setup the parent key and flags
+            if (mFixupUninitializedNeeded) {
+                MailboxUtilities.fixupUninitializedParentKeys(mContext,
+                        accountSelector);
+            }
+            // For modified parents, reset the flags (and children's parent key)
+            for (String parentServerId: mParentFixupsNeeded) {
+                Cursor c = mContentResolver.query(Mailbox.CONTENT_URI,
+                        Mailbox.CONTENT_PROJECTION, Mailbox.PARENT_SERVER_ID + "=?",
+                        new String[] {parentServerId}, null);
+                try {
+                    if (c.moveToFirst()) {
+                        MailboxUtilities.setFlagsAndChildrensParentKey(mContext, c,
+                                accountSelector);
+                    }
+                } finally {
+                    c.close();
+                }
+            }
+
+            // Signal completion of mailbox changes
+            MailboxUtilities.endMailboxChanges(mContext, mAccount.mId);
         }
     }
 
diff --git a/exchange2/src/com/android/exchange/utility/CalendarUtilities.java b/exchange2/src/com/android/exchange/utility/CalendarUtilities.java
index 2302b7a..792d065 100644
--- a/exchange2/src/com/android/exchange/utility/CalendarUtilities.java
+++ b/exchange2/src/com/android/exchange/utility/CalendarUtilities.java
@@ -903,6 +903,22 @@
     }
 
     /**
+     * Generate a birthday string from a GregorianCalendar set appropriately; the format of this
+     * string is YYYY-MM-DD
+     * @param cal the calendar
+     * @return the birthday string
+     */
+    static public String calendarToBirthdayString(GregorianCalendar cal) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(cal.get(Calendar.YEAR));
+        sb.append('-');
+        sb.append(formatTwo(cal.get(Calendar.MONTH) + 1));
+        sb.append('-');
+        sb.append(formatTwo(cal.get(Calendar.DAY_OF_MONTH)));
+        return sb.toString();
+    }
+
+    /**
      * Generate an EAS formatted local date/time string from a time and a time zone. If the final
      * argument is false, only a date will be returned (e.g. 20100331)
      * @param millis a time in milliseconds
@@ -1298,7 +1314,7 @@
                 if (dow == 127) {
                     rrule.append(";BYMONTHDAY=-1");
                 // week 5 and dow = weekdays -> last weekday (need BYSETPOS)
-                } else if (wom == 5 && (dow == EAS_WEEKDAYS || dow == EAS_WEEKENDS)) {
+                } else if ((wom == 5 || wom == 1) && (dow == EAS_WEEKDAYS || dow == EAS_WEEKENDS)) {
                     addBySetpos(rrule, dow, wom);
                 } else if (dow > 0) addByDay(rrule, dow, wom);
                 break;