Merge "Update EasPing durations" into jb-ub-mail-ur10
diff --git a/src/com/android/exchange/adapter/PingParser.java b/src/com/android/exchange/adapter/PingParser.java
index 9c0c424..3e20c1b 100644
--- a/src/com/android/exchange/adapter/PingParser.java
+++ b/src/com/android/exchange/adapter/PingParser.java
@@ -124,8 +124,7 @@
         return  pingStatus == STATUS_EXPIRED
                 || pingStatus == STATUS_REQUEST_INCOMPLETE
                 || pingStatus == STATUS_REQUEST_MALFORMED
-                // TODO: Implement heartbeat adjusting and re-enable this.
-                // || pingStatus == STATUS_REQUEST_HEARTBEAT_OUT_OF_BOUNDS
+                || pingStatus == STATUS_REQUEST_HEARTBEAT_OUT_OF_BOUNDS
                 || pingStatus == STATUS_SERVER_ERROR;
     }
 
diff --git a/src/com/android/exchange/eas/EasPing.java b/src/com/android/exchange/eas/EasPing.java
index 2402224..49d9a6a 100644
--- a/src/com/android/exchange/eas/EasPing.java
+++ b/src/com/android/exchange/eas/EasPing.java
@@ -17,13 +17,18 @@
 package com.android.exchange.eas;
 
 import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
 import android.content.Context;
 import android.content.SyncResult;
 import android.database.Cursor;
+import android.net.Uri;
+import android.os.SystemClock;
 import android.text.format.DateUtils;
 
 import com.android.emailcommon.provider.Account;
 import com.android.emailcommon.provider.EmailContent;
+import com.android.emailcommon.provider.EmailContent.AccountColumns;
 import com.android.emailcommon.provider.EmailContent.MailboxColumns;
 import com.android.emailcommon.provider.Mailbox;
 import com.android.exchange.Eas;
@@ -50,40 +55,87 @@
 
     private final long mAccountId;
     private final android.accounts.Account mAmAccount;
+    private long mPingDuration;
 
-    // TODO: Implement Heartbeat autoadjustments based on the server responses.
     /**
-     * The heartbeat interval specified to the Exchange server. This is the maximum amount of
-     * time (in seconds) that the server should wait before responding to the ping request.
+     * The default heartbeat interval specified to the Exchange server. This is the maximum amount
+     * of time (in seconds) that the server should wait before responding to the ping request.
      */
-    private static final long PING_HEARTBEAT =
+    private static final long DEFAULT_PING_HEARTBEAT =
             8 * (DateUtils.MINUTE_IN_MILLIS / DateUtils.SECOND_IN_MILLIS);
 
-    /** {@link #PING_HEARTBEAT}, as a String. */
-    private static final String PING_HEARTBEAT_STRING = Long.toString(PING_HEARTBEAT);
+    /**
+     * The minimum heartbeat interval we should ever use, in seconds.
+     */
+    private static final long MINIMUM_PING_HEARTBEAT =
+            8 * (DateUtils.MINUTE_IN_MILLIS / DateUtils.SECOND_IN_MILLIS);
 
     /**
-     * The timeout used for the HTTP POST (in milliseconds). Notionally this should be the same
-     * as {@link #PING_HEARTBEAT} but in practice is a few seconds longer to allow for latency
-     * in the server's response.
+     * The maximum heartbeat interval we should ever use, in seconds.
      */
-    private static final long POST_TIMEOUT = (5 + PING_HEARTBEAT) * DateUtils.SECOND_IN_MILLIS;
+    private static final long MAXIMUM_PING_HEARTBEAT =
+            28 * (DateUtils.MINUTE_IN_MILLIS / DateUtils.SECOND_IN_MILLIS);
+
+    /**
+     * The maximum amount that we can change with each adjustment, in seconds.
+     */
+    private static final long MAXIMUM_HEARTBEAT_INCREMENT =
+            5 * (DateUtils.MINUTE_IN_MILLIS / DateUtils.SECOND_IN_MILLIS);
+
+    /**
+     * The extra time for the timeout used for the HTTP POST (in milliseconds). Notionally this
+     * should be the same as ping heartbeat but in practice is a few seconds longer to allow for
+     * latency in the server's response.
+     */
+    private static final long EXTRA_POST_TIMEOUT_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS;
 
     public EasPing(final Context context, final Account account) {
         super(context, account);
         mAccountId = account.mId;
         mAmAccount = new android.accounts.Account(account.mEmailAddress,
                 Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE);
+        mPingDuration = account.mPingDuration;
+        if (mPingDuration == 0) {
+            mPingDuration = DEFAULT_PING_HEARTBEAT;
+        }
+        LogUtils.d(TAG, "initial ping duration " + mPingDuration + " account " + mAccountId);
     }
 
     public final int doPing() {
+        final long startTime = SystemClock.elapsedRealtime();
         final int result = performOperation(null);
         if (result == RESULT_RESTART) {
             return PingParser.STATUS_EXPIRED;
+        } else  if (result == RESULT_REQUEST_FAILURE) {
+            final long timeoutDuration = SystemClock.elapsedRealtime() - startTime;
+            LogUtils.d(TAG, "doPing request failure " + timeoutDuration);
+            decreasePingDuration();
         }
         return result;
     }
 
