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
17package com.android.exchange.adapter;
18
19import android.content.ContentResolver;
20import android.content.res.AssetManager;
21import android.database.Cursor;
22import android.test.suitebuilder.annotation.MediumTest;
23
24import com.android.emailcommon.provider.Account;
25import com.android.emailcommon.provider.EmailContent;
26import com.android.emailcommon.provider.Mailbox;
27import com.android.emailcommon.provider.EmailContent.MailboxColumns;
28import com.android.emailcommon.service.SyncWindow;
29import com.android.exchange.CommandStatusException;
30import com.android.exchange.EasSyncService;
31import com.android.exchange.provider.EmailContentSetupUtils;
32
33import java.io.BufferedReader;
34import java.io.IOException;
35import java.io.InputStream;
36import java.io.InputStreamReader;
37import java.util.HashMap;
38
39/**
40 * You can run this entire test case with:
41 *   runtest -c com.android.exchange.adapter.FolderSyncParserTests exchange
42 */
43@MediumTest
44public class FolderSyncParserTests extends SyncAdapterTestCase<EmailSyncAdapter> {
45
46    // We increment this to generate unique server id's
47    private int mServerIdCount = 0;
48    private final long mCreationTime = System.currentTimeMillis();
49    private final String[] mMailboxQueryArgs = new String[2];
50
51    public FolderSyncParserTests() {
52        super();
53    }
54
55    private Mailbox setupBoxSync(int interval, int lookback, String serverId) {
56        // Don't save the box; just create it, and give it a server id
57        Mailbox box = EmailContentSetupUtils.setupMailbox("box1", mAccount.mId, false,
58                mProviderContext, Mailbox.TYPE_MAIL);
59        box.mSyncInterval = interval;
60        box.mSyncLookback = lookback;
61        if (serverId != null) {
62            box.mServerId = serverId;
63        } else {
64            box.mServerId = "serverId-" + mCreationTime + '-' + mServerIdCount++;
65        }
66        box.save(mProviderContext);
67        return box;
68    }
69
70    private boolean syncOptionsSame(Mailbox a, Mailbox b) {
71        if (a.mSyncInterval != b.mSyncInterval) return false;
72        if (a.mSyncLookback != b.mSyncLookback) return false;
73        return true;
74    }
75
76    public void testSaveAndRestoreMailboxSyncOptions() throws IOException {
77        EasSyncService service = getTestService();
78        EmailSyncAdapter adapter = new EmailSyncAdapter(service);
79        FolderSyncParser parser = new FolderSyncParser(getTestInputStream(), adapter);
80        mAccount.save(mProviderContext);
81
82        parser.mAccount = mAccount;
83        parser.mAccountId = mAccount.mId;
84        parser.mAccountIdAsString = Long.toString(mAccount.mId);
85        parser.mContext = mProviderContext;
86        parser.mContentResolver = mProviderContext.getContentResolver();
87
88        // Don't save the box; just create it, and give it a server id
89        Mailbox box1 = setupBoxSync(Account.CHECK_INTERVAL_NEVER, SyncWindow.SYNC_WINDOW_ACCOUNT,
90                null);
91        Mailbox box2 = setupBoxSync(Account.CHECK_INTERVAL_NEVER, SyncWindow.SYNC_WINDOW_ACCOUNT,
92                null);
93        Mailbox boxa = setupBoxSync(Account.CHECK_INTERVAL_NEVER, SyncWindow.SYNC_WINDOW_1_MONTH,
94                null);
95        Mailbox boxb = setupBoxSync(Account.CHECK_INTERVAL_NEVER, SyncWindow.SYNC_WINDOW_2_WEEKS,
96                null);
97        Mailbox boxc = setupBoxSync(Account.CHECK_INTERVAL_PUSH, SyncWindow.SYNC_WINDOW_ACCOUNT,
98                null);
99        Mailbox boxd = setupBoxSync(Account.CHECK_INTERVAL_PUSH, SyncWindow.SYNC_WINDOW_ACCOUNT,
100                null);
101        Mailbox boxe = setupBoxSync(Account.CHECK_INTERVAL_PUSH, SyncWindow.SYNC_WINDOW_1_DAY,
102                null);
103
104        // Save the options (for a, b, c, d, e);
105        parser.saveMailboxSyncOptions();
106        // There should be 5 entries in the map, and they should be the correct ones
107        assertNotNull(parser.mSyncOptionsMap.get(boxa.mServerId));
108        assertNotNull(parser.mSyncOptionsMap.get(boxb.mServerId));
109        assertNotNull(parser.mSyncOptionsMap.get(boxc.mServerId));
110        assertNotNull(parser.mSyncOptionsMap.get(boxd.mServerId));
111        assertNotNull(parser.mSyncOptionsMap.get(boxe.mServerId));
112
113        // Delete all the mailboxes in the account
114        ContentResolver cr = mProviderContext.getContentResolver();
115        cr.delete(Mailbox.CONTENT_URI, Mailbox.ACCOUNT_KEY + "=?",
116                new String[] {parser.mAccountIdAsString});
117
118        // Create new boxes, all with default values for interval & window
119        Mailbox box1x = setupBoxSync(Account.CHECK_INTERVAL_NEVER, SyncWindow.SYNC_WINDOW_ACCOUNT,
120                box1.mServerId);
121        Mailbox box2x = setupBoxSync(Account.CHECK_INTERVAL_NEVER, SyncWindow.SYNC_WINDOW_ACCOUNT,
122                box2.mServerId);
123        Mailbox boxax = setupBoxSync(Account.CHECK_INTERVAL_NEVER, SyncWindow.SYNC_WINDOW_ACCOUNT,
124                boxa.mServerId);
125        Mailbox boxbx = setupBoxSync(Account.CHECK_INTERVAL_NEVER, SyncWindow.SYNC_WINDOW_ACCOUNT,
126                boxb.mServerId);
127        Mailbox boxcx = setupBoxSync(Account.CHECK_INTERVAL_NEVER, SyncWindow.SYNC_WINDOW_ACCOUNT,
128                boxc.mServerId);
129        Mailbox boxdx = setupBoxSync(Account.CHECK_INTERVAL_NEVER, SyncWindow.SYNC_WINDOW_ACCOUNT,
130                boxd.mServerId);
131        Mailbox boxex = setupBoxSync(Account.CHECK_INTERVAL_NEVER, SyncWindow.SYNC_WINDOW_ACCOUNT,
132                boxe.mServerId);
133
134        // Restore the sync options
135        parser.restoreMailboxSyncOptions();
136        box1x = Mailbox.restoreMailboxWithId(mProviderContext, box1x.mId);
137        box2x = Mailbox.restoreMailboxWithId(mProviderContext, box2x.mId);
138        boxax = Mailbox.restoreMailboxWithId(mProviderContext, boxax.mId);
139        boxbx = Mailbox.restoreMailboxWithId(mProviderContext, boxbx.mId);
140        boxcx = Mailbox.restoreMailboxWithId(mProviderContext, boxcx.mId);
141        boxdx = Mailbox.restoreMailboxWithId(mProviderContext, boxdx.mId);
142        boxex = Mailbox.restoreMailboxWithId(mProviderContext, boxex.mId);
143
144        assertTrue(syncOptionsSame(box1, box1x));
145        assertTrue(syncOptionsSame(box2, box2x));
146        assertTrue(syncOptionsSame(boxa, boxax));
147        assertTrue(syncOptionsSame(boxb, boxbx));
148        assertTrue(syncOptionsSame(boxc, boxcx));
149        assertTrue(syncOptionsSame(boxd, boxdx));
150        assertTrue(syncOptionsSame(boxe, boxex));
151    }
152
153    private static class MockFolderSyncParser extends FolderSyncParser {
154        private BufferedReader mReader;
155        private int mDepth = 0;
156        private String[] mStack = new String[32];
157        private HashMap<String, Integer> mTagMap;
158
159
160        public MockFolderSyncParser(String fileName, AbstractSyncAdapter adapter)
161                throws IOException {
162            super(null, adapter);
163            AssetManager am = mContext.getAssets();
164            InputStream is = am.open(fileName);
165            if (is != null) {
166                mReader = new BufferedReader(new InputStreamReader(is));
167            }
168        }
169
170        private void initTagMap() {
171            mTagMap = new HashMap<String, Integer>();
172            int pageNum = 0;
173            for (String[] page: Tags.pages) {
174                int tagNum = 5;
175                for (String tag: page) {
176                    if (mTagMap.containsKey(tag)) {
177                        System.err.println("Duplicate tag: " + tag);
178                    }
179                    int val = (pageNum << Tags.PAGE_SHIFT) + tagNum;
180                    mTagMap.put(tag, val);
181                    tagNum++;
182                }
183                pageNum++;
184            }
185        }
186
187        private int lookupTag(String tagName) {
188            if (mTagMap == null) {
189                initTagMap();
190            }
191            int res = mTagMap.get(tagName);
192            return res;
193        }
194
195        private String getLine() throws IOException {
196            while (true) {
197                String line = mReader.readLine();
198                if (line == null) {
199                    return null;
200                }
201                int start = line.indexOf("| ");
202                if (start > 2) {
203                    return line.substring(start + 2);
204                }
205                // Keep looking for a suitable line
206            }
207        }
208
209        @Override
210        public int getValueInt() throws IOException {
211            return Integer.parseInt(getValue());
212        }
213
214        @Override
215        public String getValue() throws IOException {
216            String line = getLine();
217            if (line == null) throw new IOException();
218            int start = line.indexOf(": ");
219            if (start < 0) throw new IOException("Line has no value: " + line);
220            try {
221                return line.substring(start + 2).trim();
222            } finally {
223                if (nextTag(0) != END) {
224                    throw new IOException("Value not followed by end tag: " + name);
225                }
226            }
227        }
228
229        @Override
230        public void skipTag() throws IOException {
231            if (nextTag(0) == -1) {
232                nextTag(0);
233            }
234        }
235
236        @Override
237        public int nextTag(int endingTag) throws IOException {
238            String line = getLine();
239            if (line == null) {
240                return DONE;
241            }
242            if (line.startsWith("</")) {
243                int end = line.indexOf('>');
244                String tagName = line.substring(2, end).trim();
245                if (!tagName.equals(mStack[--mDepth])) {
246                    throw new IOException("Tag end doesn't match tag");
247                }
248                mStack[mDepth] = null;
249                return END;
250            } else if (line.startsWith("<")) {
251                int end = line.indexOf('>');
252                String tagName = line.substring(1, end).trim();
253                mStack[mDepth++] = tagName;
254                tag = lookupTag(tagName);
255                return tag;
256            } else {
257                return -1;
258            }
259        }
260    }
261
262    private Mailbox getMailboxWithName(String folderName) {
263        mMailboxQueryArgs[1] = folderName;
264        Cursor c = mResolver.query(Mailbox.CONTENT_URI, Mailbox.CONTENT_PROJECTION,
265                Mailbox.ACCOUNT_KEY + "=? AND " + Mailbox.DISPLAY_NAME + "=?", mMailboxQueryArgs,
266                null);
267        try {
268            assertTrue(c.getCount() == 1);
269            c.moveToFirst();
270            Mailbox m = new Mailbox();
271            m.restore(c);
272            return m;
273        } finally {
274            c.close();
275        }
276    }
277
278    private boolean isTopLevel(String folderName) {
279        Mailbox m = getMailboxWithName(folderName);
280        assertNotNull(m);
281        return m.mParentKey == Mailbox.NO_MAILBOX;
282    }
283
284    private boolean isSubfolder(String parentName, String childName) {
285        Mailbox parent = getMailboxWithName(parentName);
286        Mailbox child = getMailboxWithName(childName);
287        assertNotNull(parent);
288        assertNotNull(child);
289        assertTrue((parent.mFlags & Mailbox.FLAG_HAS_CHILDREN) != 0);
290        return child.mParentKey == parent.mId;
291    }
292
293    /**
294     * Parse a set of EAS FolderSync commands and create the Mailbox tree accordingly
295     *
296     * @param fileName the name of the file containing emaillog data for folder sync
297     * @throws IOException
298     * @throws CommandStatusException
299     */
300    private void testComplexFolderListParse(String fileName) throws IOException,
301            CommandStatusException {
302        EasSyncService service = getTestService();
303        EmailSyncAdapter adapter = new EmailSyncAdapter(service);
304        FolderSyncParser parser = new MockFolderSyncParser(fileName, adapter);
305        mAccount.save(mProviderContext);
306        mMailboxQueryArgs[0] = Long.toString(mAccount.mId);
307        parser.mAccount = mAccount;
308        parser.mAccountId = mAccount.mId;
309        parser.mAccountIdAsString = Long.toString(mAccount.mId);
310        parser.mContext = mProviderContext;
311        parser.mContentResolver = mResolver;
312
313        parser.parse();
314
315        assertTrue(isTopLevel("Inbox"));
316        assertTrue(isSubfolder("Inbox", "Gecko"));
317        assertTrue(isSubfolder("Inbox", "Wombat"));
318        assertTrue(isSubfolder("Inbox", "Laslo"));
319        assertTrue(isSubfolder("Inbox", "Tomorrow"));
320        assertTrue(isSubfolder("Inbox", "Vader"));
321        assertTrue(isSubfolder("Inbox", "Personal"));
322        assertTrue(isSubfolder("Laslo", "Lego"));
323        assertTrue(isSubfolder("Tomorrow", "HomeRun"));
324        assertTrue(isSubfolder("Tomorrow", "Services"));
325        assertTrue(isSubfolder("HomeRun", "Review"));
326        assertTrue(isSubfolder("Vader", "Max"));
327        assertTrue(isSubfolder("Vader", "Parser"));
328        assertTrue(isSubfolder("Vader", "Scott"));
329        assertTrue(isSubfolder("Vader", "Surfing"));
330        assertTrue(isSubfolder("Max", "Thomas"));
331        assertTrue(isSubfolder("Personal", "Famine"));
332        assertTrue(isSubfolder("Personal", "Bar"));
333        assertTrue(isSubfolder("Personal", "Bill"));
334        assertTrue(isSubfolder("Personal", "Boss"));
335        assertTrue(isSubfolder("Personal", "Houston"));
336        assertTrue(isSubfolder("Personal", "Mistake"));
337        assertTrue(isSubfolder("Personal", "Online"));
338        assertTrue(isSubfolder("Personal", "Sports"));
339        assertTrue(isSubfolder("Famine", "Buffalo"));
340        assertTrue(isSubfolder("Famine", "CornedBeef"));
341        assertTrue(isSubfolder("Houston", "Rebar"));
342        assertTrue(isSubfolder("Mistake", "Intro"));
343    }
344
345    // FolderSyncParserTest.txt is based on customer data (all names changed) that failed to
346    // properly create the Mailbox list
347    public void testComplexFolderListParse1() throws CommandStatusException, IOException {
348        testComplexFolderListParse("FolderSyncParserTest.txt");
349    }
350
351    // As above, with the order changed (putting children before parents; a more difficult case
352    public void testComplexFolderListParse2() throws CommandStatusException, IOException {
353        testComplexFolderListParse("FolderSyncParserTest2.txt");
354    }
355
356    // Much larger test (from user with issues related to Type 1 folders)
357    public void testComplexFolderListParse3() throws CommandStatusException, IOException {
358        EasSyncService service = getTestService();
359        EmailSyncAdapter adapter = new EmailSyncAdapter(service);
360        FolderSyncParser parser = new MockFolderSyncParser("FolderSyncParserTest3.txt", adapter);
361        mAccount.save(mProviderContext);
362        mMailboxQueryArgs[0] = Long.toString(mAccount.mId);
363        parser.mAccount = mAccount;
364        parser.mAccountId = mAccount.mId;
365        parser.mAccountIdAsString = Long.toString(mAccount.mId);
366        parser.mContext = mProviderContext;
367        parser.mContentResolver = mResolver;
368        parser.parse();
369
370        int cnt = EmailContent.count(mProviderContext, Mailbox.CONTENT_URI,
371                MailboxColumns.ACCOUNT_KEY + "=" + mAccount.mId, null);
372        // 270 in the file less 4 "conflicts" folders
373        assertEquals(266, cnt);
374    }
375}
376