diff --git a/provider_src/com/android/email/service/ImapService.java b/provider_src/com/android/email/service/ImapService.java index 4396c532e..f7fe3f894 100644 --- a/provider_src/com/android/email/service/ImapService.java +++ b/provider_src/com/android/email/service/ImapService.java @@ -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_MAX_DELAY = 3 * 60 * 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 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"; private static final String 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 = "com.android.email.intent.action.KICK_IDLE_CONNECTION"; 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 SparseLongArray sPingDelay = new SparseLongArray(); private static String sLegacyImapProtocol; @@ -180,6 +182,7 @@ public class ImapService extends Service { } private static class ImapIdleListener implements ImapFolder.IdleCallback { + private static final SparseLongArray sPingDelay = new SparseLongArray(); private final Context mContext; private final Account mAccount; @@ -200,6 +203,7 @@ public class ImapService extends Service { @Override public void onIdlingDone() { cancelKickIdleConnection(); + cancelPing(); resetPingDelay(); } @@ -210,6 +214,7 @@ public class ImapService extends Service { LogUtils.d(LOG_TAG, "Server notified new changes for mailbox " + mMailbox.mId); } cancelKickIdleConnection(); + cancelPing(); resetPingDelay(); // 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 ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); 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() { - @Override - public void run() { - 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 cancelPing() { + AlarmManager am = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); + am.cancel(getIdleRefreshIntent()); } private void resetPingDelay() { - int index = sPingDelay.indexOfKey((int) mMailbox.mId); - if (index >= 0) { - sPingDelay.removeAt(index); + synchronized (sPingDelay) { + int index = sPingDelay.indexOfKey((int) mMailbox.mId); + if (index >= 0) { + sPingDelay.removeAt(index); + } } } private long increasePingDelay() { - long delay = Math.max(RESCHEDULE_PING_DELAY, sPingDelay.get((int) mMailbox.mId)); - delay = Math.min(MAX_PING_DELAY, delay * 2); - sPingDelay.put((int) mMailbox.mId, delay); - return delay; + synchronized (sPingDelay) { + long delay = Math.max(RESCHEDULE_PING_DELAY, sPingDelay.get((int) mMailbox.mId)); + delay = Math.min(MAX_PING_DELAY, delay * 2); + 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() { @@ -599,7 +591,7 @@ public class ImapService extends Service { private PendingIntent getIdleConnectionRestartIntent() { 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); } } @@ -911,7 +903,7 @@ public class ImapService extends Service { } catch (Exception 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 // been changes while we lost connectivity. At the end of the sync // the IDLE connection will be re-established. @@ -936,10 +928,7 @@ public class ImapService extends Service { // Only imap push accounts if (account.getSyncInterval() == Account.CHECK_INTERVAL_PUSH && isLegacyImapProtocol(context, account)) { - // Request a "recents" sync - requestSync(context, account, Mailbox.NO_MAILBOX, false); - LogUtils.d(LOG_TAG, "requestSync after restarting IDLE " - + "for account %s", account.toString()); + requestSyncForAccountMailboxesIfNotIdled(account); } } } 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)) { if (Logging.LOGD) { LogUtils.d(Logging.LOG_TAG, "action: Send Pending Mail "+accountId); @@ -990,6 +1017,28 @@ public class ImapService extends Service { 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. */ @@ -1666,7 +1715,7 @@ public class ImapService extends Service { NotificationController nc = null; Store remoteStore = null; - ImapIdleFolderHolder imapHolder = null; + final ImapIdleFolderHolder imapHolder = ImapIdleFolderHolder.getInstance(); try { mSyncLock = true; @@ -1676,7 +1725,6 @@ public class ImapService extends Service { nc = NotificationControllerCreatorHolder.getInstance(ctx); remoteStore = Store.getInstance(acct, ctx); - imapHolder = ImapIdleFolderHolder.getInstance(); final ContentResolver resolver = ctx.getContentResolver(); @@ -1898,8 +1946,12 @@ public class ImapService extends Service { if (remoteStore != null) { remoteStore.closeConnections(); + final boolean registered; + synchronized (imapHolder.mIdledFolders) { + registered = imapHolder.mIdledFolders.indexOfKey((int) mailbox.mId) >= 0; + } // 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); } }