Handle correction of rejected Ping heartbeat
* Handle status 5 for Ping command (heartbeat of out range)
* Write unit test for heartbeat reset
Bug: 2834195
Change-Id: Ic7952a4b296cf15c6ba895d6579fe7956b171e5b
diff --git a/src/com/android/exchange/EasSyncService.java b/src/com/android/exchange/EasSyncService.java
index 3eb48c6..a95f304 100644
--- a/src/com/android/exchange/EasSyncService.java
+++ b/src/com/android/exchange/EasSyncService.java
@@ -153,10 +153,7 @@
static private final int PING_MINUTES = 60; // in seconds
static private final int PING_FUDGE_LOW = 10;
static private final int PING_STARTING_HEARTBEAT = (8*PING_MINUTES)-PING_FUDGE_LOW;
- static private final int PING_MIN_HEARTBEAT = (5*PING_MINUTES)-PING_FUDGE_LOW;
- static private final int PING_MAX_HEARTBEAT = (17*PING_MINUTES)-PING_FUDGE_LOW;
static private final int PING_HEARTBEAT_INCREMENT = 3*PING_MINUTES;
- static private final int PING_FORCE_HEARTBEAT = 2*PING_MINUTES;
// Maximum number of times we'll allow a sync to "loop" with MoreAvailable true before
// forcing it to stop. This number has been determined empirically.
@@ -192,12 +189,18 @@
private ArrayList<String> mPingChangeList;
// The HttpPost in progress
private volatile HttpPost mPendingPost = null;
+ // Our heartbeat when we are waiting for ping boxes to be ready
+ /*package*/ int mPingForceHeartbeat = 2*PING_MINUTES;
+ // The minimum heartbeat we will send
+ /*package*/ int mPingMinHeartbeat = (5*PING_MINUTES)-PING_FUDGE_LOW;
+ // The maximum heartbeat we will send
+ /*package*/ int mPingMaxHeartbeat = (17*PING_MINUTES)-PING_FUDGE_LOW;
// The ping time (in seconds)
- private int mPingHeartbeat = PING_STARTING_HEARTBEAT;
+ /*package*/ int mPingHeartbeat = PING_STARTING_HEARTBEAT;
// The longest successful ping heartbeat
private int mPingHighWaterMark = 0;
// Whether we've ever lowered the heartbeat
- private boolean mPingHeartbeatDropped = false;
+ /*package*/ boolean mPingHeartbeatDropped = false;
// Whether a POST was aborted due to alarm (watchdog alarm)
private boolean mPostAborted = false;
// Whether a POST was aborted due to reset
@@ -1541,6 +1544,10 @@
} catch (StaleFolderListException e) {
// We break out if we get told about a stale folder list
userLog("Ping interrupted; folder list requires sync...");
+ } catch (IllegalHeartbeatException e) {
+ // If we're sending an illegal heartbeat, reset either the min or the max to
+ // that heartbeat
+ resetHeartbeats(e.mLegalHeartbeat);
} finally {
Thread.currentThread().setName(threadName);
}
@@ -1561,6 +1568,44 @@
}
}
+ /**
+ * Reset either our minimum or maximum ping heartbeat to a heartbeat known to be legal
+ * @param legalHeartbeat a known legal heartbeat (from the EAS server)
+ */
+ /*package*/ void resetHeartbeats(int legalHeartbeat) {
+ userLog("Resetting min/max heartbeat, legal = " + legalHeartbeat);
+ // We are here because the current heartbeat (mPingHeartbeat) is invalid. Depending on
+ // whether the argument is above or below the current heartbeat, we can infer the need to
+ // change either the minimum or maximum heartbeat
+ if (legalHeartbeat > mPingHeartbeat) {
+ // The legal heartbeat is higher than the ping heartbeat; therefore, our minimum was
+ // too low. We respond by raising either or both of the minimum heartbeat or the
+ // force heartbeat to the argument value
+ if (mPingMinHeartbeat < legalHeartbeat) {
+ mPingMinHeartbeat = legalHeartbeat;
+ }
+ if (mPingForceHeartbeat < legalHeartbeat) {
+ mPingForceHeartbeat = legalHeartbeat;
+ }
+ // If our minimum is now greater than the max, bring them together
+ if (mPingMinHeartbeat > mPingMaxHeartbeat) {
+ mPingMaxHeartbeat = legalHeartbeat;
+ }
+ } else if (legalHeartbeat < mPingHeartbeat) {
+ // The legal heartbeat is lower than the ping heartbeat; therefore, our maximum was
+ // too high. We respond by lowering the maximum to the argument value
+ mPingMaxHeartbeat = legalHeartbeat;
+ // If our maximum is now less than the minimum, bring them together
+ if (mPingMaxHeartbeat < mPingMinHeartbeat) {
+ mPingMinHeartbeat = legalHeartbeat;
+ }
+ }
+ // Set current heartbeat to the legal heartbeat
+ mPingHeartbeat = legalHeartbeat;
+ // Allow the heartbeat logic to run
+ mPingHeartbeatDropped = false;
+ }
+
private void pushFallback(long mailboxId) {
Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, mailboxId);
if (mailbox == null) {
@@ -1593,7 +1638,8 @@
return false;
}
- private void runPingLoop() throws IOException, StaleFolderListException {
+ private void runPingLoop() throws IOException, StaleFolderListException,
+ IllegalHeartbeatException {
int pingHeartbeat = mPingHeartbeat;
userLog("runPingLoop");
// Do push for all sync services here
@@ -1693,7 +1739,7 @@
userLog("Forcing ping after waiting for all boxes to be ready");
}
HttpResponse res =
- sendPing(s.toByteArray(), forcePing ? PING_FORCE_HEARTBEAT : pingHeartbeat);
+ sendPing(s.toByteArray(), forcePing ? mPingForceHeartbeat : pingHeartbeat);
int code = res.getStatusLine().getStatusCode();
userLog("Ping response: ", code);
@@ -1719,11 +1765,11 @@
mPingHighWaterMark = pingHeartbeat;
userLog("Setting high water mark at: ", mPingHighWaterMark);
}
- if ((pingHeartbeat < PING_MAX_HEARTBEAT) &&
+ if ((pingHeartbeat < mPingMaxHeartbeat) &&
!mPingHeartbeatDropped) {
pingHeartbeat += PING_HEARTBEAT_INCREMENT;
- if (pingHeartbeat > PING_MAX_HEARTBEAT) {
- pingHeartbeat = PING_MAX_HEARTBEAT;
+ if (pingHeartbeat > mPingMaxHeartbeat) {
+ pingHeartbeat = mPingMaxHeartbeat;
}
userLog("Increasing ping heartbeat to ", pingHeartbeat, "s");
}
@@ -1748,12 +1794,12 @@
// ping.
} else if (mPostAborted || isLikelyNatFailure(message)) {
long pingLength = SystemClock.elapsedRealtime() - pingTime;
- if ((pingHeartbeat > PING_MIN_HEARTBEAT) &&
+ if ((pingHeartbeat > mPingMinHeartbeat) &&
(pingHeartbeat > mPingHighWaterMark)) {
pingHeartbeat -= PING_HEARTBEAT_INCREMENT;
mPingHeartbeatDropped = true;
- if (pingHeartbeat < PING_MIN_HEARTBEAT) {
- pingHeartbeat = PING_MIN_HEARTBEAT;
+ if (pingHeartbeat < mPingMinHeartbeat) {
+ pingHeartbeat = mPingMinHeartbeat;
}
userLog("Decreased ping heartbeat to ", pingHeartbeat, "s");
} else if (mPostAborted) {
@@ -1823,7 +1869,7 @@
private int parsePingResult(InputStream is, ContentResolver cr,
HashMap<String, Integer> errorMap)
- throws IOException, StaleFolderListException {
+ throws IOException, StaleFolderListException, IllegalHeartbeatException {
PingParser pp = new PingParser(is, this);
if (pp.parse()) {
// True indicates some mailboxes need syncing...
diff --git a/src/com/android/exchange/IllegalHeartbeatException.java b/src/com/android/exchange/IllegalHeartbeatException.java
new file mode 100644
index 0000000..e480aa7
--- /dev/null
+++ b/src/com/android/exchange/IllegalHeartbeatException.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ * Licensed to The Android Open Source Project.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.exchange;
+
+public class IllegalHeartbeatException extends EasException {
+ private static final long serialVersionUID = 1L;
+ public final int mLegalHeartbeat;
+
+ public IllegalHeartbeatException(int legalHeartbeat) {
+ mLegalHeartbeat = legalHeartbeat;
+ }
+}
diff --git a/src/com/android/exchange/adapter/PingParser.java b/src/com/android/exchange/adapter/PingParser.java
index 0731ac0..f061472 100644
--- a/src/com/android/exchange/adapter/PingParser.java
+++ b/src/com/android/exchange/adapter/PingParser.java
@@ -17,6 +17,7 @@
package com.android.exchange.adapter;
import com.android.exchange.EasSyncService;
+import com.android.exchange.IllegalHeartbeatException;
import com.android.exchange.StaleFolderListException;
import java.io.IOException;
@@ -62,7 +63,7 @@
}
@Override
- public boolean parse() throws IOException, StaleFolderListException {
+ public boolean parse() throws IOException, StaleFolderListException, IllegalHeartbeatException {
boolean res = false;
if (nextTag(START_DOCUMENT) != Tags.PING_PING) {
throw new IOException();
@@ -77,9 +78,15 @@
} else if (status == 7 || status == 4) {
// Status of 7 or 4 indicate a stale folder list
throw new StaleFolderListException();
+ } else if (status == 5) {
+ // Status 5 means our heartbeat is beyond allowable limits
+ // In this case, there will be a heartbeat interval set
}
} else if (tag == Tags.PING_FOLDERS) {
parsePingFolders(syncList);
+ } else if (tag == Tags.PING_HEARTBEAT_INTERVAL) {
+ // Throw an exception, saving away the legal heartbeat interval specified
+ throw new IllegalHeartbeatException(getValueInt());
} else {
skipTag();
}
diff --git a/tests/src/com/android/exchange/EasSyncServiceTests.java b/tests/src/com/android/exchange/EasSyncServiceTests.java
index d4372a5..1832d5a 100644
--- a/tests/src/com/android/exchange/EasSyncServiceTests.java
+++ b/tests/src/com/android/exchange/EasSyncServiceTests.java
@@ -23,7 +23,11 @@
import java.io.File;
import java.io.IOException;
-public class EasSyncServiceTests extends AndroidTestCase {
+/**
+ * You can run this entire test case with:
+ * runtest -c com.android.exchange.EasSyncServiceTests email
+ */
+ public class EasSyncServiceTests extends AndroidTestCase {
Context mMockContext;
@Override
@@ -73,4 +77,46 @@
}
}
}
+
+ public void testResetHeartbeats() {
+ EasSyncService svc = new EasSyncService();
+ // Test case in which the minimum and force heartbeats need to come up
+ svc.mPingMaxHeartbeat = 1000;
+ svc.mPingMinHeartbeat = 200;
+ svc.mPingHeartbeat = 300;
+ svc.mPingForceHeartbeat = 100;
+ svc.mPingHeartbeatDropped = true;
+ svc.resetHeartbeats(400);
+ assertEquals(400, svc.mPingMinHeartbeat);
+ assertEquals(1000, svc.mPingMaxHeartbeat);
+ assertEquals(400, svc.mPingHeartbeat);
+ assertEquals(400, svc.mPingForceHeartbeat);
+ assertFalse(svc.mPingHeartbeatDropped);
+
+ // Test case in which the force heartbeat needs to come up
+ svc.mPingMaxHeartbeat = 1000;
+ svc.mPingMinHeartbeat = 200;
+ svc.mPingHeartbeat = 100;
+ svc.mPingForceHeartbeat = 100;
+ svc.mPingHeartbeatDropped = true;
+ svc.resetHeartbeats(150);
+ assertEquals(200, svc.mPingMinHeartbeat);
+ assertEquals(1000, svc.mPingMaxHeartbeat);
+ assertEquals(150, svc.mPingHeartbeat);
+ assertEquals(150, svc.mPingForceHeartbeat);
+ assertFalse(svc.mPingHeartbeatDropped);
+
+ // Test case in which the maximum needs to come down
+ svc.mPingMaxHeartbeat = 1000;
+ svc.mPingMinHeartbeat = 200;
+ svc.mPingHeartbeat = 800;
+ svc.mPingForceHeartbeat = 100;
+ svc.mPingHeartbeatDropped = true;
+ svc.resetHeartbeats(600);
+ assertEquals(200, svc.mPingMinHeartbeat);
+ assertEquals(600, svc.mPingMaxHeartbeat);
+ assertEquals(600, svc.mPingHeartbeat);
+ assertEquals(100, svc.mPingForceHeartbeat);
+ assertFalse(svc.mPingHeartbeatDropped);
+ }
}