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