+    private void decreasePingDuration() {
+        mPingDuration = Math.max(MINIMUM_PING_HEARTBEAT,
+                mPingDuration - MAXIMUM_HEARTBEAT_INCREMENT);
+        LogUtils.d(TAG, "decreasePingDuration adjusting by " + MAXIMUM_HEARTBEAT_INCREMENT +
+                " new duration " + mPingDuration + " account " + mAccountId);
+        storePingDuration();
+    }
+
+    private void increasePingDuration() {
+        mPingDuration = Math.min(MAXIMUM_PING_HEARTBEAT,
+                mPingDuration + MAXIMUM_HEARTBEAT_INCREMENT);
+        LogUtils.d(TAG, "increasePingDuration adjusting by " + MAXIMUM_HEARTBEAT_INCREMENT +
+                " new duration " + mPingDuration + " account " + mAccountId);
+        storePingDuration();
+    }
+
+    private void storePingDuration() {
+        final ContentValues values = new ContentValues(1);
+        values.put(AccountColumns.PING_DURATION, mPingDuration);
+        Account.update(mContext, Account.CONTENT_URI, mAccountId, values);
+    }
+
     public final long getAccountId() {
         return mAccountId;
     }
@@ -132,6 +184,7 @@
     protected int handleResponse(final EasResponse response, final SyncResult syncResult)
             throws IOException {
         if (response.isEmpty()) {
+            // TODO this should probably not be an IOException, maybe something more descriptive?
             throw new IOException("Empty ping response");
         }
 
@@ -146,6 +199,8 @@
         switch (pingStatus) {
             case PingParser.STATUS_EXPIRED:
                 LogUtils.i(TAG, "Ping expired for account %d", mAccountId);
+                // On successful expiration, we can increase our ping duration
+                increasePingDuration();
                 break;
             case PingParser.STATUS_CHANGES_FOUND:
                 LogUtils.i(TAG, "Ping found changed folders for account %d", mAccountId);
@@ -159,8 +214,11 @@
                 LogUtils.e(TAG, "Bad ping request for account %d", mAccountId);
                 break;
             case PingParser.STATUS_REQUEST_HEARTBEAT_OUT_OF_BOUNDS:
-                LogUtils.i(TAG, "Heartbeat out of bounds for account %d", mAccountId);
-                // TODO: Implement auto heartbeat adjustments.
+                long newDuration = pp.getHeartbeatInterval();
+                LogUtils.i(TAG, "Heartbeat out of bounds for account %d, " +
+                        "old duration %d new duration %d", mAccountId, mPingDuration, newDuration);
+                mPingDuration = newDuration;
+                storePingDuration();
                 break;
             case PingParser.STATUS_REQUEST_TOO_MANY_FOLDERS:
                 LogUtils.i(TAG, "Too many folders for account %d", mAccountId);
@@ -187,7 +245,7 @@
 
     @Override
     protected long getTimeout() {
-        return POST_TIMEOUT;
+        return mPingDuration * DateUtils.SECOND_IN_MILLIS + EXTRA_POST_TIMEOUT_MILLIS;
     }
 
     /**
@@ -209,7 +267,7 @@
                     // If either side changes, the other must be kept in sync.
                     s = new Serializer();
                     s.start(Tags.PING_PING);
-                    s.data(Tags.PING_HEARTBEAT_INTERVAL, PING_HEARTBEAT_STRING);
+                    s.data(Tags.PING_HEARTBEAT_INTERVAL, Long.toString(mPingDuration));
                     s.start(Tags.PING_FOLDERS);
                 }
                 s.start(Tags.PING_FOLDER);