Home | History | Annotate | Download | only in adapter
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.exchange.adapter;
     18 
     19 import android.content.ContentResolver;
     20 import android.content.res.AssetManager;
     21 import android.database.Cursor;
     22 import android.test.suitebuilder.annotation.MediumTest;
     23 
     24 import com.android.emailcommon.provider.Account;
     25 import com.android.emailcommon.provider.Mailbox;
     26 import com.android.emailcommon.service.SyncWindow;
     27 import com.android.exchange.CommandStatusException;
     28 import com.android.exchange.EasSyncService;
     29 import com.android.exchange.provider.EmailContentSetupUtils;
     30 
     31 import java.io.BufferedReader;
     32 import java.io.IOException;
     33 import java.io.InputStream;
     34 import java.io.InputStreamReader;
     35 import java.util.HashMap;
     36 
     37 /**
     38  * You can run this entire test case with:
     39  *   runtest -c com.android.exchange.adapter.FolderSyncParserTests exchange
     40  */
     41 @MediumTest
     42 public class FolderSyncParserTests extends SyncAdapterTestCase<EmailSyncAdapter> {
     43 
     44     // We increment this to generate unique server id's
     45     private int mServerIdCount = 0;
     46     private final long mCreationTime = System.currentTimeMillis();
     47     private final String[] mMailboxQueryArgs = new String[2];
     48 
     49     public FolderSyncParserTests() {
     50         super();
     51     }
     52 
     53     public void testIsValidMailFolder() throws IOException {
     54         EasSyncService service = getTestService();
     55         EmailSyncAdapter adapter = new EmailSyncAdapter(service);
     56         FolderSyncParser parser = new FolderSyncParser(getTestInputStream(), adapter);
     57         HashMap<String, Mailbox> mailboxMap = new HashMap<String, Mailbox>();
     58         // The parser needs the mAccount set
     59         parser.mAccount = mAccount;
     60         mAccount.save(getContext());
     61 
     62         // Don't save the box; just create it, and give it a server id
     63         Mailbox boxMailType = EmailContentSetupUtils.setupMailbox("box1", mAccount.mId, false,
     64                 mProviderContext, Mailbox.TYPE_MAIL);
     65         boxMailType.mServerId = "__1:1";
     66         // Automatically valid since TYPE_MAIL
     67         assertTrue(parser.isValidMailFolder(boxMailType, mailboxMap));
     68 
     69         Mailbox boxCalendarType = EmailContentSetupUtils.setupMailbox("box", mAccount.mId, false,
     70                 mProviderContext, Mailbox.TYPE_CALENDAR);
     71         Mailbox boxContactsType = EmailContentSetupUtils.setupMailbox("box", mAccount.mId, false,
     72                 mProviderContext, Mailbox.TYPE_CONTACTS);
     73         Mailbox boxTasksType = EmailContentSetupUtils.setupMailbox("box", mAccount.mId, false,
     74                 mProviderContext, Mailbox.TYPE_TASKS);
     75         // Automatically invalid since TYPE_CALENDAR and TYPE_CONTACTS
     76         assertFalse(parser.isValidMailFolder(boxCalendarType, mailboxMap));
     77         assertFalse(parser.isValidMailFolder(boxContactsType, mailboxMap));
     78         assertFalse(parser.isValidMailFolder(boxTasksType, mailboxMap));
     79 
     80         // Unknown boxes are invalid unless they have a parent that's valid
     81         Mailbox boxUnknownType = EmailContentSetupUtils.setupMailbox("box", mAccount.mId, false,
     82                 mProviderContext, Mailbox.TYPE_UNKNOWN);
     83         assertFalse(parser.isValidMailFolder(boxUnknownType, mailboxMap));
     84         boxUnknownType.mParentServerId = boxMailType.mServerId;
     85         // We shouldn't find the parent yet
     86         assertFalse(parser.isValidMailFolder(boxUnknownType, mailboxMap));
     87         // Put the mailbox in the map; the unknown box should now be valid
     88         mailboxMap.put(boxMailType.mServerId, boxMailType);
     89         assertTrue(parser.isValidMailFolder(boxUnknownType, mailboxMap));
     90 
     91         // Clear the map, but save away the parent box
     92         mailboxMap.clear();
     93         assertFalse(parser.isValidMailFolder(boxUnknownType, mailboxMap));
     94         boxMailType.save(mProviderContext);
     95         // The box should now be valid
     96         assertTrue(parser.isValidMailFolder(boxUnknownType, mailboxMap));
     97 
     98         // Somewhat harder case.  The parent will be in the map, but also unknown.  The parent's
     99         // parent will be in the database.
    100         Mailbox boxParentUnknownType = EmailContentSetupUtils.setupMailbox("box", mAccount.mId,
    101                 false, mProviderContext, Mailbox.TYPE_UNKNOWN);
    102         assertFalse(parser.isValidMailFolder(boxParentUnknownType, mailboxMap));
    103         // Give the unknown type parent a parent (boxMailType)
    104         boxParentUnknownType.mServerId = "__1:2";
    105         boxParentUnknownType.mParentServerId = boxMailType.mServerId;
    106         // Give our unknown box an unknown parent
    107         boxUnknownType.mParentServerId = boxParentUnknownType.mServerId;
    108         // Confirm the box is still invalid
    109         assertFalse(parser.isValidMailFolder(boxUnknownType, mailboxMap));
    110         // Put the unknown type parent into the mailbox map
    111         mailboxMap.put(boxParentUnknownType.mServerId, boxParentUnknownType);
    112         // Our unknown box should now be valid, because 1) the parent is unknown, BUT 2) the
    113         // parent's parent is a mail type
    114         assertTrue(parser.isValidMailFolder(boxUnknownType, mailboxMap));
    115     }
    116 
    117     private Mailbox setupBoxSync(int interval, int lookback, String serverId) {
    118         // Don't save the box; just create it, and give it a server id
    119         Mailbox box = EmailContentSetupUtils.setupMailbox("box1", mAccount.mId, false,
    120                 mProviderContext, Mailbox.TYPE_MAIL);
    121         box.mSyncInterval = interval;
    122         box.mSyncLookback = lookback;
    123         if (serverId != null) {
    124             box.mServerId = serverId;
    125         } else {
    126             box.mServerId = "serverId-" + mCreationTime + '-' + mServerIdCount++;
    127         }
    128         box.save(mProviderContext);
    129         return box;
    130     }
    131 
    132     private boolean syncOptionsSame(Mailbox a, Mailbox b) {
    133         if (a.mSyncInterval != b.mSyncInterval) return false;
    134         if (a.mSyncLookback != b.mSyncLookback) return false;
    135         return true;
    136     }
    137 
    138     public void testSaveAndRestoreMailboxSyncOptions() throws IOException {
    139         EasSyncService service = getTestService();
    140         EmailSyncAdapter adapter = new EmailSyncAdapter(service);
    141         FolderSyncParser parser = new FolderSyncParser(getTestInputStream(), adapter);
    142         mAccount.save(mProviderContext);
    143 
    144         parser.mAccount = mAccount;
    145         parser.mAccountId = mAccount.mId;
    146         parser.mAccountIdAsString = Long.toString(mAccount.mId);
    147         parser.mContext = mProviderContext;
    148         parser.mContentResolver = mProviderContext.getContentResolver();
    149 
    150         // Don't save the box; just create it, and give it a server id
    151         Mailbox box1 = setupBoxSync(Account.CHECK_INTERVAL_NEVER, SyncWindow.SYNC_WINDOW_UNKNOWN,
    152                 null);
    153         Mailbox box2 = setupBoxSync(Account.CHECK_INTERVAL_NEVER, SyncWindow.SYNC_WINDOW_UNKNOWN,
    154                 null);
    155         Mailbox boxa = setupBoxSync(Account.CHECK_INTERVAL_NEVER, SyncWindow.SYNC_WINDOW_1_MONTH,
    156                 null);
    157         Mailbox boxb = setupBoxSync(Account.CHECK_INTERVAL_NEVER, SyncWindow.SYNC_WINDOW_2_WEEKS,
    158                 null);
    159         Mailbox boxc = setupBoxSync(Account.CHECK_INTERVAL_PUSH, SyncWindow.SYNC_WINDOW_UNKNOWN,
    160                 null);
    161         Mailbox boxd = setupBoxSync(Account.CHECK_INTERVAL_PUSH, SyncWindow.SYNC_WINDOW_UNKNOWN,
    162                 null);
    163         Mailbox boxe = setupBoxSync(Account.CHECK_INTERVAL_PUSH, SyncWindow.SYNC_WINDOW_1_DAY,
    164                 null);
    165 
    166         // Save the options (for a, b, c, d, e);
    167         parser.saveMailboxSyncOptions();
    168         // There should be 5 entries in the map, and they should be the correct ones
    169         assertNotNull(parser.mSyncOptionsMap.get(boxa.mServerId));
    170         assertNotNull(parser.mSyncOptionsMap.get(boxb.mServerId));
    171         assertNotNull(parser.mSyncOptionsMap.get(boxc.mServerId));
    172         assertNotNull(parser.mSyncOptionsMap.get(boxd.mServerId));
    173         assertNotNull(parser.mSyncOptionsMap.get(boxe.mServerId));
    174 
    175         // Delete all the mailboxes in the account
    176         ContentResolver cr = mProviderContext.getContentResolver();
    177         cr.delete(Mailbox.CONTENT_URI, Mailbox.ACCOUNT_KEY + "=?",
    178                 new String[] {parser.mAccountIdAsString});
    179 
    180         // Create new boxes, all with default values for interval & window
    181         Mailbox box1x = setupBoxSync(Account.CHECK_INTERVAL_NEVER, SyncWindow.SYNC_WINDOW_UNKNOWN,
    182                 box1.mServerId);
    183         Mailbox box2x = setupBoxSync(Account.CHECK_INTERVAL_NEVER, SyncWindow.SYNC_WINDOW_UNKNOWN,
    184                 box2.mServerId);
    185         Mailbox boxax = setupBoxSync(Account.CHECK_INTERVAL_NEVER, SyncWindow.SYNC_WINDOW_UNKNOWN,
    186                 boxa.mServerId);
    187         Mailbox boxbx = setupBoxSync(Account.CHECK_INTERVAL_NEVER, SyncWindow.SYNC_WINDOW_UNKNOWN,
    188                 boxb.mServerId);
    189         Mailbox boxcx = setupBoxSync(Account.CHECK_INTERVAL_NEVER, SyncWindow.SYNC_WINDOW_UNKNOWN,
    190                 boxc.mServerId);
    191         Mailbox boxdx = setupBoxSync(Account.CHECK_INTERVAL_NEVER, SyncWindow.SYNC_WINDOW_UNKNOWN,
    192                 boxd.mServerId);
    193         Mailbox boxex = setupBoxSync(Account.CHECK_INTERVAL_NEVER, SyncWindow.SYNC_WINDOW_UNKNOWN,
    194                 boxe.mServerId);
    195 
    196         // Restore the sync options
    197         parser.restoreMailboxSyncOptions();
    198         box1x = Mailbox.restoreMailboxWithId(mProviderContext, box1x.mId);
    199         box2x = Mailbox.restoreMailboxWithId(mProviderContext, box2x.mId);
    200         boxax = Mailbox.restoreMailboxWithId(mProviderContext, boxax.mId);
    201         boxbx = Mailbox.restoreMailboxWithId(mProviderContext, boxbx.mId);
    202         boxcx = Mailbox.restoreMailboxWithId(mProviderContext, boxcx.mId);
    203         boxdx = Mailbox.restoreMailboxWithId(mProviderContext, boxdx.mId);
    204         boxex = Mailbox.restoreMailboxWithId(mProviderContext, boxex.mId);
    205 
    206         assertTrue(syncOptionsSame(box1, box1x));
    207         assertTrue(syncOptionsSame(box2, box2x));
    208         assertTrue(syncOptionsSame(boxa, boxax));
    209         assertTrue(syncOptionsSame(boxb, boxbx));
    210         assertTrue(syncOptionsSame(boxc, boxcx));
    211         assertTrue(syncOptionsSame(boxd, boxdx));
    212         assertTrue(syncOptionsSame(boxe, boxex));
    213     }
    214 
    215     private static class MockFolderSyncParser extends FolderSyncParser {
    216         private BufferedReader mReader;
    217         private int mDepth = 0;
    218         private String[] mStack = new String[32];
    219         private HashMap<String, Integer> mTagMap;
    220 
    221 
    222         public MockFolderSyncParser(String fileName, AbstractSyncAdapter adapter)
    223                 throws IOException {
    224             super(null, adapter);
    225             AssetManager am = mContext.getAssets();
    226             InputStream is = am.open(fileName);
    227             if (is != null) {
    228                 mReader = new BufferedReader(new InputStreamReader(is));
    229             }
    230             mInUnitTest = true;
    231         }
    232 
    233         private void initTagMap() {
    234             mTagMap = new HashMap<String, Integer>();
    235             int pageNum = 0;
    236             for (String[] page: Tags.pages) {
    237                 int tagNum = 5;
    238                 for (String tag: page) {
    239                     if (mTagMap.containsKey(tag)) {
    240                         System.err.println("Duplicate tag: " + tag);
    241                     }
    242                     int val = (pageNum << Tags.PAGE_SHIFT) + tagNum;
    243                     mTagMap.put(tag, val);
    244                     tagNum++;
    245                 }
    246                 pageNum++;
    247             }
    248         }
    249 
    250         private int lookupTag(String tagName) {
    251             if (mTagMap == null) {
    252                 initTagMap();
    253             }
    254             int res = mTagMap.get(tagName);
    255             return res;
    256         }
    257 
    258         private String getLine() throws IOException {
    259             while (true) {
    260                 String line = mReader.readLine();
    261                 if (line == null) {
    262                     return null;
    263                 }
    264                 int start = line.indexOf("| ");
    265                 if (start > 2) {
    266                     return line.substring(start + 2);
    267                 }
    268                 // Keep looking for a suitable line
    269             }
    270         }
    271 
    272         @Override
    273         public int getValueInt() throws IOException {
    274             return Integer.parseInt(getValue());
    275         }
    276 
    277         @Override
    278         public String getValue() throws IOException {
    279             String line = getLine();
    280             if (line == null) throw new IOException();
    281             int start = line.indexOf(": ");
    282             if (start < 0) throw new IOException("Line has no value: " + line);
    283             try {
    284                 return line.substring(start + 2).trim();
    285             } finally {
    286                 if (nextTag(0) != END) {
    287                     throw new IOException("Value not followed by end tag: " + name);
    288                 }
    289             }
    290         }
    291 
    292         @Override
    293         public void skipTag() throws IOException {
    294             if (nextTag(0) == -1) {
    295                 nextTag(0);
    296             }
    297         }
    298 
    299         @Override
    300         public int nextTag(int endingTag) throws IOException {
    301             String line = getLine();
    302             if (line == null) {
    303                 return DONE;
    304             }
    305             if (line.startsWith("</")) {
    306                 int end = line.indexOf('>');
    307                 String tagName = line.substring(2, end).trim();
    308                 if (!tagName.equals(mStack[--mDepth])) {
    309                     throw new IOException("Tag end doesn't match tag");
    310                 }
    311                 mStack[mDepth] = null;
    312                 return END;
    313             } else if (line.startsWith("<")) {
    314                 int end = line.indexOf('>');
    315                 String tagName = line.substring(1, end).trim();
    316                 mStack[mDepth++] = tagName;
    317                 tag = lookupTag(tagName);
    318                 return tag;
    319             } else {
    320                 return -1;
    321             }
    322         }
    323     }
    324 
    325     private Mailbox getMailboxWithName(String folderName) {
    326         mMailboxQueryArgs[1] = folderName;
    327         Cursor c = mResolver.query(Mailbox.CONTENT_URI, Mailbox.CONTENT_PROJECTION,
    328                 Mailbox.ACCOUNT_KEY + "=? AND " + Mailbox.DISPLAY_NAME + "=?", mMailboxQueryArgs,
    329                 null);
    330         try {
    331             assertTrue(c.getCount() == 1);
    332             c.moveToFirst();
    333             Mailbox m = new Mailbox();
    334             m.restore(c);
    335             return m;
    336         } finally {
    337             c.close();
    338         }
    339     }
    340 
    341     private boolean isTopLevel(String folderName) {
    342         Mailbox m = getMailboxWithName(folderName);
    343         assertNotNull(m);
    344         return m.mParentKey == Mailbox.NO_MAILBOX;
    345     }
    346 
    347     private boolean isSubfolder(String parentName, String childName) {
    348         Mailbox parent = getMailboxWithName(parentName);
    349         Mailbox child = getMailboxWithName(childName);
    350         assertNotNull(parent);
    351         assertNotNull(child);
    352         assertTrue((parent.mFlags & Mailbox.FLAG_HAS_CHILDREN) != 0);
    353         return child.mParentKey == parent.mId;
    354     }
    355 
    356     /**
    357      * Parse a set of EAS FolderSync commands and create the Mailbox tree accordingly
    358      *
    359      * @param fileName the name of the file containing emaillog data for folder sync
    360      * @throws IOException
    361      * @throws CommandStatusException
    362      */
    363     private void testComplexFolderListParse(String fileName) throws IOException,
    364     CommandStatusException {
    365         EasSyncService service = getTestService();
    366         EmailSyncAdapter adapter = new EmailSyncAdapter(service);
    367         FolderSyncParser parser = new MockFolderSyncParser(fileName, adapter);
    368         mAccount.save(mProviderContext);
    369         mMailboxQueryArgs[0] = Long.toString(mAccount.mId);
    370         parser.mAccount = mAccount;
    371         parser.mAccountId = mAccount.mId;
    372         parser.mAccountIdAsString = Long.toString(mAccount.mId);
    373         parser.mContext = mProviderContext;
    374         parser.mContentResolver = mResolver;
    375 
    376         parser.parse();
    377 
    378         assertTrue(isTopLevel("Inbox"));
    379         assertTrue(isSubfolder("Inbox", "Gecko"));
    380         assertTrue(isSubfolder("Inbox", "Wombat"));
    381         assertTrue(isSubfolder("Inbox", "Laslo"));
    382         assertTrue(isSubfolder("Inbox", "Tomorrow"));
    383         assertTrue(isSubfolder("Inbox", "Vader"));
    384         assertTrue(isSubfolder("Inbox", "Personal"));
    385         assertTrue(isSubfolder("Laslo", "Lego"));
    386         assertTrue(isSubfolder("Tomorrow", "HomeRun"));
    387         assertTrue(isSubfolder("Tomorrow", "Services"));
    388         assertTrue(isSubfolder("HomeRun", "Review"));
    389         assertTrue(isSubfolder("Vader", "Max"));
    390         assertTrue(isSubfolder("Vader", "Parser"));
    391         assertTrue(isSubfolder("Vader", "Scott"));
    392         assertTrue(isSubfolder("Vader", "Surfing"));
    393         assertTrue(isSubfolder("Max", "Thomas"));
    394         assertTrue(isSubfolder("Personal", "Famine"));
    395         assertTrue(isSubfolder("Personal", "Bar"));
    396         assertTrue(isSubfolder("Personal", "Bill"));
    397         assertTrue(isSubfolder("Personal", "Boss"));
    398         assertTrue(isSubfolder("Personal", "Houston"));
    399         assertTrue(isSubfolder("Personal", "Mistake"));
    400         assertTrue(isSubfolder("Personal", "Online"));
    401         assertTrue(isSubfolder("Personal", "Sports"));
    402         assertTrue(isSubfolder("Famine", "Buffalo"));
    403         assertTrue(isSubfolder("Famine", "CornedBeef"));
    404         assertTrue(isSubfolder("Houston", "Rebar"));
    405         assertTrue(isSubfolder("Mistake", "Intro"));
    406     }
    407 
    408     // FolderSyncParserTest.txt is based on customer data (all names changed) that failed to
    409     // properly create the Mailbox list
    410     public void testComplexFolderListParse1() throws CommandStatusException, IOException {
    411         testComplexFolderListParse("FolderSyncParserTest.txt");
    412     }
    413 
    414     // As above, with the order changed (putting children before parents; a more difficult case
    415     public void testComplexFolderListParse2() throws CommandStatusException, IOException {
    416         testComplexFolderListParse("FolderSyncParserTest2.txt");
    417     }
    418 }
    419