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
This commit is contained in:
parent
250ca15b88
commit
23f8d3be7d
|
@ -2176,6 +2176,7 @@ public abstract class EmailContent {
|
||||||
public static final int TYPE_CONTACTS = 0x42;
|
public static final int TYPE_CONTACTS = 0x42;
|
||||||
public static final int TYPE_TASKS = 0x43;
|
public static final int TYPE_TASKS = 0x43;
|
||||||
public static final int TYPE_EAS_ACCOUNT_MAILBOX = 0x44;
|
public static final int TYPE_EAS_ACCOUNT_MAILBOX = 0x44;
|
||||||
|
public static final int TYPE_UNKNOWN = 0x45;
|
||||||
|
|
||||||
public static final int TYPE_NOT_SYNCABLE = 0x100;
|
public static final int TYPE_NOT_SYNCABLE = 0x100;
|
||||||
// A mailbox that holds Messages that are attachments
|
// A mailbox that holds Messages that are attachments
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
package com.android.exchange.adapter;
|
package com.android.exchange.adapter;
|
||||||
|
|
||||||
|
import com.android.email.Utility;
|
||||||
import com.android.email.provider.AttachmentProvider;
|
import com.android.email.provider.AttachmentProvider;
|
||||||
import com.android.email.provider.EmailContent;
|
import com.android.email.provider.EmailContent;
|
||||||
import com.android.email.provider.EmailProvider;
|
import com.android.email.provider.EmailProvider;
|
||||||
|
@ -38,6 +39,7 @@ import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -51,7 +53,7 @@ public class FolderSyncParser extends AbstractSyncParser {
|
||||||
public static final String TAG = "FolderSyncParser";
|
public static final String TAG = "FolderSyncParser";
|
||||||
|
|
||||||
// These are defined by the EAS protocol
|
// 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 INBOX_TYPE = 2;
|
||||||
public static final int DRAFTS_TYPE = 3;
|
public static final int DRAFTS_TYPE = 3;
|
||||||
public static final int DELETED_TYPE = 4;
|
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 JOURNAL_TYPE = 11;
|
||||||
public static final int USER_MAILBOX_TYPE = 12;
|
public static final int USER_MAILBOX_TYPE = 12;
|
||||||
|
|
||||||
public static final List<Integer> mValidFolderTypes = Arrays.asList(INBOX_TYPE, DRAFTS_TYPE,
|
// EAS types that we are willing to consider valid folders for EAS sync
|
||||||
DELETED_TYPE, SENT_TYPE, OUTBOX_TYPE, USER_MAILBOX_TYPE, CALENDAR_TYPE, CONTACTS_TYPE);
|
public static final List<Integer> 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 " +
|
public static final String ALL_BUT_ACCOUNT_MAILBOX = MailboxColumns.ACCOUNT_KEY + "=? and " +
|
||||||
MailboxColumns.TYPE + "!=" + Mailbox.TYPE_EAS_ACCOUNT_MAILBOX;
|
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 + "=?";
|
MailboxColumns.ACCOUNT_KEY + "=?";
|
||||||
|
|
||||||
private static final String WHERE_DISPLAY_NAME_AND_ACCOUNT = MailboxColumns.DISPLAY_NAME +
|
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<ContentProviderOperation> ops) throws IOException {
|
public Mailbox addParser() throws IOException {
|
||||||
String name = null;
|
String name = null;
|
||||||
String serverId = null;
|
String serverId = null;
|
||||||
String parentId = null;
|
String parentId = null;
|
||||||
|
@ -204,58 +208,100 @@ public class FolderSyncParser extends AbstractSyncParser {
|
||||||
skipTag();
|
skipTag();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (mValidFolderTypes.contains(type)) {
|
|
||||||
Mailbox m = new Mailbox();
|
if (VALID_EAS_FOLDER_TYPES.contains(type)) {
|
||||||
m.mDisplayName = name;
|
Mailbox mailbox = new Mailbox();
|
||||||
m.mServerId = serverId;
|
mailbox.mDisplayName = name;
|
||||||
m.mAccountKey = mAccountId;
|
mailbox.mServerId = serverId;
|
||||||
m.mType = Mailbox.TYPE_MAIL;
|
mailbox.mAccountKey = mAccountId;
|
||||||
|
mailbox.mType = Mailbox.TYPE_MAIL;
|
||||||
// Note that all mailboxes default to checking "never" (i.e. manual sync only)
|
// Note that all mailboxes default to checking "never" (i.e. manual sync only)
|
||||||
// We set specific intervals for inbox, contacts, and (eventually) calendar
|
// We set specific intervals for inbox, contacts, and (eventually) calendar
|
||||||
m.mSyncInterval = Mailbox.CHECK_INTERVAL_NEVER;
|
mailbox.mSyncInterval = Mailbox.CHECK_INTERVAL_NEVER;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case INBOX_TYPE:
|
case INBOX_TYPE:
|
||||||
m.mType = Mailbox.TYPE_INBOX;
|
mailbox.mType = Mailbox.TYPE_INBOX;
|
||||||
m.mSyncInterval = mAccount.mSyncInterval;
|
mailbox.mSyncInterval = mAccount.mSyncInterval;
|
||||||
break;
|
break;
|
||||||
case CONTACTS_TYPE:
|
case CONTACTS_TYPE:
|
||||||
m.mType = Mailbox.TYPE_CONTACTS;
|
mailbox.mType = Mailbox.TYPE_CONTACTS;
|
||||||
m.mSyncInterval = mAccount.mSyncInterval;
|
mailbox.mSyncInterval = mAccount.mSyncInterval;
|
||||||
break;
|
break;
|
||||||
case OUTBOX_TYPE:
|
case OUTBOX_TYPE:
|
||||||
// TYPE_OUTBOX mailboxes are known by ExchangeService to sync whenever they
|
// TYPE_OUTBOX mailboxes are known by ExchangeService to sync whenever they
|
||||||
// aren't empty. The value of mSyncFrequency is ignored for this kind of
|
// aren't empty. The value of mSyncFrequency is ignored for this kind of
|
||||||
// mailbox.
|
// mailbox.
|
||||||
m.mType = Mailbox.TYPE_OUTBOX;
|
mailbox.mType = Mailbox.TYPE_OUTBOX;
|
||||||
break;
|
break;
|
||||||
case SENT_TYPE:
|
case SENT_TYPE:
|
||||||
m.mType = Mailbox.TYPE_SENT;
|
mailbox.mType = Mailbox.TYPE_SENT;
|
||||||
break;
|
break;
|
||||||
case DRAFTS_TYPE:
|
case DRAFTS_TYPE:
|
||||||
m.mType = Mailbox.TYPE_DRAFTS;
|
mailbox.mType = Mailbox.TYPE_DRAFTS;
|
||||||
break;
|
break;
|
||||||
case DELETED_TYPE:
|
case DELETED_TYPE:
|
||||||
m.mType = Mailbox.TYPE_TRASH;
|
mailbox.mType = Mailbox.TYPE_TRASH;
|
||||||
break;
|
break;
|
||||||
case CALENDAR_TYPE:
|
case CALENDAR_TYPE:
|
||||||
m.mType = Mailbox.TYPE_CALENDAR;
|
mailbox.mType = Mailbox.TYPE_CALENDAR;
|
||||||
m.mSyncInterval = mAccount.mSyncInterval;
|
mailbox.mSyncInterval = mAccount.mSyncInterval;
|
||||||
|
break;
|
||||||
|
case USER_GENERIC_TYPE:
|
||||||
|
mailbox.mType = Mailbox.TYPE_UNKNOWN;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make boxes like Contacts and Calendar invisible in the folder list
|
// 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")) {
|
if (!parentId.equals("0")) {
|
||||||
m.mParentServerId = parentId;
|
mailbox.mParentServerId = parentId;
|
||||||
}
|
}
|
||||||
|
|
||||||
userLog("Adding mailbox: ", m.mDisplayName);
|
return mailbox;
|
||||||
ops.add(ContentProviderOperation
|
|
||||||
.newInsert(Mailbox.CONTENT_URI).withValues(m.toContentValues()).build());
|
|
||||||
}
|
}
|
||||||
|
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<String, Mailbox> 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<ContentProviderOperation> ops) throws IOException {
|
public void updateParser(ArrayList<ContentProviderOperation> ops) throws IOException {
|
||||||
|
@ -304,12 +350,27 @@ public class FolderSyncParser extends AbstractSyncParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void changesParser() throws IOException {
|
public void changesParser() throws IOException {
|
||||||
// Keep track of new boxes, deleted boxes, updated boxes
|
|
||||||
ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
|
ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
|
||||||
|
// Mailboxes that we known contain email
|
||||||
|
ArrayList<Mailbox> validMailboxes = new ArrayList<Mailbox>();
|
||||||
|
// Mailboxes that we're unsure about
|
||||||
|
ArrayList<Mailbox> userMailboxes = new ArrayList<Mailbox>();
|
||||||
|
// Maps folder serverId to mailbox type
|
||||||
|
HashMap<String, Mailbox> mailboxMap = new HashMap<String, Mailbox>();
|
||||||
|
|
||||||
while (nextTag(Tags.FOLDER_CHANGES) != END) {
|
while (nextTag(Tags.FOLDER_CHANGES) != END) {
|
||||||
if (tag == Tags.FOLDER_ADD) {
|
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) {
|
} else if (tag == Tags.FOLDER_DELETE) {
|
||||||
deleteParser(ops);
|
deleteParser(ops);
|
||||||
} else if (tag == Tags.FOLDER_UPDATE) {
|
} else if (tag == Tags.FOLDER_UPDATE) {
|
||||||
|
@ -328,6 +389,22 @@ public class FolderSyncParser extends AbstractSyncParser {
|
||||||
return;
|
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
|
// Create the new mailboxes in a single batch operation
|
||||||
// Don't save any data if the service has been stopped
|
// Don't save any data if the service has been stopped
|
||||||
synchronized (mService.getSynchronizer()) {
|
synchronized (mService.getSynchronizer()) {
|
||||||
|
|
|
@ -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<EmailSyncAdapter> {
|
||||||
|
|
||||||
|
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<String, Mailbox> mailboxMap = new HashMap<String, Mailbox>();
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue