• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
FolderSyncParserTests()49     public FolderSyncParserTests() {
50         super();
51     }
52 
testIsValidMailFolder()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 
setupBoxSync(int interval, int lookback, String serverId)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 
syncOptionsSame(Mailbox a, Mailbox b)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 
testSaveAndRestoreMailboxSyncOptions()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 
MockFolderSyncParser(String fileName, AbstractSyncAdapter adapter)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 
initTagMap()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 
lookupTag(String tagName)250         private int lookupTag(String tagName) {
251             if (mTagMap == null) {
252                 initTagMap();
253             }
254             int res = mTagMap.get(tagName);
255             return res;
256         }
257 
getLine()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
getValueInt()273         public int getValueInt() throws IOException {
274             return Integer.parseInt(getValue());
275         }
276 
277         @Override
getValue()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
skipTag()293         public void skipTag() throws IOException {
294             if (nextTag(0) == -1) {
295                 nextTag(0);
296             }
297         }
298 
299         @Override
nextTag(int endingTag)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 
getMailboxWithName(String folderName)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 
isTopLevel(String folderName)341     private boolean isTopLevel(String folderName) {
342         Mailbox m = getMailboxWithName(folderName);
343         assertNotNull(m);
344         return m.mParentKey == Mailbox.NO_MAILBOX;
345     }
346 
isSubfolder(String parentName, String childName)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      */
testComplexFolderListParse(String fileName)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
testComplexFolderListParse1()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
testComplexFolderListParse2()415     public void testComplexFolderListParse2() throws CommandStatusException, IOException {
416         testComplexFolderListParse("FolderSyncParserTest2.txt");
417     }
418 }
419