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.EmailContent; 26 import com.android.emailcommon.provider.Mailbox; 27 import com.android.emailcommon.provider.EmailContent.MailboxColumns; 28 import com.android.emailcommon.service.SyncWindow; 29 import com.android.exchange.CommandStatusException; 30 import com.android.exchange.EasSyncService; 31 import com.android.exchange.provider.EmailContentSetupUtils; 32 33 import java.io.BufferedReader; 34 import java.io.IOException; 35 import java.io.InputStream; 36 import java.io.InputStreamReader; 37 import 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 44 public 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 brokentestSaveAndRestoreMailboxSyncOptions() 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 brokentestComplexFolderListParse1() 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 brokentestComplexFolderListParse2() 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 brokentestComplexFolderListParse3() 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