Increase timeouts for Exchange sync; prevent early upload sync

* It's reported that 50% of third party users have issues syncing
  Calendar in Exchange
* In testing, it was determined that the server takes > 30 seconds
  to respond to a sync request initially, which is beyond our timeout
  limit
* Also, I found that the system SyncManager was trying to trigger an
  upload sync at the same time (i.e. before the sync session was
  established with the server)
* There are four changes here:
  1) Prevent upload syncs while the sync key is null or "0" ("0"
     is the initial state)
  2) Increase timeout for connection; at worst, this will
     cause a short extra delay in syncs with a bad connection, but this
     will be unnoticable to users
  3) Increase the read timeout for initial sync to twice that of
     regular syncs (the initial sync always seems to take longer)
  3) Reduce the lookback for calendar to two weeks (from one month);
     this is a better default anyway, and it probably reduces the server
     and client load a great deal
* Empirically, this solves the bug for a known completely repeatable
  case.

Bug: 2569162
Change-Id: I36b1c3e1e0b65f50d42e05f1830fed912191651f
diff --git a/src/com/android/exchange/CalendarSyncAdapterService.java b/src/com/android/exchange/CalendarSyncAdapterService.java
index 357d67f..7941a0c 100644
--- a/src/com/android/exchange/CalendarSyncAdapterService.java
+++ b/src/com/android/exchange/CalendarSyncAdapterService.java
@@ -45,6 +45,10 @@
         MailboxColumns.ACCOUNT_KEY + "=? AND " + MailboxColumns.TYPE + '=' + Mailbox.TYPE_CALENDAR;
     private static final String DIRTY_IN_ACCOUNT =
         Events._SYNC_DIRTY + "=1 AND " + Events._SYNC_ACCOUNT + "=?";
