From 598a070c27d44959e4238c3a16c9b7620d8e852d Mon Sep 17 00:00:00 2001 From: Jorge Ruesga Date: Sat, 4 Apr 2015 20:42:30 +0200 Subject: [PATCH] email: support for auto-sync multiple IMAP folders This enables to auto-sync multiple IMAP folders, not only Inbox. Default to Inbox only. This changes relays in the syncloopback attribute to configure the folders to add to sync process. Change-Id: I8973cfd6ddec33446256bc8b48418558e27596b5 Signed-off-by: Jorge Ruesga --- .../android/emailcommon/provider/Mailbox.java | 17 +++++ .../emailcommon/service/SyncWindow.java | 2 +- .../com/android/email/provider/DBHelper.java | 25 ++++++- .../email/service/EmailServiceStub.java | 5 ++ .../android/email/service/ImapService.java | 39 ++-------- .../service/PopImapSyncAdapterService.java | 72 ++++++++++++++----- res/xml/services.xml | 2 + 7 files changed, 111 insertions(+), 51 deletions(-) diff --git a/emailcommon/src/com/android/emailcommon/provider/Mailbox.java b/emailcommon/src/com/android/emailcommon/provider/Mailbox.java index 952a73147..c726b94c3 100644 --- a/emailcommon/src/com/android/emailcommon/provider/Mailbox.java +++ b/emailcommon/src/com/android/emailcommon/provider/Mailbox.java @@ -299,6 +299,10 @@ public class Mailbox extends EmailContent implements EmailContent.MailboxColumns MailboxColumns.SYNC_INTERVAL + "=1 and " + MailboxColumns.TYPE + "=? and " + MailboxColumns.ACCOUNT_KEY + "=?"; + /** Selection for mailboxes that are configured for sync for an account. */ + private static final String SYNCING_MAILBOXES_FOR_ACCOUNT_SELECTION = + MailboxColumns.SYNC_INTERVAL + "=1 and " + MailboxColumns.ACCOUNT_KEY + "=?"; + // Types of mailboxes. The list is ordered to match a typical UI presentation, e.g. // placing the inbox at the top. // Arrays of "special_mailbox_display_names" and "special_mailbox_icons" are depends on @@ -910,6 +914,19 @@ public class Mailbox extends EmailContent implements EmailContent.MailboxColumns new String[] { Integer.toString(mailboxType), Long.toString(accountId) }, null); } + /** + * Get the mailbox ids for an account that are configured for sync by the user. + * @param cr The {@link ContentResolver}. + * @param accountId The id for the account that is syncing. + * @return A cursor (with one column, containing ids) with all mailbox ids that match. + */ + public static Cursor getLoopBackMailboxIdsForSync(final ContentResolver cr, + final long accountId) { + return cr.query(Mailbox.CONTENT_URI, Mailbox.ID_PROJECTION, + SYNCING_MAILBOXES_FOR_ACCOUNT_SELECTION, + new String[] {Long.toString(accountId) }, null); + } + /** * Get the account id for a mailbox. * @param context The {@link Context}. diff --git a/emailcommon/src/com/android/emailcommon/service/SyncWindow.java b/emailcommon/src/com/android/emailcommon/service/SyncWindow.java index 8dfe4ad2e..af1edea7d 100644 --- a/emailcommon/src/com/android/emailcommon/service/SyncWindow.java +++ b/emailcommon/src/com/android/emailcommon/service/SyncWindow.java @@ -42,7 +42,7 @@ public class SyncWindow { return 365*10; case SYNC_WINDOW_ACCOUNT: default: - return 14; + return 7; } } } diff --git a/provider_src/com/android/email/provider/DBHelper.java b/provider_src/com/android/email/provider/DBHelper.java index 63262a5ec..b5255ac2b 100644 --- a/provider_src/com/android/email/provider/DBHelper.java +++ b/provider_src/com/android/email/provider/DBHelper.java @@ -186,7 +186,8 @@ public final class DBHelper { // Version 126: Decode address lists for To, From, Cc, Bcc and Reply-To columns in Message. // Version 127: Force mFlags to contain the correct flags for EAS accounts given a protocol // version above 12.0 - public static final int DATABASE_VERSION = 128; + // Version 129: Update all IMAP INBOX mailboxes to force synchronization + public static final int DATABASE_VERSION = 129; // Any changes to the database format *must* include update-in-place code. // Original version: 2 @@ -1538,6 +1539,28 @@ public final class DBHelper { LogUtils.w(TAG, "Exception upgrading EmailProvider.db from v127 to v128", e); } } + + // This statement changes the syncInterval column to 1 for all IMAP INBOX mailboxes. + // It does this by matching mailboxes against all account IDs whose receive auth is + // either R.string.protocol_legacy_imap, R.string.protocol_imap or "imap" + // It needed in order to mark + // We do it here to avoid the minor collisions with aosp main db + if (oldVersion <= 129) { + db.execSQL("update " + Mailbox.TABLE_NAME + " set " + + MailboxColumns.SYNC_INTERVAL + "= 1 where " + + MailboxColumns.TYPE + "= " + Mailbox.TYPE_INBOX + " and " + + MailboxColumns.ACCOUNT_KEY + " in (select " + + Account.TABLE_NAME + "." + AccountColumns._ID + " from " + + Account.TABLE_NAME + " join " + HostAuth.TABLE_NAME + " where " + + HostAuth.TABLE_NAME + "." + HostAuthColumns._ID + "=" + + Account.TABLE_NAME + "." + AccountColumns.HOST_AUTH_KEY_RECV + + " and (" + HostAuth.TABLE_NAME + "." + + HostAuthColumns.PROTOCOL + "='" + + mContext.getString(R.string.protocol_legacy_imap) + "' or " + + HostAuth.TABLE_NAME + "." + HostAuthColumns.PROTOCOL + "='" + + mContext.getString(R.string.protocol_imap) + "' or " + + HostAuth.TABLE_NAME + "." + HostAuthColumns.PROTOCOL + "='imap'));"); + } } @Override diff --git a/provider_src/com/android/email/service/EmailServiceStub.java b/provider_src/com/android/email/service/EmailServiceStub.java index ff2c91fc7..e4e757ba9 100755 --- a/provider_src/com/android/email/service/EmailServiceStub.java +++ b/provider_src/com/android/email/service/EmailServiceStub.java @@ -419,6 +419,11 @@ public abstract class EmailServiceStub extends IEmailService.Stub implements IEm mailbox.save(mContext); if (type == Mailbox.TYPE_INBOX) { inboxId = mailbox.mId; + + // In a clean start we must mark the Inbox mailbox as syncable. This + // is required by the new multiple mailboxes sync. Initially Inbox + // should start marked + mailbox.mSyncInterval = 1; } } } diff --git a/provider_src/com/android/email/service/ImapService.java b/provider_src/com/android/email/service/ImapService.java index ffcd15572..34ecfccc7 100644 --- a/provider_src/com/android/email/service/ImapService.java +++ b/provider_src/com/android/email/service/ImapService.java @@ -74,7 +74,6 @@ import java.util.List; public class ImapService extends Service { // TODO get these from configurations or settings. private static final long QUICK_SYNC_WINDOW_MILLIS = DateUtils.DAY_IN_MILLIS; - private static final long FULL_SYNC_WINDOW_MILLIS = 7 * DateUtils.DAY_IN_MILLIS; private static final long FULL_SYNC_INTERVAL_MILLIS = 4 * DateUtils.HOUR_IN_MILLIS; private static final String TAG = "ImapService"; @@ -503,38 +502,12 @@ public class ImapService extends Service { final boolean fullSync = (uiRefresh || loadMore || timeSinceLastFullSync >= FULL_SYNC_INTERVAL_MILLIS || timeSinceLastFullSync < 0); - if (account.mSyncLookback == SyncWindow.SYNC_WINDOW_ALL) { - // This is really for testing. There is no UI that allows setting the sync window for - // IMAP, but it can be set by sending a special intent to AccountSetupFinal activity. - endDate = 0; - } else if (fullSync) { - // Find the oldest message in the local store. We need our time window to include - // all messages that are currently present locally. - endDate = System.currentTimeMillis() - FULL_SYNC_WINDOW_MILLIS; - Cursor localOldestCursor = null; - try { - // b/11520812 Ignore message with timestamp = 0 (which includes NULL) - localOldestCursor = resolver.query(EmailContent.Message.CONTENT_URI, - OldestTimestampInfo.PROJECTION, - EmailContent.MessageColumns.ACCOUNT_KEY + "=?" + " AND " + - MessageColumns.MAILBOX_KEY + "=? AND " + - MessageColumns.TIMESTAMP + "!=0", - new String[] {String.valueOf(account.mId), String.valueOf(mailbox.mId)}, - null); - if (localOldestCursor != null && localOldestCursor.moveToFirst()) { - long oldestLocalMessageDate = localOldestCursor.getLong( - OldestTimestampInfo.COLUMN_OLDEST_TIMESTAMP); - if (oldestLocalMessageDate > 0) { - endDate = Math.min(endDate, oldestLocalMessageDate); - LogUtils.d( - Logging.LOG_TAG, "oldest local message " + oldestLocalMessageDate); - } - } - } finally { - if (localOldestCursor != null) { - localOldestCursor.close(); - } - } + if (fullSync) { + int syncLookBack = mailbox.mSyncLookback == SyncWindow.SYNC_WINDOW_ACCOUNT + ? account.mSyncLookback + : mailbox.mSyncLookback; + endDate = System.currentTimeMillis() - + (SyncWindow.toDays(syncLookBack) * DateUtils.DAY_IN_MILLIS); LogUtils.d(Logging.LOG_TAG, "full sync: original window: now - " + endDate); } else { // We are doing a frequent, quick sync. This only syncs a small time window, so that diff --git a/provider_src/com/android/email/service/PopImapSyncAdapterService.java b/provider_src/com/android/email/service/PopImapSyncAdapterService.java index 08a6f3adb..432cdd107 100644 --- a/provider_src/com/android/email/service/PopImapSyncAdapterService.java +++ b/provider_src/com/android/email/service/PopImapSyncAdapterService.java @@ -31,6 +31,7 @@ import android.os.Bundle; import android.os.IBinder; import com.android.email.R; +import com.android.email.service.EmailServiceUtils.EmailServiceInfo; import com.android.emailcommon.TempDirectory; import com.android.emailcommon.mail.MessagingException; import com.android.emailcommon.provider.Account; @@ -50,6 +51,9 @@ public class PopImapSyncAdapterService extends Service { private static final String TAG = "PopImapSyncService"; private SyncAdapterImpl mSyncAdapter = null; + private static String sPop3Protocol; + private static String sLegacyImapProtocol; + public PopImapSyncAdapterService() { super(); } @@ -82,17 +86,15 @@ public class PopImapSyncAdapterService extends Service { * @return whether or not this mailbox retrieves its data from the server (as opposed to just * a local mailbox that is never synced). */ - private static boolean loadsFromServer(Context context, Mailbox m, String protocol) { - String legacyImapProtocol = context.getString(R.string.protocol_legacy_imap); - String pop3Protocol = context.getString(R.string.protocol_pop3); - if (legacyImapProtocol.equals(protocol)) { + private static boolean loadsFromServer(Context context, Mailbox m, Account acct) { + if (isLegacyImapProtocol(context, acct)) { // TODO: actually use a sync flag when creating the mailboxes. Right now we use an // approximation for IMAP. return m.mType != Mailbox.TYPE_DRAFTS && m.mType != Mailbox.TYPE_OUTBOX && m.mType != Mailbox.TYPE_SEARCH; - } else if (pop3Protocol.equals(protocol)) { + } else if (isPop3Protocol(context, acct)) { return Mailbox.TYPE_INBOX == m.mType; } @@ -108,9 +110,8 @@ public class PopImapSyncAdapterService extends Service { Account account = Account.restoreAccountWithId(context, mailbox.mAccountKey); if (account == null) return; ContentResolver resolver = context.getContentResolver(); - String protocol = account.getProtocol(context); if ((mailbox.mType != Mailbox.TYPE_OUTBOX) && - !loadsFromServer(context, mailbox, protocol)) { + !loadsFromServer(context, mailbox, account)) { // This is an update to a message in a non-syncing mailbox; delete this from the // updates table and return resolver.delete(Message.UPDATED_CONTENT_URI, MessageColumns.MAILBOX_KEY + "=?", @@ -129,7 +130,6 @@ public class PopImapSyncAdapterService extends Service { try { int lastSyncResult; try { - String legacyImapProtocol = context.getString(R.string.protocol_legacy_imap); if (mailbox.mType == Mailbox.TYPE_OUTBOX) { EmailServiceStub.sendMailImpl(context, account.mId); } else { @@ -138,7 +138,7 @@ public class PopImapSyncAdapterService extends Service { EmailServiceStatus.syncMailboxStatus(resolver, extras, mailboxId, EmailServiceStatus.IN_PROGRESS, 0, lastSyncResult); final int status; - if (protocol.equals(legacyImapProtocol)) { + if (isLegacyImapProtocol(context, account)) { status = ImapService.synchronizeMailboxSynchronous(context, account, mailbox, deltaMessageCount != 0, uiRefresh); } else { @@ -240,13 +240,20 @@ public class PopImapSyncAdapterService extends Service { // Get the id for the mailbox we want to sync. long [] mailboxIds = Mailbox.getMailboxIdsFromBundle(extras); if (mailboxIds == null || mailboxIds.length == 0) { - // No mailbox specified, just sync the inbox. - // TODO: IMAP may eventually want to allow multiple auto-sync mailboxes. - final long inboxId = Mailbox.findMailboxOfType(context, acct.mId, - Mailbox.TYPE_INBOX); - if (inboxId != Mailbox.NO_MAILBOX) { - mailboxIds = new long[1]; - mailboxIds[0] = inboxId; + EmailServiceInfo info = EmailServiceUtils.getServiceInfo( + context, acct.getProtocol(context)); + // No mailbox specified, check if the protocol support auto-sync + // multiple mailboxes. In that case retrieve the mailboxes to sync + // from the account settings. Otherwise just sync the inbox. + if (info.offerLookback) { + mailboxIds = getLoopBackMailboxIdsForSync(context, acct); + } else { + final long inboxId = Mailbox.findMailboxOfType(context, acct.mId, + Mailbox.TYPE_INBOX); + if (inboxId != Mailbox.NO_MAILBOX) { + mailboxIds = new long[1]; + mailboxIds[0] = inboxId; + } } } @@ -270,4 +277,37 @@ public class PopImapSyncAdapterService extends Service { } } } + + private static boolean isLegacyImapProtocol(Context ctx, Account acct) { + if (sLegacyImapProtocol == null) { + sLegacyImapProtocol = ctx.getString(R.string.protocol_legacy_imap); + } + return acct.getProtocol(ctx).equals(sLegacyImapProtocol); + } + + private static boolean isPop3Protocol(Context ctx, Account acct) { + if (sPop3Protocol == null) { + sPop3Protocol = ctx.getString(R.string.protocol_pop3); + } + return acct.getProtocol(ctx).equals(sPop3Protocol); + } + + private static long[] getLoopBackMailboxIdsForSync(Context ctx, Account acct) { + final Cursor c = Mailbox.getLoopBackMailboxIdsForSync(ctx.getContentResolver(), acct.mId); + if (c == null) { + // No mailboxes + return null; + } + long[] mailboxes = new long[c.getCount()]; + try { + int i = 0; + while (c.moveToNext()) { + mailboxes[i] = c.getLong(Mailbox.CONTENT_ID_COLUMN); + i++; + } + } finally { + c.close(); + } + return mailboxes; + } } diff --git a/res/xml/services.xml b/res/xml/services.xml index d3e381af2..1bd882306 100644 --- a/res/xml/services.xml +++ b/res/xml/services.xml @@ -87,6 +87,8 @@ email:offerOAuth="true" email:offerLoadMore="true" email:offerMoveTo="true" + email:offerLookback="true" + email:defaultLookback="auto" />