Optimize connection loss and re-gain behaviour.

Schedule the ping restart through AlarmManager to be able to cancel it
properly on connection loss, and make sure to only restart idle
connections that aren't already idling (which may happen e.g. on bootup
or if there was a sync request in the 30 second wait window).

Change-Id: If62ffa0981a7a0a71ed7764a9724c07466c6d8a3
This commit is contained in:
Danny Baumann 2015-06-17 11:18:39 +02:00 committed by Steve Kondik
parent a0ef884d04
commit 99c6d7b40a
1 changed files with 106 additions and 54 deletions

View File

@ -112,6 +112,7 @@ public class ImapService extends Service {
private static final int KICK_IDLE_CONNECTION_TIMEOUT = 25 * 60 * 1000; private static final int KICK_IDLE_CONNECTION_TIMEOUT = 25 * 60 * 1000;
private static final int KICK_IDLE_CONNECTION_MAX_DELAY = 3 * 60 * 1000; private static final int KICK_IDLE_CONNECTION_MAX_DELAY = 3 * 60 * 1000;
private static final int ALARM_REQUEST_KICK_IDLE_CODE = 1000; private static final int ALARM_REQUEST_KICK_IDLE_CODE = 1000;
private static final int ALARM_REQUEST_REFRESH_IDLE_CODE = 1001;
// Restart idle connection between 30 seconds and 1 minute after re-gaining connectivity // Restart idle connection between 30 seconds and 1 minute after re-gaining connectivity
private static final int RESTART_IDLE_DELAY_MIN = 30 * 1000; private static final int RESTART_IDLE_DELAY_MIN = 30 * 1000;
@ -155,13 +156,14 @@ public class ImapService extends Service {
"org.codeaurora.email.intent.extra.MESSAGE_INFO"; "org.codeaurora.email.intent.extra.MESSAGE_INFO";
private static final String ACTION_RESTART_IDLE_CONNECTION = private static final String ACTION_RESTART_IDLE_CONNECTION =
"com.android.email.intent.action.RESTART_IDLE_CONNECTION"; "com.android.email.intent.action.RESTART_IDLE_CONNECTION";
private static final String ACTION_RESTART_ALL_IDLE_CONNECTIONS =
"com.android.email.intent.action.RESTART_ALL_IDLE_CONNECTIONS";
private static final String ACTION_KICK_IDLE_CONNECTION = private static final String ACTION_KICK_IDLE_CONNECTION =
"com.android.email.intent.action.KICK_IDLE_CONNECTION"; "com.android.email.intent.action.KICK_IDLE_CONNECTION";
private static final String EXTRA_MAILBOX = "com.android.email.intent.extra.MAILBOX"; private static final String EXTRA_MAILBOX = "com.android.email.intent.extra.MAILBOX";
private static final long RESCHEDULE_PING_DELAY = 150L; private static final long RESCHEDULE_PING_DELAY = 500L;
private static final long MAX_PING_DELAY = 30 * 60 * 1000L; private static final long MAX_PING_DELAY = 30 * 60 * 1000L;
private static final SparseLongArray sPingDelay = new SparseLongArray();
private static String sLegacyImapProtocol; private static String sLegacyImapProtocol;
@ -180,6 +182,7 @@ public class ImapService extends Service {
} }
private static class ImapIdleListener implements ImapFolder.IdleCallback { private static class ImapIdleListener implements ImapFolder.IdleCallback {
private static final SparseLongArray sPingDelay = new SparseLongArray();
private final Context mContext; private final Context mContext;
private final Account mAccount; private final Account mAccount;
@ -200,6 +203,7 @@ public class ImapService extends Service {
@Override @Override
public void onIdlingDone() { public void onIdlingDone() {
cancelKickIdleConnection(); cancelKickIdleConnection();
cancelPing();
resetPingDelay(); resetPingDelay();
} }
@ -210,6 +214,7 @@ public class ImapService extends Service {
LogUtils.d(LOG_TAG, "Server notified new changes for mailbox " + mMailbox.mId); LogUtils.d(LOG_TAG, "Server notified new changes for mailbox " + mMailbox.mId);
} }
cancelKickIdleConnection(); cancelKickIdleConnection();
cancelPing();
resetPingDelay(); resetPingDelay();
// Request a sync but wait a bit for new incoming messages from server // Request a sync but wait a bit for new incoming messages from server
@ -249,63 +254,50 @@ public class ImapService extends Service {
} }
} }
private void reschedulePing(final long delay) { private void reschedulePing(long delay) {
// Check for connectivity before reschedule // Check for connectivity before reschedule
ConnectivityManager cm = ConnectivityManager cm =
(ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
if (activeNetwork == null || !activeNetwork.isConnected()) { if (activeNetwork == null || !activeNetwork.isConnected()) {
return; cancelPing();
} else {
PendingIntent pi = getIdleRefreshIntent();
AlarmManager am = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
am.set(AlarmManager.RTC_WAKEUP, delay, pi);
} }
}
sExecutor.execute(new Runnable() { private void cancelPing() {
@Override AlarmManager am = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
public void run() { am.cancel(getIdleRefreshIntent());
LogUtils.i(LOG_TAG, "Reschedule delayed ping (" + delay +
") for mailbox " + mMailbox.mId);
try {
Thread.sleep(delay);
} catch (InterruptedException ie) {
}
try {
// Check that the account is ready for push
Account account = Account.restoreAccountWithId(
mContext, mMailbox.mAccountKey);
if (account.getSyncInterval() != Account.CHECK_INTERVAL_PUSH) {
LogUtils.i(LOG_TAG, "Account isn't declared for push: " + account.mId);
return;
}
ImapIdleFolderHolder holder = ImapIdleFolderHolder.getInstance();
holder.registerMailboxForIdle(mContext, account, mMailbox);
// Request a quick sync to make sure we didn't lose any new mails
// during the failure time
ImapService.requestSync(mContext, account, mMailbox.mId, false);
LogUtils.d(LOG_TAG, "requestSync after reschedulePing for account %s (%s)",
account.toString(), mMailbox.mDisplayName);
} catch (MessagingException ex) {
LogUtils.w(LOG_TAG, ex, "Failed to register mailbox for idle. Reschedule.");
reschedulePing(increasePingDelay());
}
}
});
} }
private void resetPingDelay() { private void resetPingDelay() {
int index = sPingDelay.indexOfKey((int) mMailbox.mId); synchronized (sPingDelay) {
if (index >= 0) { int index = sPingDelay.indexOfKey((int) mMailbox.mId);
sPingDelay.removeAt(index); if (index >= 0) {
sPingDelay.removeAt(index);
}
} }
} }
private long increasePingDelay() { private long increasePingDelay() {
long delay = Math.max(RESCHEDULE_PING_DELAY, sPingDelay.get((int) mMailbox.mId)); synchronized (sPingDelay) {
delay = Math.min(MAX_PING_DELAY, delay * 2); long delay = Math.max(RESCHEDULE_PING_DELAY, sPingDelay.get((int) mMailbox.mId));
sPingDelay.put((int) mMailbox.mId, delay); delay = Math.min(MAX_PING_DELAY, delay * 2);
return delay; sPingDelay.put((int) mMailbox.mId, delay);
return delay;
}
}
private PendingIntent getIdleRefreshIntent() {
int requestCode = ALARM_REQUEST_REFRESH_IDLE_CODE + (int) mMailbox.mId;
Intent i = new Intent(mContext, ImapService.class);
i.setAction(ACTION_RESTART_IDLE_CONNECTION);
i.putExtra(EXTRA_MAILBOX, (int) mMailbox.mId);
return PendingIntent.getService(mContext, requestCode, i,
PendingIntent.FLAG_CANCEL_CURRENT);
} }
private void scheduleKickIdleConnection() { private void scheduleKickIdleConnection() {
@ -599,7 +591,7 @@ public class ImapService extends Service {
private PendingIntent getIdleConnectionRestartIntent() { private PendingIntent getIdleConnectionRestartIntent() {
Intent i = new Intent(mContext, ImapService.class); Intent i = new Intent(mContext, ImapService.class);
i.setAction(ACTION_RESTART_IDLE_CONNECTION); i.setAction(ACTION_RESTART_ALL_IDLE_CONNECTIONS);
return PendingIntent.getService(mContext, 0, i, PendingIntent.FLAG_CANCEL_CURRENT); return PendingIntent.getService(mContext, 0, i, PendingIntent.FLAG_CANCEL_CURRENT);
} }
} }
@ -911,7 +903,7 @@ public class ImapService extends Service {
} catch (Exception e) { } catch (Exception e) {
LogUtils.e(Logging.LOG_TAG, "RemoteException " + e); LogUtils.e(Logging.LOG_TAG, "RemoteException " + e);
} }
} else if (ACTION_RESTART_IDLE_CONNECTION.equals(action)) { } else if (ACTION_RESTART_ALL_IDLE_CONNECTIONS.equals(action)) {
// Initiate a sync for all IDLEd accounts, since there might have // Initiate a sync for all IDLEd accounts, since there might have
// been changes while we lost connectivity. At the end of the sync // been changes while we lost connectivity. At the end of the sync
// the IDLE connection will be re-established. // the IDLE connection will be re-established.
@ -936,10 +928,7 @@ public class ImapService extends Service {
// Only imap push accounts // Only imap push accounts
if (account.getSyncInterval() == Account.CHECK_INTERVAL_PUSH if (account.getSyncInterval() == Account.CHECK_INTERVAL_PUSH
&& isLegacyImapProtocol(context, account)) { && isLegacyImapProtocol(context, account)) {
// Request a "recents" sync requestSyncForAccountMailboxesIfNotIdled(account);
requestSync(context, account, Mailbox.NO_MAILBOX, false);
LogUtils.d(LOG_TAG, "requestSync after restarting IDLE "
+ "for account %s", account.toString());
} }
} }
} finally { } finally {
@ -950,6 +939,44 @@ public class ImapService extends Service {
} }
} }
}); });
} else if (ACTION_RESTART_IDLE_CONNECTION.equals(action)) {
final long mailboxId = intent.getLongExtra(EXTRA_MAILBOX, -1);
if (mailboxId < 0) {
return START_NOT_STICKY;
}
mIdleRefreshWakeLock.acquire();
sExecutor.execute(new Runnable() {
@Override
public void run() {
try {
// Check that the account is ready for push
Mailbox mailbox = Mailbox.restoreMailboxWithId(context, mailboxId);
if (mailbox == null) {
return;
}
Account account = Account.restoreAccountWithId(context,
mailbox.mAccountKey);
if (account == null) {
return;
}
if (account.getSyncInterval() != Account.CHECK_INTERVAL_PUSH) {
LogUtils.i(LOG_TAG, "Account isn't declared for push: " + account.mId);
return;
}
// Request a quick sync to make sure we didn't lose any new mails
// during the failure time; IDLE will restart afterwards
ImapService.requestSync(context, account, mailboxId, false);
LogUtils.d(LOG_TAG, "requestSync after reschedulePing for account %s (%s)",
account.toString(), mailbox.mDisplayName);
} finally {
mIdleRefreshWakeLock.release();
}
}
});
} else if (ACTION_KICK_IDLE_CONNECTION.equals(action)) { } else if (ACTION_KICK_IDLE_CONNECTION.equals(action)) {
if (Logging.LOGD) { if (Logging.LOGD) {
LogUtils.d(Logging.LOG_TAG, "action: Send Pending Mail "+accountId); LogUtils.d(Logging.LOG_TAG, "action: Send Pending Mail "+accountId);
@ -990,6 +1017,28 @@ public class ImapService extends Service {
return Service.START_STICKY; return Service.START_STICKY;
} }
private void requestSyncForAccountMailboxesIfNotIdled(Account account) {
Cursor c = Mailbox.getLoopBackMailboxIdsForSync(getContentResolver(), account.mId);
if (c == null) {
return;
}
ImapIdleFolderHolder holder = ImapIdleFolderHolder.getInstance();
try {
while (c.moveToNext()) {
long mailboxId = c.getLong(c.getColumnIndex(BaseColumns._ID));
if (!holder.isMailboxIdled(mailboxId)) {
final Mailbox mailbox = Mailbox.restoreMailboxWithId(this, mailboxId);
requestSync(this, account, mailboxId, false);
LogUtils.d(LOG_TAG, "requestSync after restarting IDLE for account %s (%s)",
account.toString(), mailbox.mDisplayName);
}
}
} finally {
c.close();
}
}
/** /**
* Create our EmailService implementation here. * Create our EmailService implementation here.
*/ */
@ -1666,7 +1715,7 @@ public class ImapService extends Service {
NotificationController nc = null; NotificationController nc = null;
Store remoteStore = null; Store remoteStore = null;
ImapIdleFolderHolder imapHolder = null; final ImapIdleFolderHolder imapHolder = ImapIdleFolderHolder.getInstance();
try { try {
mSyncLock = true; mSyncLock = true;
@ -1676,7 +1725,6 @@ public class ImapService extends Service {
nc = NotificationControllerCreatorHolder.getInstance(ctx); nc = NotificationControllerCreatorHolder.getInstance(ctx);
remoteStore = Store.getInstance(acct, ctx); remoteStore = Store.getInstance(acct, ctx);
imapHolder = ImapIdleFolderHolder.getInstance();
final ContentResolver resolver = ctx.getContentResolver(); final ContentResolver resolver = ctx.getContentResolver();
@ -1898,8 +1946,12 @@ public class ImapService extends Service {
if (remoteStore != null) { if (remoteStore != null) {
remoteStore.closeConnections(); remoteStore.closeConnections();
final boolean registered;
synchronized (imapHolder.mIdledFolders) {
registered = imapHolder.mIdledFolders.indexOfKey((int) mailbox.mId) >= 0;
}
// Register the imap idle again // Register the imap idle again
if (imapHolder != null && acct.getSyncInterval() == Account.CHECK_INTERVAL_PUSH) { if (registered && acct.getSyncInterval() == Account.CHECK_INTERVAL_PUSH) {
imapHolder.registerMailboxForIdle(ctx, acct, mailbox); imapHolder.registerMailboxForIdle(ctx, acct, mailbox);
} }
} }