+    private static final String[] ID_SYNC_KEY_PROJECTION =
+        new String[] {MailboxColumns.ID, MailboxColumns.SYNC_KEY};
+    private static final int ID_SYNC_KEY_MAILBOX_ID = 0;
+    private static final int ID_SYNC_KEY_SYNC_KEY = 1;
 
     public CalendarSyncAdapterService() {
         super();
@@ -119,15 +123,22 @@
             if (accountCursor.moveToFirst()) {
                 long accountId = accountCursor.getLong(0);
                 // Now, find the calendar mailbox associated with the account
-                Cursor mailboxCursor = cr.query(Mailbox.CONTENT_URI, EmailContent.ID_PROJECTION,
+                Cursor mailboxCursor = cr.query(Mailbox.CONTENT_URI, ID_SYNC_KEY_PROJECTION,
                         ACCOUNT_AND_TYPE_CALENDAR, new String[] {Long.toString(accountId)}, null);
                 try {
                      if (mailboxCursor.moveToFirst()) {
                         if (logging) {
                             Log.d(TAG, "Upload sync requested for " + account.name);
                         }
+                        String syncKey = mailboxCursor.getString(ID_SYNC_KEY_SYNC_KEY);
+                        if ((syncKey == null) || (syncKey.equals("0"))) {
+                            if (logging) {
+                                Log.d(TAG, "Can't sync; mailbox in initial state");
+                            }
+                            return;
+                        }
                         // Ask for a sync from our sync manager
-                        SyncManager.serviceRequest(mailboxCursor.getLong(0),
+                        SyncManager.serviceRequest(mailboxCursor.getLong(ID_SYNC_KEY_MAILBOX_ID),
                                 SyncManager.SYNC_UPSYNC);
                     }
                 } finally {
diff --git a/src/com/android/exchange/EasSyncService.java b/src/com/android/exchange/EasSyncService.java
index 9ddb46f..cdd5111 100644
--- a/src/com/android/exchange/EasSyncService.java
+++ b/src/com/android/exchange/EasSyncService.java
@@ -120,7 +120,14 @@
     static private final int CHUNK_SIZE = 16*1024;
 
     static private final String PING_COMMAND = "Ping";
+    // Command timeout is the the time allowed for reading data from an open connection before an
+    // IOException is thrown.  After a small added allowance, our watchdog alarm goes off (allowing
+    // us to detect a silently dropped connection).  The allowance is defined below.
     static private final int COMMAND_TIMEOUT = 20*SECONDS;
+    // Connection timeout is the time given to connect to the server before reporting an IOException
+    static private final int CONNECTION_TIMEOUT = 30*SECONDS;
+    // The extra time allowed beyond the COMMAND_TIMEOUT before which our watchdog alarm triggers
+    static private final int WATCHDOG_TIMEOUT_ALLOWANCE = 10*SECONDS;
 
     static private final String AUTO_DISCOVER_SCHEMA_PREFIX =
         "http://schemas.microsoft.com/exchange/autodiscover/mobilesync/";
@@ -1034,7 +1041,7 @@
 
     private HttpClient getHttpClient(int timeout) {
         HttpParams params = new BasicHttpParams();
-        HttpConnectionParams.setConnectionTimeout(params, 15*SECONDS);
+        HttpConnectionParams.setConnectionTimeout(params, CONNECTION_TIMEOUT);
         HttpConnectionParams.setSoTimeout(params, timeout);
         HttpConnectionParams.setSocketBufferSize(params, 8192);
         HttpClient client = new DefaultHttpClient(getClientConnectionManager(), params);
@@ -1078,7 +1085,7 @@
             boolean isPingCommand) throws IOException {
         synchronized(getSynchronizer()) {
             mPendingPost = method;
-            long alarmTime = timeout+(10*SECONDS);
+            long alarmTime = timeout + WATCHDOG_TIMEOUT_ALLOWANCE;
             if (isPingCommand) {
                 SyncManager.runAsleep(mMailboxId, alarmTime);
             } else {
@@ -1878,9 +1885,14 @@
                 .data(Tags.SYNC_COLLECTION_ID, mailbox.mServerId)
                 .tag(Tags.SYNC_DELETES_AS_MOVES);
 
-            // EAS doesn't like GetChanges if the syncKey is "0"; not documented
+            // Start with the default timeout
+            int timeout = COMMAND_TIMEOUT;
             if (!syncKey.equals("0")) {
+                // EAS doesn't like GetChanges if the syncKey is "0"; not documented
                 s.tag(Tags.SYNC_GET_CHANGES);
+            } else {
+                // Use 2x timeout for initial sync, which empirically can take a while longer
+                timeout <<= 1;
             }
             s.data(Tags.SYNC_WINDOW_SIZE,
                     className.equals("Email") ? EMAIL_WINDOW_SIZE : PIM_WINDOW_SIZE);
@@ -1891,8 +1903,8 @@
             if (className.equals("Email")) {
                 s.data(Tags.SYNC_FILTER_TYPE, getEmailFilter());
             } else if (className.equals("Calendar")) {
-                // TODO Force one month for calendar until we can set this!
-                s.data(Tags.SYNC_FILTER_TYPE, Eas.FILTER_1_MONTH);
+                // TODO Force two weeks for calendar until we can set this!
+                s.data(Tags.SYNC_FILTER_TYPE, Eas.FILTER_2_WEEKS);
             }
             // Set the truncation amount for all classes
             if (mProtocolVersionDouble >= Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) {
@@ -1911,7 +1923,8 @@
             target.sendLocalChanges(s);
 
             s.end().end().end().done();
-            HttpResponse resp = sendHttpClientPost("Sync", s.toByteArray());
+            HttpResponse resp = sendHttpClientPost("Sync", new ByteArrayEntity(s.toByteArray()),
+                    timeout);
             int code = resp.getStatusLine().getStatusCode();
             if (code == HttpStatus.SC_OK) {
                 InputStream is = resp.getEntity().getContent();