From 23f8d3be7dad09dca750c482ddb03c80780d896d Mon Sep 17 00:00:00 2001 From: Marc Blank Date: Thu, 16 Sep 2010 13:52:41 -0700 Subject: [PATCH] Handle EAS type 1 folders (user-created) * If a type 1 folder has an mail folder as a parent (at any level), it is also a mail folder (and therefore, we should have it in our folder list) * Before rejecting type 1 folders, look for parents and accept those that are mail folders * Add unit test to verify logic Bug: 2978410 Change-Id: I44cda1d1c1fd7f3976af53a1672736201cc995ff --- .../android/email/provider/EmailContent.java | 1 + .../exchange/adapter/FolderSyncParser.java | 137 ++++++++++++++---- .../adapter/FolderSyncParserTests.java | 101 +++++++++++++ 3 files changed, 209 insertions(+), 30 deletions(-) create mode 100644 tests/src/com/android/exchange/adapter/FolderSyncParserTests.java diff --git a/src/com/android/email/provider/EmailContent.java b/src/com/android/email/provider/EmailContent.java index 911eef380..0f91052d7 100644 --- a/src/com/android/email/provider/EmailContent.java +++ b/src/com/android/email/provider/EmailContent.java @@ -2176,6 +2176,7 @@ public abstract class EmailContent { public static final int TYPE_CONTACTS = 0x42; public static final int TYPE_TASKS = 0x43; public static final int TYPE_EAS_ACCOUNT_MAILBOX = 0x44; + public static final int TYPE_UNKNOWN = 0x45; public static final int TYPE_NOT_SYNCABLE = 0x100; // A mailbox that holds Messages that are attachments diff --git a/src/com/android/exchange/adapter/FolderSyncParser.java b/src/com/android/exchange/adapter/FolderSyncParser.java index ed299b0fb..1e8f893d8 100644 --- a/src/com/android/exchange/adapter/FolderSyncParser.java +++ b/src/com/android/exchange/adapter/FolderSyncParser.java @@ -17,6 +17,7 @@ package com.android.exchange.adapter; +import com.android.email.Utility; import com.android.email.provider.AttachmentProvider; import com.android.email.provider.EmailContent; import com.android.email.provider.EmailProvider; @@ -38,6 +39,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; /** @@ -51,7 +53,7 @@ public class FolderSyncParser extends AbstractSyncParser { public static final String TAG = "FolderSyncParser"; // These are defined by the EAS protocol - public static final int USER_FOLDER_TYPE = 1; + public static final int USER_GENERIC_TYPE = 1; public static final int INBOX_TYPE = 2; public static final int DRAFTS_TYPE = 3; public static final int DELETED_TYPE = 4; @@ -64,13 +66,15 @@ public class FolderSyncParser extends AbstractSyncParser { public static final int JOURNAL_TYPE = 11; public static final int USER_MAILBOX_TYPE = 12; - public static final List mValidFolderTypes = Arrays.asList(INBOX_TYPE, DRAFTS_TYPE, - DELETED_TYPE, SENT_TYPE, OUTBOX_TYPE, USER_MAILBOX_TYPE, CALENDAR_TYPE, CONTACTS_TYPE); + // EAS types that we are willing to consider valid folders for EAS sync + public static final List VALID_EAS_FOLDER_TYPES = Arrays.asList(INBOX_TYPE, + DRAFTS_TYPE, DELETED_TYPE, SENT_TYPE, OUTBOX_TYPE, USER_MAILBOX_TYPE, CALENDAR_TYPE, + CONTACTS_TYPE, USER_GENERIC_TYPE); public static final String ALL_BUT_ACCOUNT_MAILBOX = MailboxColumns.ACCOUNT_KEY + "=? and " + MailboxColumns.TYPE + "!=" + Mailbox.TYPE_EAS_ACCOUNT_MAILBOX; - private static final String WHERE_SERVER_ID_AND_ACCOUNT = MailboxColumns.SERVER_ID + "=? and " + + private static final String WHERE_SERVER_ID_AND_ACCOUNT = MailboxColumns.SERVER_ID + "=? and " + MailboxColumns.ACCOUNT_KEY + "=?"; private static final String WHERE_DISPLAY_NAME_AND_ACCOUNT = MailboxColumns.DISPLAY_NAME + @@ -176,7 +180,7 @@ public class FolderSyncParser extends AbstractSyncParser { } } - public void addParser(ArrayList ops) throws IOException { + public Mailbox addParser() throws IOException { String name = null; String serverId = null; String parentId = null; @@ -204,58 +208,100 @@ public class FolderSyncParser extends AbstractSyncParser { skipTag(); } } - if (mValidFolderTypes.contains(type)) { - Mailbox m = new Mailbox(); - m.mDisplayName = name; - m.mServerId = serverId; - m.mAccountKey = mAccountId; - m.mType = Mailbox.TYPE_MAIL; + + if (VALID_EAS_FOLDER_TYPES.contains(type)) { + Mailbox mailbox = new Mailbox(); + mailbox.mDisplayName = name; + mailbox.mServerId = serverId; + mailbox.mAccountKey = mAccountId; + mailbox.mType = Mailbox.TYPE_MAIL; // Note that all mailboxes default to checking "never" (i.e. manual sync only) // We set specific intervals for inbox, contacts, and (eventually) calendar - m.mSyncInterval = Mailbox.CHECK_INTERVAL_NEVER; + mailbox.mSyncInterval = Mailbox.CHECK_INTERVAL_NEVER; switch (type) { case INBOX_TYPE: - m.mType = Mailbox.TYPE_INBOX; - m.mSyncInterval = mAccount.mSyncInterval; + mailbox.mType = Mailbox.TYPE_INBOX; + mailbox.mSyncInterval = mAccount.mSyncInterval; break; case CONTACTS_TYPE: - m.mType = Mailbox.TYPE_CONTACTS; - m.mSyncInterval = mAccount.mSyncInterval; + mailbox.mType = Mailbox.TYPE_CONTACTS; + mailbox.mSyncInterval = mAccount.mSyncInterval; break; case OUTBOX_TYPE: // TYPE_OUTBOX mailboxes are known by ExchangeService to sync whenever they // aren't empty. The value of mSyncFrequency is ignored for this kind of // mailbox. - m.mType = Mailbox.TYPE_OUTBOX; + mailbox.mType = Mailbox.TYPE_OUTBOX; break; case SENT_TYPE: - m.mType = Mailbox.TYPE_SENT; + mailbox.mType = Mailbox.TYPE_SENT; break; case DRAFTS_TYPE: - m.mType = Mailbox.TYPE_DRAFTS; + mailbox.mType = Mailbox.TYPE_DRAFTS; break; case DELETED_TYPE: - m.mType = Mailbox.TYPE_TRASH; + mailbox.mType = Mailbox.TYPE_TRASH; break; case CALENDAR_TYPE: - m.mType = Mailbox.TYPE_CALENDAR; - m.mSyncInterval = mAccount.mSyncInterval; + mailbox.mType = Mailbox.TYPE_CALENDAR; + mailbox.mSyncInterval = mAccount.mSyncInterval; + break; + case USER_GENERIC_TYPE: + mailbox.mType = Mailbox.TYPE_UNKNOWN; break; } // Make boxes like Contacts and Calendar invisible in the folder list - m.mFlagVisible = (m.mType < Mailbox.TYPE_NOT_EMAIL); + mailbox.mFlagVisible = (mailbox.mType < Mailbox.TYPE_NOT_EMAIL); if (!parentId.equals("0")) { - m.mParentServerId = parentId; + mailbox.mParentServerId = parentId; } - userLog("Adding mailbox: ", m.mDisplayName); - ops.add(ContentProviderOperation - .newInsert(Mailbox.CONTENT_URI).withValues(m.toContentValues()).build()); + return mailbox; } + return null; + } - return; + /** + * Determine whether a given mailbox holds mail, rather than other data. We do this by first + * checking the type of the mailbox (if it's a known good type, great; if it's a known bad + * type, return false). If it's unknown, we check the parent, first by trying to find it in + * the current set of newly synced items, and then by looking it up in EmailProvider. If + * we can find the parent, we use the same rules to determine if it holds mail; if it does, + * then its children do as well, so that's a go. + * + * @param mailbox the mailbox we're checking + * @param mailboxMap a HashMap relating server id's of mailboxes in the current sync set to + * the corresponding mailbox structures + * @return whether or not the mailbox contains email (rather than PIM or unknown data) + */ + /*package*/ boolean isValidMailFolder(Mailbox mailbox, HashMap mailboxMap) { + int folderType = mailbox.mType; + // Automatically accept our email types + if (folderType < Mailbox.TYPE_NOT_EMAIL) return true; + // Automatically reject everything else but "unknown" + if (folderType != Mailbox.TYPE_UNKNOWN) return false; + // If this is TYPE_UNKNOWN, check the parent + Mailbox parent = mailboxMap.get(mailbox.mParentServerId); + // If the parent is in the map, then check it out; if not, it could be an existing saved + // Mailbox, so we'll have to query the database + if (parent == null) { + mBindArguments[0] = Long.toString(mAccount.mId); + mBindArguments[1] = mailbox.mParentServerId; + long parentId = Utility.getFirstRowInt(mContext, Mailbox.CONTENT_URI, + EmailContent.ID_PROJECTION, + MailboxColumns.ACCOUNT_KEY + "=? AND " + MailboxColumns.SERVER_ID + "=?", + mBindArguments, null, EmailContent.ID_PROJECTION_COLUMN, -1); + if (parentId != -1) { + // Get the parent from the database + parent = Mailbox.restoreMailboxWithId(mContext, parentId); + if (parent == null) return false; + } else { + return false; + } + } + return isValidMailFolder(parent, mailboxMap); } public void updateParser(ArrayList ops) throws IOException { @@ -304,12 +350,27 @@ public class FolderSyncParser extends AbstractSyncParser { } public void changesParser() throws IOException { - // Keep track of new boxes, deleted boxes, updated boxes ArrayList ops = new ArrayList(); + // Mailboxes that we known contain email + ArrayList validMailboxes = new ArrayList(); + // Mailboxes that we're unsure about + ArrayList userMailboxes = new ArrayList(); + // Maps folder serverId to mailbox type + HashMap mailboxMap = new HashMap(); while (nextTag(Tags.FOLDER_CHANGES) != END) { if (tag == Tags.FOLDER_ADD) { - addParser(ops); + Mailbox mailbox = addParser(); + if (mailbox != null) { + // Save away the type of this folder + mailboxMap.put(mailbox.mServerId, mailbox); + // And add the mailbox to the proper list + if (type == USER_MAILBOX_TYPE) { + userMailboxes.add(mailbox); + } else { + validMailboxes.add(mailbox); + } + } } else if (tag == Tags.FOLDER_DELETE) { deleteParser(ops); } else if (tag == Tags.FOLDER_UPDATE) { @@ -328,6 +389,22 @@ public class FolderSyncParser extends AbstractSyncParser { return; } + // Go through the generic user mailboxes; we'll call them valid if any parent is valid + for (Mailbox m: userMailboxes) { + if (isValidMailFolder(m, mailboxMap)) { + m.mType = Mailbox.TYPE_MAIL; + validMailboxes.add(m); + } else { + userLog("Rejecting unknown type mailbox: " + m.mDisplayName); + } + } + + for (Mailbox m: validMailboxes) { + userLog("Adding mailbox: ", m.mDisplayName); + ops.add(ContentProviderOperation + .newInsert(Mailbox.CONTENT_URI).withValues(m.toContentValues()).build()); + } + // Create the new mailboxes in a single batch operation // Don't save any data if the service has been stopped synchronized (mService.getSynchronizer()) { diff --git a/tests/src/com/android/exchange/adapter/FolderSyncParserTests.java b/tests/src/com/android/exchange/adapter/FolderSyncParserTests.java new file mode 100644 index 000000000..9de9b8f35 --- /dev/null +++ b/tests/src/com/android/exchange/adapter/FolderSyncParserTests.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2010 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.adapter; + +import com.android.email.provider.ProviderTestUtils; +import com.android.email.provider.EmailContent.Account; +import com.android.email.provider.EmailContent.Mailbox; +import com.android.exchange.EasSyncService; + +import java.io.IOException; +import java.util.HashMap; + +/** + * You can run this entire test case with: + * runtest -c com.android.exchange.adapter.FolderSyncParserTests email + */ + +public class FolderSyncParserTests extends SyncAdapterTestCase { + + public FolderSyncParserTests() { + super(); + } + + public void testIsValidMailFolder() throws IOException { + EasSyncService service = getTestService(); + EmailSyncAdapter adapter = new EmailSyncAdapter(service.mMailbox, service); + FolderSyncParser parser = new FolderSyncParser(getTestInputStream(), adapter); + HashMap mailboxMap = new HashMap(); + Account acct = ProviderTestUtils.setupAccount("account", true, mMockContext); + // The parser needs the mAccount set + parser.mAccount = acct; + + // Don't save the box; just create it, and give it a server id + Mailbox boxMailType = ProviderTestUtils.setupMailbox("box1", acct.mId, false, mMockContext, + Mailbox.TYPE_MAIL); + boxMailType.mServerId = "1:1"; + // Automatically valid since TYPE_MAIL + assertTrue(parser.isValidMailFolder(boxMailType, mailboxMap)); + + Mailbox boxCalendarType = ProviderTestUtils.setupMailbox("box", acct.mId, false, + mMockContext, Mailbox.TYPE_CALENDAR); + Mailbox boxContactsType = ProviderTestUtils.setupMailbox("box", acct.mId, false, + mMockContext, Mailbox.TYPE_CONTACTS); + Mailbox boxTasksType = ProviderTestUtils.setupMailbox("box", acct.mId, false, + mMockContext, Mailbox.TYPE_TASKS); + // Automatically invalid since TYPE_CALENDAR and TYPE_CONTACTS + assertFalse(parser.isValidMailFolder(boxCalendarType, mailboxMap)); + assertFalse(parser.isValidMailFolder(boxContactsType, mailboxMap)); + assertFalse(parser.isValidMailFolder(boxTasksType, mailboxMap)); + + // Unknown boxes are invalid unless they have a parent that's valid + Mailbox boxUnknownType = ProviderTestUtils.setupMailbox("box", acct.mId, false, + mMockContext, Mailbox.TYPE_UNKNOWN); + assertFalse(parser.isValidMailFolder(boxUnknownType, mailboxMap)); + boxUnknownType.mParentServerId = boxMailType.mServerId; + // We shouldn't find the parent yet + assertFalse(parser.isValidMailFolder(boxUnknownType, mailboxMap)); + // Put the mailbox in the map; the unknown box should now be valid + mailboxMap.put(boxMailType.mServerId, boxMailType); + assertTrue(parser.isValidMailFolder(boxUnknownType, mailboxMap)); + + // Clear the map, but save away the parent box + mailboxMap.clear(); + assertFalse(parser.isValidMailFolder(boxUnknownType, mailboxMap)); + boxMailType.save(mMockContext); + // The box should now be valid + assertTrue(parser.isValidMailFolder(boxUnknownType, mailboxMap)); + + // Somewhat harder case. The parent will be in the map, but also unknown. The parent's + // parent will be in the database. + Mailbox boxParentUnknownType = ProviderTestUtils.setupMailbox("box", acct.mId, false, + mMockContext, Mailbox.TYPE_UNKNOWN); + assertFalse(parser.isValidMailFolder(boxParentUnknownType, mailboxMap)); + // Give the unknown type parent a parent (boxMailType) + boxParentUnknownType.mServerId = "1:2"; + boxParentUnknownType.mParentServerId = boxMailType.mServerId; + // Give our unknown box an unknown parent + boxUnknownType.mParentServerId = boxParentUnknownType.mServerId; + // Confirm the box is still invalid + assertFalse(parser.isValidMailFolder(boxUnknownType, mailboxMap)); + // Put the unknown type parent into the mailbox map + mailboxMap.put(boxParentUnknownType.mServerId, boxParentUnknownType); + // Our unknown box should now be valid, because 1) the parent is unknown, BUT 2) the + // parent's parent is a mail type + assertTrue(parser.isValidMailFolder(boxUnknownType, mailboxMap)); + } +}