1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/tests/background/junit3/src/db/TestBookmarks.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1103 @@ 1.4 +/* Any copyright is dedicated to the Public Domain. 1.5 + http://creativecommons.org/publicdomain/zero/1.0/ */ 1.6 + 1.7 +package org.mozilla.gecko.background.db; 1.8 + 1.9 +import java.util.ArrayList; 1.10 +import java.util.Collection; 1.11 +import java.util.Collections; 1.12 +import java.util.HashSet; 1.13 +import java.util.Iterator; 1.14 + 1.15 +import org.json.simple.JSONArray; 1.16 +import org.mozilla.gecko.R; 1.17 +import org.mozilla.gecko.background.common.log.Logger; 1.18 +import org.mozilla.gecko.background.helpers.AndroidSyncTestCase; 1.19 +import org.mozilla.gecko.background.sync.helpers.BookmarkHelpers; 1.20 +import org.mozilla.gecko.background.sync.helpers.SimpleSuccessBeginDelegate; 1.21 +import org.mozilla.gecko.background.sync.helpers.SimpleSuccessCreationDelegate; 1.22 +import org.mozilla.gecko.background.sync.helpers.SimpleSuccessFetchDelegate; 1.23 +import org.mozilla.gecko.background.sync.helpers.SimpleSuccessFinishDelegate; 1.24 +import org.mozilla.gecko.background.sync.helpers.SimpleSuccessStoreDelegate; 1.25 +import org.mozilla.gecko.db.BrowserContract; 1.26 +import org.mozilla.gecko.db.BrowserContract.Bookmarks; 1.27 +import org.mozilla.gecko.sync.Utils; 1.28 +import org.mozilla.gecko.sync.repositories.InactiveSessionException; 1.29 +import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException; 1.30 +import org.mozilla.gecko.sync.repositories.NoStoreDelegateException; 1.31 +import org.mozilla.gecko.sync.repositories.NullCursorException; 1.32 +import org.mozilla.gecko.sync.repositories.RepositorySession; 1.33 +import org.mozilla.gecko.sync.repositories.RepositorySessionBundle; 1.34 +import org.mozilla.gecko.sync.repositories.android.AndroidBrowserBookmarksDataAccessor; 1.35 +import org.mozilla.gecko.sync.repositories.android.AndroidBrowserBookmarksRepository; 1.36 +import org.mozilla.gecko.sync.repositories.android.AndroidBrowserBookmarksRepositorySession; 1.37 +import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers; 1.38 +import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionBeginDelegate; 1.39 +import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate; 1.40 +import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate; 1.41 +import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionStoreDelegate; 1.42 +import org.mozilla.gecko.sync.repositories.domain.BookmarkRecord; 1.43 +import org.mozilla.gecko.sync.repositories.domain.Record; 1.44 + 1.45 +import android.content.ContentResolver; 1.46 +import android.content.ContentUris; 1.47 +import android.content.ContentValues; 1.48 +import android.database.Cursor; 1.49 +import android.net.Uri; 1.50 + 1.51 +public class TestBookmarks extends AndroidSyncTestCase { 1.52 + 1.53 + protected static final String LOG_TAG = "BookmarksTest"; 1.54 + 1.55 + /** 1.56 + * Trivial test that forbidden records (reading list prior to Bug 762109, pinned items…) 1.57 + * will be ignored if processed. 1.58 + */ 1.59 + public void testForbiddenItemsAreIgnored() { 1.60 + final AndroidBrowserBookmarksRepository repo = new AndroidBrowserBookmarksRepository(); 1.61 + final long now = System.currentTimeMillis(); 1.62 + final String bookmarksCollection = "bookmarks"; 1.63 + 1.64 + final BookmarkRecord toRead = new BookmarkRecord("daaaaaaaaaaa", "bookmarks", now - 1, false); 1.65 + final BookmarkRecord pinned = new BookmarkRecord("pinpinpinpin", "bookmarks", now - 1, false); 1.66 + final BookmarkRecord normal = new BookmarkRecord("baaaaaaaaaaa", "bookmarks", now - 2, false); 1.67 + 1.68 + final BookmarkRecord readingList = new BookmarkRecord(Bookmarks.READING_LIST_FOLDER_GUID, 1.69 + bookmarksCollection, now - 3, false); 1.70 + final BookmarkRecord pinnedItems = new BookmarkRecord(Bookmarks.PINNED_FOLDER_GUID, 1.71 + bookmarksCollection, now - 4, false); 1.72 + 1.73 + toRead.type = normal.type = pinned.type = "bookmark"; 1.74 + readingList.type = "folder"; 1.75 + pinnedItems.type = "folder"; 1.76 + 1.77 + toRead.parentID = Bookmarks.READING_LIST_FOLDER_GUID; 1.78 + pinned.parentID = Bookmarks.PINNED_FOLDER_GUID; 1.79 + normal.parentID = Bookmarks.TOOLBAR_FOLDER_GUID; 1.80 + 1.81 + readingList.parentID = Bookmarks.PLACES_FOLDER_GUID; 1.82 + pinnedItems.parentID = Bookmarks.PLACES_FOLDER_GUID; 1.83 + 1.84 + inBegunSession(repo, new SimpleSuccessBeginDelegate() { 1.85 + @Override 1.86 + public void onBeginSucceeded(RepositorySession session) { 1.87 + assertTrue(((AndroidBrowserBookmarksRepositorySession) session).shouldIgnore(toRead)); 1.88 + assertTrue(((AndroidBrowserBookmarksRepositorySession) session).shouldIgnore(pinned)); 1.89 + assertTrue(((AndroidBrowserBookmarksRepositorySession) session).shouldIgnore(readingList)); 1.90 + assertTrue(((AndroidBrowserBookmarksRepositorySession) session).shouldIgnore(pinnedItems)); 1.91 + assertFalse(((AndroidBrowserBookmarksRepositorySession) session).shouldIgnore(normal)); 1.92 + finishAndNotify(session); 1.93 + } 1.94 + }); 1.95 + } 1.96 + 1.97 + /** 1.98 + * Trivial test that pinned items will be skipped if present in the DB. 1.99 + */ 1.100 + public void testPinnedItemsAreNotRetrieved() { 1.101 + final AndroidBrowserBookmarksRepository repo = new AndroidBrowserBookmarksRepository(); 1.102 + 1.103 + // Ensure that they exist. 1.104 + setUpFennecPinnedItemsRecord(); 1.105 + 1.106 + // They're there in the DB… 1.107 + final ArrayList<String> roots = fetchChildrenDirect(Bookmarks.FIXED_ROOT_ID); 1.108 + Logger.info(LOG_TAG, "Roots: " + roots); 1.109 + assertTrue(roots.contains(Bookmarks.PINNED_FOLDER_GUID)); 1.110 + 1.111 + final ArrayList<String> pinned = fetchChildrenDirect(Bookmarks.FIXED_PINNED_LIST_ID); 1.112 + Logger.info(LOG_TAG, "Pinned: " + pinned); 1.113 + assertTrue(pinned.contains("dapinneditem")); 1.114 + 1.115 + // … but not when we fetch. 1.116 + final ArrayList<String> guids = fetchGUIDs(repo); 1.117 + assertFalse(guids.contains(Bookmarks.PINNED_FOLDER_GUID)); 1.118 + assertFalse(guids.contains("dapinneditem")); 1.119 + } 1.120 + 1.121 + /** 1.122 + * Trivial test that reading list records will be skipped if present in the DB. 1.123 + */ 1.124 + public void testReadingListIsNotRetrieved() { 1.125 + final AndroidBrowserBookmarksRepository repo = new AndroidBrowserBookmarksRepository(); 1.126 + 1.127 + // Ensure that it exists. 1.128 + setUpFennecReadingListRecord(); 1.129 + 1.130 + // It's there in the DB… 1.131 + final ArrayList<String> roots = fetchChildrenDirect(BrowserContract.Bookmarks.FIXED_ROOT_ID); 1.132 + Logger.info(LOG_TAG, "Roots: " + roots); 1.133 + assertTrue(roots.contains(Bookmarks.READING_LIST_FOLDER_GUID)); 1.134 + 1.135 + // … but not when we fetch. 1.136 + assertFalse(fetchGUIDs(repo).contains(Bookmarks.READING_LIST_FOLDER_GUID)); 1.137 + } 1.138 + 1.139 + public void testRetrieveFolderHasAccurateChildren() { 1.140 + AndroidBrowserBookmarksRepository repo = new AndroidBrowserBookmarksRepository(); 1.141 + 1.142 + final long now = System.currentTimeMillis(); 1.143 + 1.144 + final String folderGUID = "eaaaaaaaafff"; 1.145 + BookmarkRecord folder = new BookmarkRecord(folderGUID, "bookmarks", now - 5, false); 1.146 + BookmarkRecord bookmarkA = new BookmarkRecord("daaaaaaaaaaa", "bookmarks", now - 1, false); 1.147 + BookmarkRecord bookmarkB = new BookmarkRecord("baaaaaaaabbb", "bookmarks", now - 3, false); 1.148 + BookmarkRecord bookmarkC = new BookmarkRecord("aaaaaaaaaccc", "bookmarks", now - 2, false); 1.149 + 1.150 + folder.children = childrenFromRecords(bookmarkA, bookmarkB, bookmarkC); 1.151 + folder.sortIndex = 150; 1.152 + folder.title = "Test items"; 1.153 + folder.parentID = "toolbar"; 1.154 + folder.parentName = "Bookmarks Toolbar"; 1.155 + folder.type = "folder"; 1.156 + 1.157 + bookmarkA.parentID = folderGUID; 1.158 + bookmarkA.bookmarkURI = "http://example.com/A"; 1.159 + bookmarkA.title = "Title A"; 1.160 + bookmarkA.type = "bookmark"; 1.161 + 1.162 + bookmarkB.parentID = folderGUID; 1.163 + bookmarkB.bookmarkURI = "http://example.com/B"; 1.164 + bookmarkB.title = "Title B"; 1.165 + bookmarkB.type = "bookmark"; 1.166 + 1.167 + bookmarkC.parentID = folderGUID; 1.168 + bookmarkC.bookmarkURI = "http://example.com/C"; 1.169 + bookmarkC.title = "Title C"; 1.170 + bookmarkC.type = "bookmark"; 1.171 + 1.172 + BookmarkRecord[] folderOnly = new BookmarkRecord[1]; 1.173 + BookmarkRecord[] children = new BookmarkRecord[3]; 1.174 + 1.175 + folderOnly[0] = folder; 1.176 + 1.177 + children[0] = bookmarkA; 1.178 + children[1] = bookmarkB; 1.179 + children[2] = bookmarkC; 1.180 + 1.181 + wipe(); 1.182 + Logger.debug(getName(), "Storing just folder..."); 1.183 + storeRecordsInSession(repo, folderOnly, null); 1.184 + 1.185 + // We don't have any children, despite our insistence upon storing. 1.186 + assertChildrenAreOrdered(repo, folderGUID, new Record[] {}); 1.187 + 1.188 + // Now store the children. 1.189 + Logger.debug(getName(), "Storing children..."); 1.190 + storeRecordsInSession(repo, children, null); 1.191 + 1.192 + // Now we have children, but their order is not determined, because 1.193 + // they were stored out-of-session with the original folder. 1.194 + assertChildrenAreUnordered(repo, folderGUID, children); 1.195 + 1.196 + // Now if we store the folder record again, they'll be put in the 1.197 + // right place. 1.198 + folder.lastModified++; 1.199 + Logger.debug(getName(), "Storing just folder again..."); 1.200 + storeRecordsInSession(repo, folderOnly, null); 1.201 + Logger.debug(getName(), "Fetching children yet again..."); 1.202 + assertChildrenAreOrdered(repo, folderGUID, children); 1.203 + 1.204 + // Now let's see what happens when we see records in the same session. 1.205 + BookmarkRecord[] parentMixed = new BookmarkRecord[4]; 1.206 + BookmarkRecord[] parentFirst = new BookmarkRecord[4]; 1.207 + BookmarkRecord[] parentLast = new BookmarkRecord[4]; 1.208 + 1.209 + // None of our records have a position set. 1.210 + assertTrue(bookmarkA.androidPosition <= 0); 1.211 + assertTrue(bookmarkB.androidPosition <= 0); 1.212 + assertTrue(bookmarkC.androidPosition <= 0); 1.213 + 1.214 + parentMixed[1] = folder; 1.215 + parentMixed[0] = bookmarkA; 1.216 + parentMixed[2] = bookmarkC; 1.217 + parentMixed[3] = bookmarkB; 1.218 + 1.219 + parentFirst[0] = folder; 1.220 + parentFirst[1] = bookmarkC; 1.221 + parentFirst[2] = bookmarkA; 1.222 + parentFirst[3] = bookmarkB; 1.223 + 1.224 + parentLast[3] = folder; 1.225 + parentLast[0] = bookmarkB; 1.226 + parentLast[1] = bookmarkA; 1.227 + parentLast[2] = bookmarkC; 1.228 + 1.229 + wipe(); 1.230 + storeRecordsInSession(repo, parentMixed, null); 1.231 + assertChildrenAreOrdered(repo, folderGUID, children); 1.232 + 1.233 + wipe(); 1.234 + storeRecordsInSession(repo, parentFirst, null); 1.235 + assertChildrenAreOrdered(repo, folderGUID, children); 1.236 + 1.237 + wipe(); 1.238 + storeRecordsInSession(repo, parentLast, null); 1.239 + assertChildrenAreOrdered(repo, folderGUID, children); 1.240 + 1.241 + // Ensure that records are ordered even if we re-process the folder. 1.242 + wipe(); 1.243 + storeRecordsInSession(repo, parentLast, null); 1.244 + folder.lastModified++; 1.245 + storeRecordsInSession(repo, folderOnly, null); 1.246 + assertChildrenAreOrdered(repo, folderGUID, children); 1.247 + } 1.248 + 1.249 + public void testMergeFoldersPreservesSaneOrder() { 1.250 + AndroidBrowserBookmarksRepository repo = new AndroidBrowserBookmarksRepository(); 1.251 + 1.252 + final long now = System.currentTimeMillis(); 1.253 + final String folderGUID = "mobile"; 1.254 + 1.255 + wipe(); 1.256 + final long mobile = setUpFennecMobileRecord(); 1.257 + 1.258 + // No children. 1.259 + assertChildrenAreUnordered(repo, folderGUID, new Record[] {}); 1.260 + 1.261 + // Add some, as Fennec would. 1.262 + fennecAddBookmark("Bookmark One", "http://example.com/fennec/One"); 1.263 + fennecAddBookmark("Bookmark Two", "http://example.com/fennec/Two"); 1.264 + 1.265 + Logger.debug(getName(), "Fetching children..."); 1.266 + JSONArray folderChildren = fetchChildrenForGUID(repo, folderGUID); 1.267 + 1.268 + assertTrue(folderChildren != null); 1.269 + Logger.debug(getName(), "Children are " + folderChildren.toJSONString()); 1.270 + assertEquals(2, folderChildren.size()); 1.271 + String guidOne = (String) folderChildren.get(0); 1.272 + String guidTwo = (String) folderChildren.get(1); 1.273 + 1.274 + // Make sure positions were saved. 1.275 + assertChildrenAreDirect(mobile, new String[] { 1.276 + guidOne, 1.277 + guidTwo 1.278 + }); 1.279 + 1.280 + // Add some through Sync. 1.281 + BookmarkRecord folder = new BookmarkRecord(folderGUID, "bookmarks", now, false); 1.282 + BookmarkRecord bookmarkA = new BookmarkRecord("daaaaaaaaaaa", "bookmarks", now, false); 1.283 + BookmarkRecord bookmarkB = new BookmarkRecord("baaaaaaaabbb", "bookmarks", now, false); 1.284 + 1.285 + folder.children = childrenFromRecords(bookmarkA, bookmarkB); 1.286 + folder.sortIndex = 150; 1.287 + folder.title = "Mobile Bookmarks"; 1.288 + folder.parentID = "places"; 1.289 + folder.parentName = ""; 1.290 + folder.type = "folder"; 1.291 + 1.292 + bookmarkA.parentID = folderGUID; 1.293 + bookmarkA.parentName = "Mobile Bookmarks"; // Using this title exercises Bug 748898. 1.294 + bookmarkA.bookmarkURI = "http://example.com/A"; 1.295 + bookmarkA.title = "Title A"; 1.296 + bookmarkA.type = "bookmark"; 1.297 + 1.298 + bookmarkB.parentID = folderGUID; 1.299 + bookmarkB.parentName = "mobile"; 1.300 + bookmarkB.bookmarkURI = "http://example.com/B"; 1.301 + bookmarkB.title = "Title B"; 1.302 + bookmarkB.type = "bookmark"; 1.303 + 1.304 + BookmarkRecord[] parentMixed = new BookmarkRecord[3]; 1.305 + parentMixed[0] = bookmarkA; 1.306 + parentMixed[1] = folder; 1.307 + parentMixed[2] = bookmarkB; 1.308 + 1.309 + storeRecordsInSession(repo, parentMixed, null); 1.310 + 1.311 + BookmarkRecord expectedOne = new BookmarkRecord(guidOne, "bookmarks", now - 10, false); 1.312 + BookmarkRecord expectedTwo = new BookmarkRecord(guidTwo, "bookmarks", now - 10, false); 1.313 + 1.314 + // We want the server to win in this case, and otherwise to preserve order. 1.315 + // TODO 1.316 + assertChildrenAreOrdered(repo, folderGUID, new Record[] { 1.317 + bookmarkA, 1.318 + bookmarkB, 1.319 + expectedOne, 1.320 + expectedTwo 1.321 + }); 1.322 + 1.323 + // Furthermore, the children of that folder should be correct in the DB. 1.324 + ContentResolver cr = getApplicationContext().getContentResolver(); 1.325 + final long folderId = fennecGetFolderId(cr, folderGUID); 1.326 + Logger.debug(getName(), "Folder " + folderGUID + " => " + folderId); 1.327 + 1.328 + assertChildrenAreDirect(folderId, new String[] { 1.329 + bookmarkA.guid, 1.330 + bookmarkB.guid, 1.331 + expectedOne.guid, 1.332 + expectedTwo.guid 1.333 + }); 1.334 + } 1.335 + 1.336 + /** 1.337 + * Apply a folder record whose children array is already accurately 1.338 + * stored in the database. Verify that the parent folder is not flagged 1.339 + * for reupload (i.e., that its modified time is *ahem* unmodified). 1.340 + */ 1.341 + public void testNoReorderingMeansNoReupload() { 1.342 + AndroidBrowserBookmarksRepository repo = new AndroidBrowserBookmarksRepository(); 1.343 + 1.344 + final long now = System.currentTimeMillis(); 1.345 + 1.346 + final String folderGUID = "eaaaaaaaafff"; 1.347 + BookmarkRecord folder = new BookmarkRecord(folderGUID, "bookmarks", now -5, false); 1.348 + BookmarkRecord bookmarkA = new BookmarkRecord("daaaaaaaaaaa", "bookmarks", now -1, false); 1.349 + BookmarkRecord bookmarkB = new BookmarkRecord("baaaaaaaabbb", "bookmarks", now -3, false); 1.350 + 1.351 + folder.children = childrenFromRecords(bookmarkA, bookmarkB); 1.352 + folder.sortIndex = 150; 1.353 + folder.title = "Test items"; 1.354 + folder.parentID = "toolbar"; 1.355 + folder.parentName = "Bookmarks Toolbar"; 1.356 + folder.type = "folder"; 1.357 + 1.358 + bookmarkA.parentID = folderGUID; 1.359 + bookmarkA.bookmarkURI = "http://example.com/A"; 1.360 + bookmarkA.title = "Title A"; 1.361 + bookmarkA.type = "bookmark"; 1.362 + 1.363 + bookmarkB.parentID = folderGUID; 1.364 + bookmarkB.bookmarkURI = "http://example.com/B"; 1.365 + bookmarkB.title = "Title B"; 1.366 + bookmarkB.type = "bookmark"; 1.367 + 1.368 + BookmarkRecord[] abf = new BookmarkRecord[3]; 1.369 + BookmarkRecord[] justFolder = new BookmarkRecord[1]; 1.370 + 1.371 + abf[0] = bookmarkA; 1.372 + abf[1] = bookmarkB; 1.373 + abf[2] = folder; 1.374 + 1.375 + justFolder[0] = folder; 1.376 + 1.377 + final String[] abGUIDs = new String[] { bookmarkA.guid, bookmarkB.guid }; 1.378 + final Record[] abRecords = new Record[] { bookmarkA, bookmarkB }; 1.379 + final String[] baGUIDs = new String[] { bookmarkB.guid, bookmarkA.guid }; 1.380 + final Record[] baRecords = new Record[] { bookmarkB, bookmarkA }; 1.381 + 1.382 + wipe(); 1.383 + Logger.debug(getName(), "Storing A, B, folder..."); 1.384 + storeRecordsInSession(repo, abf, null); 1.385 + 1.386 + ContentResolver cr = getApplicationContext().getContentResolver(); 1.387 + final long folderID = fennecGetFolderId(cr, folderGUID); 1.388 + assertChildrenAreOrdered(repo, folderGUID, abRecords); 1.389 + assertChildrenAreDirect(folderID, abGUIDs); 1.390 + 1.391 + // To ensure an interval. 1.392 + try { 1.393 + Thread.sleep(100); 1.394 + } catch (InterruptedException e) { 1.395 + } 1.396 + 1.397 + // Store the same folder record again, and check the tracking. 1.398 + // Because the folder array didn't change, 1.399 + // the item is still tracked to not be uploaded. 1.400 + folder.lastModified = System.currentTimeMillis() + 1; 1.401 + HashSet<String> tracked = new HashSet<String>(); 1.402 + storeRecordsInSession(repo, justFolder, tracked); 1.403 + assertChildrenAreOrdered(repo, folderGUID, abRecords); 1.404 + assertChildrenAreDirect(folderID, abGUIDs); 1.405 + 1.406 + assertTrue(tracked.contains(folderGUID)); 1.407 + 1.408 + // Store again, but with a different order. 1.409 + tracked = new HashSet<String>(); 1.410 + folder.children = childrenFromRecords(bookmarkB, bookmarkA); 1.411 + folder.lastModified = System.currentTimeMillis() + 1; 1.412 + storeRecordsInSession(repo, justFolder, tracked); 1.413 + assertChildrenAreOrdered(repo, folderGUID, baRecords); 1.414 + assertChildrenAreDirect(folderID, baGUIDs); 1.415 + 1.416 + // Now it's going to be reuploaded. 1.417 + assertFalse(tracked.contains(folderGUID)); 1.418 + } 1.419 + 1.420 + /** 1.421 + * Exercise the deletion of folders when their children have not been 1.422 + * marked as deleted. In a database with constraints, this would fail 1.423 + * if we simply deleted the records, so we move them first. 1.424 + */ 1.425 + public void testFolderDeletionOrphansChildren() { 1.426 + AndroidBrowserBookmarksRepository repo = new AndroidBrowserBookmarksRepository(); 1.427 + 1.428 + long now = System.currentTimeMillis(); 1.429 + 1.430 + // Add a folder and four children. 1.431 + final String folderGUID = "eaaaaaaaafff"; 1.432 + BookmarkRecord folder = new BookmarkRecord(folderGUID, "bookmarks", now -5, false); 1.433 + BookmarkRecord bookmarkA = new BookmarkRecord("daaaaaaaaaaa", "bookmarks", now -1, false); 1.434 + BookmarkRecord bookmarkB = new BookmarkRecord("baaaaaaaabbb", "bookmarks", now -3, false); 1.435 + BookmarkRecord bookmarkC = new BookmarkRecord("daaaaaaaaccc", "bookmarks", now -7, false); 1.436 + BookmarkRecord bookmarkD = new BookmarkRecord("baaaaaaaaddd", "bookmarks", now -4, false); 1.437 + 1.438 + folder.children = childrenFromRecords(bookmarkA, bookmarkB, bookmarkC, bookmarkD); 1.439 + folder.sortIndex = 150; 1.440 + folder.title = "Test items"; 1.441 + folder.parentID = "toolbar"; 1.442 + folder.parentName = "Bookmarks Toolbar"; 1.443 + folder.type = "folder"; 1.444 + 1.445 + bookmarkA.parentID = folderGUID; 1.446 + bookmarkA.bookmarkURI = "http://example.com/A"; 1.447 + bookmarkA.title = "Title A"; 1.448 + bookmarkA.type = "bookmark"; 1.449 + 1.450 + bookmarkB.parentID = folderGUID; 1.451 + bookmarkB.bookmarkURI = "http://example.com/B"; 1.452 + bookmarkB.title = "Title B"; 1.453 + bookmarkB.type = "bookmark"; 1.454 + 1.455 + bookmarkC.parentID = folderGUID; 1.456 + bookmarkC.bookmarkURI = "http://example.com/C"; 1.457 + bookmarkC.title = "Title C"; 1.458 + bookmarkC.type = "bookmark"; 1.459 + 1.460 + bookmarkD.parentID = folderGUID; 1.461 + bookmarkD.bookmarkURI = "http://example.com/D"; 1.462 + bookmarkD.title = "Title D"; 1.463 + bookmarkD.type = "bookmark"; 1.464 + 1.465 + BookmarkRecord[] abfcd = new BookmarkRecord[5]; 1.466 + BookmarkRecord[] justFolder = new BookmarkRecord[1]; 1.467 + abfcd[0] = bookmarkA; 1.468 + abfcd[1] = bookmarkB; 1.469 + abfcd[2] = folder; 1.470 + abfcd[3] = bookmarkC; 1.471 + abfcd[4] = bookmarkD; 1.472 + 1.473 + justFolder[0] = folder; 1.474 + 1.475 + final String[] abcdGUIDs = new String[] { bookmarkA.guid, bookmarkB.guid, bookmarkC.guid, bookmarkD.guid }; 1.476 + final Record[] abcdRecords = new Record[] { bookmarkA, bookmarkB, bookmarkC, bookmarkD }; 1.477 + 1.478 + wipe(); 1.479 + Logger.debug(getName(), "Storing A, B, folder, C, D..."); 1.480 + storeRecordsInSession(repo, abfcd, null); 1.481 + 1.482 + // Verify that it worked. 1.483 + ContentResolver cr = getApplicationContext().getContentResolver(); 1.484 + final long folderID = fennecGetFolderId(cr, folderGUID); 1.485 + assertChildrenAreOrdered(repo, folderGUID, abcdRecords); 1.486 + assertChildrenAreDirect(folderID, abcdGUIDs); 1.487 + 1.488 + now = System.currentTimeMillis(); 1.489 + 1.490 + // Add one child to unsorted bookmarks. 1.491 + BookmarkRecord unsortedA = new BookmarkRecord("yiamunsorted", "bookmarks", now, false); 1.492 + unsortedA.parentID = "unfiled"; 1.493 + unsortedA.title = "Unsorted A"; 1.494 + unsortedA.type = "bookmark"; 1.495 + unsortedA.androidPosition = 0; 1.496 + 1.497 + BookmarkRecord[] ua = new BookmarkRecord[1]; 1.498 + ua[0] = unsortedA; 1.499 + 1.500 + storeRecordsInSession(repo, ua, null); 1.501 + 1.502 + // Ensure that the database is in this state. 1.503 + assertChildrenAreOrdered(repo, "unfiled", ua); 1.504 + 1.505 + // Delete the second child, the folder, and then the third child. 1.506 + bookmarkB.bookmarkURI = bookmarkC.bookmarkURI = folder.bookmarkURI = null; 1.507 + bookmarkB.deleted = bookmarkC.deleted = folder.deleted = true; 1.508 + bookmarkB.title = bookmarkC.title = folder.title = null; 1.509 + 1.510 + // Nulling the type of folder is very important: it verifies 1.511 + // that the session can behave correctly according to local type. 1.512 + bookmarkB.type = bookmarkC.type = folder.type = null; 1.513 + 1.514 + bookmarkB.lastModified = bookmarkC.lastModified = folder.lastModified = now = System.currentTimeMillis(); 1.515 + 1.516 + BookmarkRecord[] deletions = new BookmarkRecord[] { bookmarkB, folder, bookmarkC }; 1.517 + storeRecordsInSession(repo, deletions, null); 1.518 + 1.519 + // Verify that the unsorted bookmarks folder contains its child and the 1.520 + // first and fourth children of the now-deleted folder. 1.521 + // Also verify that the folder is gone. 1.522 + long unsortedID = fennecGetFolderId(cr, "unfiled"); 1.523 + long toolbarID = fennecGetFolderId(cr, "toolbar"); 1.524 + String[] expected = new String[] { unsortedA.guid, bookmarkA.guid, bookmarkD.guid }; 1.525 + 1.526 + // This will trigger positioning. 1.527 + assertChildrenAreUnordered(repo, "unfiled", new Record[] { unsortedA, bookmarkA, bookmarkD }); 1.528 + assertChildrenAreDirect(unsortedID, expected); 1.529 + assertChildrenAreDirect(toolbarID, new String[] {}); 1.530 + } 1.531 + 1.532 + /** 1.533 + * A test where we expect to replace a local folder with a new folder (with a 1.534 + * new GUID), whilst adding children to it. Verifies that replace and insert 1.535 + * co-operate. 1.536 + */ 1.537 + public void testInsertAndReplaceGuid() { 1.538 + AndroidBrowserBookmarksRepository repo = new AndroidBrowserBookmarksRepository(); 1.539 + wipe(); 1.540 + 1.541 + BookmarkRecord folder1 = BookmarkHelpers.createFolder1(); 1.542 + BookmarkRecord folder2 = BookmarkHelpers.createFolder2(); // child of folder1 1.543 + BookmarkRecord folder3 = BookmarkHelpers.createFolder3(); // child of folder2 1.544 + BookmarkRecord bmk1 = BookmarkHelpers.createBookmark1(); // child of folder1 1.545 + BookmarkRecord bmk2 = BookmarkHelpers.createBookmark2(); // child of folder1 1.546 + BookmarkRecord bmk3 = BookmarkHelpers.createBookmark3(); // child of folder2 1.547 + BookmarkRecord bmk4 = BookmarkHelpers.createBookmark4(); // child of folder3 1.548 + 1.549 + BookmarkRecord[] records = new BookmarkRecord[] { 1.550 + folder1, folder2, folder3, 1.551 + bmk1, bmk4 1.552 + }; 1.553 + storeRecordsInSession(repo, records, null); 1.554 + 1.555 + assertChildrenAreUnordered(repo, folder1.guid, new Record[] { bmk1, folder2 }); 1.556 + assertChildrenAreUnordered(repo, folder2.guid, new Record[] { folder3 }); 1.557 + assertChildrenAreUnordered(repo, folder3.guid, new Record[] { bmk4 }); 1.558 + 1.559 + // Replace folder3 with a record with a new GUID, and add bmk4 as folder3's child. 1.560 + final long now = System.currentTimeMillis(); 1.561 + folder3.guid = Utils.generateGuid(); 1.562 + folder3.lastModified = now; 1.563 + bmk4.title = bmk4.title + "/NEW"; 1.564 + bmk4.parentID = folder3.guid; // Incoming child knows its parent. 1.565 + bmk4.parentName = folder3.title; 1.566 + bmk4.lastModified = now; 1.567 + 1.568 + // Order of store should not matter. 1.569 + ArrayList<BookmarkRecord> changedRecords = new ArrayList<BookmarkRecord>(); 1.570 + changedRecords.add(bmk2); changedRecords.add(bmk3); changedRecords.add(bmk4); changedRecords.add(folder3); 1.571 + Collections.shuffle(changedRecords); 1.572 + storeRecordsInSession(repo, changedRecords.toArray(new BookmarkRecord[changedRecords.size()]), null); 1.573 + 1.574 + assertChildrenAreUnordered(repo, folder1.guid, new Record[] { bmk1, bmk2, folder2 }); 1.575 + assertChildrenAreUnordered(repo, folder2.guid, new Record[] { bmk3, folder3 }); 1.576 + assertChildrenAreUnordered(repo, folder3.guid, new Record[] { bmk4 }); 1.577 + 1.578 + assertNotNull(fetchGUID(repo, folder3.guid)); 1.579 + assertEquals(bmk4.title, fetchGUID(repo, bmk4.guid).title); 1.580 + } 1.581 + 1.582 + /** 1.583 + * A test where we expect to replace a local folder with a new folder (with a 1.584 + * new title but the same GUID), whilst adding children to it. Verifies that 1.585 + * replace and insert co-operate. 1.586 + */ 1.587 + public void testInsertAndReplaceTitle() { 1.588 + AndroidBrowserBookmarksRepository repo = new AndroidBrowserBookmarksRepository(); 1.589 + wipe(); 1.590 + 1.591 + BookmarkRecord folder1 = BookmarkHelpers.createFolder1(); 1.592 + BookmarkRecord folder2 = BookmarkHelpers.createFolder2(); // child of folder1 1.593 + BookmarkRecord folder3 = BookmarkHelpers.createFolder3(); // child of folder2 1.594 + BookmarkRecord bmk1 = BookmarkHelpers.createBookmark1(); // child of folder1 1.595 + BookmarkRecord bmk2 = BookmarkHelpers.createBookmark2(); // child of folder1 1.596 + BookmarkRecord bmk3 = BookmarkHelpers.createBookmark3(); // child of folder2 1.597 + BookmarkRecord bmk4 = BookmarkHelpers.createBookmark4(); // child of folder3 1.598 + 1.599 + BookmarkRecord[] records = new BookmarkRecord[] { 1.600 + folder1, folder2, folder3, 1.601 + bmk1, bmk4 1.602 + }; 1.603 + storeRecordsInSession(repo, records, null); 1.604 + 1.605 + assertChildrenAreUnordered(repo, folder1.guid, new Record[] { bmk1, folder2 }); 1.606 + assertChildrenAreUnordered(repo, folder2.guid, new Record[] { folder3 }); 1.607 + assertChildrenAreUnordered(repo, folder3.guid, new Record[] { bmk4 }); 1.608 + 1.609 + // Rename folder1, and add bmk2 as folder1's child. 1.610 + final long now = System.currentTimeMillis(); 1.611 + folder1.title = folder1.title + "/NEW"; 1.612 + folder1.lastModified = now; 1.613 + bmk2.title = bmk2.title + "/NEW"; 1.614 + bmk2.parentID = folder1.guid; // Incoming child knows its parent. 1.615 + bmk2.parentName = folder1.title; 1.616 + bmk2.lastModified = now; 1.617 + 1.618 + // Order of store should not matter. 1.619 + ArrayList<BookmarkRecord> changedRecords = new ArrayList<BookmarkRecord>(); 1.620 + changedRecords.add(bmk2); changedRecords.add(bmk3); changedRecords.add(folder1); 1.621 + Collections.shuffle(changedRecords); 1.622 + storeRecordsInSession(repo, changedRecords.toArray(new BookmarkRecord[changedRecords.size()]), null); 1.623 + 1.624 + assertChildrenAreUnordered(repo, folder1.guid, new Record[] { bmk1, bmk2, folder2 }); 1.625 + assertChildrenAreUnordered(repo, folder2.guid, new Record[] { bmk3, folder3 }); 1.626 + assertChildrenAreUnordered(repo, folder3.guid, new Record[] { bmk4 }); 1.627 + 1.628 + assertEquals(folder1.title, fetchGUID(repo, folder1.guid).title); 1.629 + assertEquals(bmk2.title, fetchGUID(repo, bmk2.guid).title); 1.630 + } 1.631 + 1.632 + /** 1.633 + * Create and begin a new session, handing control to the delegate when started. 1.634 + * Returns when the delegate has notified. 1.635 + */ 1.636 + public void inBegunSession(final AndroidBrowserBookmarksRepository repo, 1.637 + final RepositorySessionBeginDelegate beginDelegate) { 1.638 + Runnable go = new Runnable() { 1.639 + @Override 1.640 + public void run() { 1.641 + RepositorySessionCreationDelegate delegate = new SimpleSuccessCreationDelegate() { 1.642 + @Override 1.643 + public void onSessionCreated(final RepositorySession session) { 1.644 + try { 1.645 + session.begin(beginDelegate); 1.646 + } catch (InvalidSessionTransitionException e) { 1.647 + performNotify(e); 1.648 + } 1.649 + } 1.650 + }; 1.651 + repo.createSession(delegate, getApplicationContext()); 1.652 + } 1.653 + }; 1.654 + performWait(go); 1.655 + } 1.656 + 1.657 + /** 1.658 + * Finish the provided session, notifying on success. 1.659 + * 1.660 + * @param session 1.661 + */ 1.662 + public void finishAndNotify(final RepositorySession session) { 1.663 + try { 1.664 + session.finish(new SimpleSuccessFinishDelegate() { 1.665 + @Override 1.666 + public void onFinishSucceeded(RepositorySession session, 1.667 + RepositorySessionBundle bundle) { 1.668 + performNotify(); 1.669 + } 1.670 + }); 1.671 + } catch (InactiveSessionException e) { 1.672 + performNotify(e); 1.673 + } 1.674 + } 1.675 + 1.676 + /** 1.677 + * Simple helper class for fetching all records. 1.678 + * The fetched records' GUIDs are stored in `fetchedGUIDs`. 1.679 + */ 1.680 + public class SimpleFetchAllBeginDelegate extends SimpleSuccessBeginDelegate { 1.681 + public final ArrayList<String> fetchedGUIDs = new ArrayList<String>(); 1.682 + 1.683 + @Override 1.684 + public void onBeginSucceeded(final RepositorySession session) { 1.685 + RepositorySessionFetchRecordsDelegate fetchDelegate = new SimpleSuccessFetchDelegate() { 1.686 + 1.687 + @Override 1.688 + public void onFetchedRecord(Record record) { 1.689 + fetchedGUIDs.add(record.guid); 1.690 + } 1.691 + 1.692 + @Override 1.693 + public void onFetchCompleted(long end) { 1.694 + finishAndNotify(session); 1.695 + } 1.696 + }; 1.697 + session.fetchSince(0, fetchDelegate); 1.698 + } 1.699 + } 1.700 + 1.701 + /** 1.702 + * Simple helper class for fetching a single record by GUID. 1.703 + * The fetched record is stored in `fetchedRecord`. 1.704 + */ 1.705 + public class SimpleFetchOneBeginDelegate extends SimpleSuccessBeginDelegate { 1.706 + public final String guid; 1.707 + public Record fetchedRecord = null; 1.708 + 1.709 + public SimpleFetchOneBeginDelegate(String guid) { 1.710 + this.guid = guid; 1.711 + } 1.712 + 1.713 + @Override 1.714 + public void onBeginSucceeded(final RepositorySession session) { 1.715 + RepositorySessionFetchRecordsDelegate fetchDelegate = new SimpleSuccessFetchDelegate() { 1.716 + 1.717 + @Override 1.718 + public void onFetchedRecord(Record record) { 1.719 + fetchedRecord = record; 1.720 + } 1.721 + 1.722 + @Override 1.723 + public void onFetchCompleted(long end) { 1.724 + finishAndNotify(session); 1.725 + } 1.726 + }; 1.727 + try { 1.728 + session.fetch(new String[] { guid }, fetchDelegate); 1.729 + } catch (InactiveSessionException e) { 1.730 + performNotify("Session is inactive.", e); 1.731 + } 1.732 + } 1.733 + } 1.734 + 1.735 + /** 1.736 + * Create a new session for the given repository, storing each record 1.737 + * from the provided array. Notifies on failure or success. 1.738 + * 1.739 + * Optionally populates a provided Collection with tracked items. 1.740 + * @param repo 1.741 + * @param records 1.742 + * @param tracked 1.743 + */ 1.744 + public void storeRecordsInSession(AndroidBrowserBookmarksRepository repo, 1.745 + final BookmarkRecord[] records, 1.746 + final Collection<String> tracked) { 1.747 + SimpleSuccessBeginDelegate beginDelegate = new SimpleSuccessBeginDelegate() { 1.748 + @Override 1.749 + public void onBeginSucceeded(final RepositorySession session) { 1.750 + RepositorySessionStoreDelegate storeDelegate = new SimpleSuccessStoreDelegate() { 1.751 + 1.752 + @Override 1.753 + public void onStoreCompleted(final long storeEnd) { 1.754 + // Pass back whatever we tracked. 1.755 + if (tracked != null) { 1.756 + Iterator<String> iter = session.getTrackedRecordIDs(); 1.757 + while (iter.hasNext()) { 1.758 + tracked.add(iter.next()); 1.759 + } 1.760 + } 1.761 + finishAndNotify(session); 1.762 + } 1.763 + 1.764 + @Override 1.765 + public void onRecordStoreSucceeded(String guid) { 1.766 + } 1.767 + }; 1.768 + session.setStoreDelegate(storeDelegate); 1.769 + for (BookmarkRecord record : records) { 1.770 + try { 1.771 + session.store(record); 1.772 + } catch (NoStoreDelegateException e) { 1.773 + // Never happens. 1.774 + } 1.775 + } 1.776 + session.storeDone(); 1.777 + } 1.778 + }; 1.779 + inBegunSession(repo, beginDelegate); 1.780 + } 1.781 + 1.782 + public ArrayList<String> fetchGUIDs(AndroidBrowserBookmarksRepository repo) { 1.783 + SimpleFetchAllBeginDelegate beginDelegate = new SimpleFetchAllBeginDelegate(); 1.784 + inBegunSession(repo, beginDelegate); 1.785 + return beginDelegate.fetchedGUIDs; 1.786 + } 1.787 + 1.788 + public BookmarkRecord fetchGUID(AndroidBrowserBookmarksRepository repo, 1.789 + final String guid) { 1.790 + Logger.info(LOG_TAG, "Fetching for " + guid); 1.791 + SimpleFetchOneBeginDelegate beginDelegate = new SimpleFetchOneBeginDelegate(guid); 1.792 + inBegunSession(repo, beginDelegate); 1.793 + Logger.info(LOG_TAG, "Fetched " + beginDelegate.fetchedRecord); 1.794 + assertTrue(beginDelegate.fetchedRecord != null); 1.795 + return (BookmarkRecord) beginDelegate.fetchedRecord; 1.796 + } 1.797 + 1.798 + public JSONArray fetchChildrenForGUID(AndroidBrowserBookmarksRepository repo, 1.799 + final String guid) { 1.800 + return fetchGUID(repo, guid).children; 1.801 + } 1.802 + 1.803 + @SuppressWarnings("unchecked") 1.804 + protected static JSONArray childrenFromRecords(BookmarkRecord... records) { 1.805 + JSONArray children = new JSONArray(); 1.806 + for (BookmarkRecord record : records) { 1.807 + children.add(record.guid); 1.808 + } 1.809 + return children; 1.810 + } 1.811 + 1.812 + 1.813 + protected void updateRow(ContentValues values) { 1.814 + Uri uri = BrowserContractHelpers.BOOKMARKS_CONTENT_URI; 1.815 + final String where = BrowserContract.Bookmarks.GUID + " = ?"; 1.816 + final String[] args = new String[] { values.getAsString(BrowserContract.Bookmarks.GUID) }; 1.817 + getApplicationContext().getContentResolver().update(uri, values, where, args); 1.818 + } 1.819 + 1.820 + protected Uri insertRow(ContentValues values) { 1.821 + Uri uri = BrowserContractHelpers.BOOKMARKS_CONTENT_URI; 1.822 + return getApplicationContext().getContentResolver().insert(uri, values); 1.823 + } 1.824 + 1.825 + protected static ContentValues specialFolder() { 1.826 + ContentValues values = new ContentValues(); 1.827 + 1.828 + final long now = System.currentTimeMillis(); 1.829 + values.put(Bookmarks.DATE_CREATED, now); 1.830 + values.put(Bookmarks.DATE_MODIFIED, now); 1.831 + values.put(Bookmarks.TYPE, BrowserContract.Bookmarks.TYPE_FOLDER); 1.832 + 1.833 + return values; 1.834 + } 1.835 + 1.836 + protected static ContentValues fennecMobileRecordWithoutTitle() { 1.837 + ContentValues values = specialFolder(); 1.838 + values.put(BrowserContract.SyncColumns.GUID, "mobile"); 1.839 + values.putNull(BrowserContract.Bookmarks.TITLE); 1.840 + 1.841 + return values; 1.842 + } 1.843 + 1.844 + protected ContentValues fennecPinnedItemsRecord() { 1.845 + final ContentValues values = specialFolder(); 1.846 + final String title = getApplicationContext().getResources().getString(R.string.bookmarks_folder_pinned); 1.847 + 1.848 + values.put(BrowserContract.SyncColumns.GUID, Bookmarks.PINNED_FOLDER_GUID); 1.849 + values.put(Bookmarks._ID, Bookmarks.FIXED_PINNED_LIST_ID); 1.850 + values.put(Bookmarks.PARENT, Bookmarks.FIXED_ROOT_ID); 1.851 + values.put(Bookmarks.TITLE, title); 1.852 + return values; 1.853 + } 1.854 + 1.855 + protected static ContentValues fennecPinnedChildItemRecord() { 1.856 + ContentValues values = new ContentValues(); 1.857 + 1.858 + final long now = System.currentTimeMillis(); 1.859 + 1.860 + values.put(BrowserContract.SyncColumns.GUID, "dapinneditem"); 1.861 + values.put(Bookmarks.DATE_CREATED, now); 1.862 + values.put(Bookmarks.DATE_MODIFIED, now); 1.863 + values.put(Bookmarks.TYPE, BrowserContract.Bookmarks.TYPE_BOOKMARK); 1.864 + values.put(Bookmarks.URL, "user-entered:foobar"); 1.865 + values.put(Bookmarks.PARENT, Bookmarks.FIXED_PINNED_LIST_ID); 1.866 + values.put(Bookmarks.TITLE, "Foobar"); 1.867 + return values; 1.868 + } 1.869 + 1.870 + protected ContentValues fennecReadingListRecord() { 1.871 + final ContentValues values = specialFolder(); 1.872 + final String title = getApplicationContext().getResources().getString(R.string.bookmarks_folder_reading_list); 1.873 + 1.874 + values.put(BrowserContract.SyncColumns.GUID, Bookmarks.READING_LIST_FOLDER_GUID); 1.875 + values.put(Bookmarks._ID, Bookmarks.FIXED_READING_LIST_ID); 1.876 + values.put(Bookmarks.PARENT, Bookmarks.FIXED_ROOT_ID); 1.877 + values.put(Bookmarks.TITLE, title); 1.878 + return values; 1.879 + } 1.880 + 1.881 + protected long setUpFennecMobileRecordWithoutTitle() { 1.882 + ContentResolver cr = getApplicationContext().getContentResolver(); 1.883 + ContentValues values = fennecMobileRecordWithoutTitle(); 1.884 + updateRow(values); 1.885 + return fennecGetMobileBookmarksFolderId(cr); 1.886 + } 1.887 + 1.888 + protected long setUpFennecMobileRecord() { 1.889 + ContentResolver cr = getApplicationContext().getContentResolver(); 1.890 + ContentValues values = fennecMobileRecordWithoutTitle(); 1.891 + values.put(BrowserContract.Bookmarks.PARENT, BrowserContract.Bookmarks.FIXED_ROOT_ID); 1.892 + String title = getApplicationContext().getResources().getString(R.string.bookmarks_folder_mobile); 1.893 + values.put(BrowserContract.Bookmarks.TITLE, title); 1.894 + updateRow(values); 1.895 + return fennecGetMobileBookmarksFolderId(cr); 1.896 + } 1.897 + 1.898 + protected void setUpFennecPinnedItemsRecord() { 1.899 + insertRow(fennecPinnedItemsRecord()); 1.900 + insertRow(fennecPinnedChildItemRecord()); 1.901 + } 1.902 + 1.903 + protected void setUpFennecReadingListRecord() { 1.904 + insertRow(fennecReadingListRecord()); 1.905 + } 1.906 + 1.907 + // 1.908 + // Fennec fake layer. 1.909 + // 1.910 + private Uri appendProfile(Uri uri) { 1.911 + final String defaultProfile = "default"; // Fennec constant removed in Bug 715307. 1.912 + return uri.buildUpon().appendQueryParameter(BrowserContract.PARAM_PROFILE, defaultProfile).build(); 1.913 + } 1.914 + 1.915 + private long fennecGetFolderId(ContentResolver cr, String guid) { 1.916 + Cursor c = null; 1.917 + try { 1.918 + c = cr.query(appendProfile(BrowserContractHelpers.BOOKMARKS_CONTENT_URI), 1.919 + new String[] { BrowserContract.Bookmarks._ID }, 1.920 + BrowserContract.Bookmarks.GUID + " = ?", 1.921 + new String[] { guid }, 1.922 + null); 1.923 + 1.924 + if (c.moveToFirst()) { 1.925 + return c.getLong(c.getColumnIndexOrThrow(BrowserContract.Bookmarks._ID)); 1.926 + } 1.927 + return -1; 1.928 + } finally { 1.929 + if (c != null) { 1.930 + c.close(); 1.931 + } 1.932 + } 1.933 + } 1.934 + 1.935 + private long fennecGetMobileBookmarksFolderId(ContentResolver cr) { 1.936 + return fennecGetFolderId(cr, BrowserContract.Bookmarks.MOBILE_FOLDER_GUID); 1.937 + } 1.938 + 1.939 + public void fennecAddBookmark(String title, String uri) { 1.940 + ContentResolver cr = getApplicationContext().getContentResolver(); 1.941 + 1.942 + long folderId = fennecGetMobileBookmarksFolderId(cr); 1.943 + if (folderId < 0) { 1.944 + return; 1.945 + } 1.946 + 1.947 + ContentValues values = new ContentValues(); 1.948 + values.put(BrowserContract.Bookmarks.TITLE, title); 1.949 + values.put(BrowserContract.Bookmarks.URL, uri); 1.950 + values.put(BrowserContract.Bookmarks.PARENT, folderId); 1.951 + 1.952 + // Restore deleted record if possible 1.953 + values.put(BrowserContract.Bookmarks.IS_DELETED, 0); 1.954 + 1.955 + Logger.debug(getName(), "Adding bookmark " + title + ", " + uri + " in " + folderId); 1.956 + int updated = cr.update(appendProfile(BrowserContractHelpers.BOOKMARKS_CONTENT_URI), 1.957 + values, 1.958 + BrowserContract.Bookmarks.URL + " = ?", 1.959 + new String[] { uri }); 1.960 + 1.961 + if (updated == 0) { 1.962 + Uri insert = cr.insert(appendProfile(BrowserContractHelpers.BOOKMARKS_CONTENT_URI), values); 1.963 + long idFromUri = ContentUris.parseId(insert); 1.964 + Logger.debug(getName(), "Inserted " + uri + " as " + idFromUri); 1.965 + Logger.debug(getName(), "Position is " + getPosition(idFromUri)); 1.966 + } 1.967 + } 1.968 + 1.969 + private long getPosition(long idFromUri) { 1.970 + ContentResolver cr = getApplicationContext().getContentResolver(); 1.971 + Cursor c = cr.query(appendProfile(BrowserContractHelpers.BOOKMARKS_CONTENT_URI), 1.972 + new String[] { BrowserContract.Bookmarks.POSITION }, 1.973 + BrowserContract.Bookmarks._ID + " = ?", 1.974 + new String[] { String.valueOf(idFromUri) }, 1.975 + null); 1.976 + if (!c.moveToFirst()) { 1.977 + return -2; 1.978 + } 1.979 + return c.getLong(0); 1.980 + } 1.981 + 1.982 + protected AndroidBrowserBookmarksDataAccessor dataAccessor = null; 1.983 + protected AndroidBrowserBookmarksDataAccessor getDataAccessor() { 1.984 + if (dataAccessor == null) { 1.985 + dataAccessor = new AndroidBrowserBookmarksDataAccessor(getApplicationContext()); 1.986 + } 1.987 + return dataAccessor; 1.988 + } 1.989 + 1.990 + protected void wipe() { 1.991 + Logger.debug(getName(), "Wiping."); 1.992 + getDataAccessor().wipe(); 1.993 + } 1.994 + 1.995 + protected void assertChildrenAreOrdered(AndroidBrowserBookmarksRepository repo, String guid, Record[] expected) { 1.996 + Logger.debug(getName(), "Fetching children..."); 1.997 + JSONArray folderChildren = fetchChildrenForGUID(repo, guid); 1.998 + 1.999 + assertTrue(folderChildren != null); 1.1000 + Logger.debug(getName(), "Children are " + folderChildren.toJSONString()); 1.1001 + assertEquals(expected.length, folderChildren.size()); 1.1002 + for (int i = 0; i < expected.length; ++i) { 1.1003 + assertEquals(expected[i].guid, ((String) folderChildren.get(i))); 1.1004 + } 1.1005 + } 1.1006 + 1.1007 + protected void assertChildrenAreUnordered(AndroidBrowserBookmarksRepository repo, String guid, Record[] expected) { 1.1008 + Logger.debug(getName(), "Fetching children..."); 1.1009 + JSONArray folderChildren = fetchChildrenForGUID(repo, guid); 1.1010 + 1.1011 + assertTrue(folderChildren != null); 1.1012 + Logger.debug(getName(), "Children are " + folderChildren.toJSONString()); 1.1013 + assertEquals(expected.length, folderChildren.size()); 1.1014 + for (Record record : expected) { 1.1015 + folderChildren.contains(record.guid); 1.1016 + } 1.1017 + } 1.1018 + 1.1019 + /** 1.1020 + * Return a sequence of children GUIDs for the provided folder ID. 1.1021 + */ 1.1022 + protected ArrayList<String> fetchChildrenDirect(long id) { 1.1023 + Logger.debug(getName(), "Fetching children directly from DB..."); 1.1024 + final ArrayList<String> out = new ArrayList<String>(); 1.1025 + final AndroidBrowserBookmarksDataAccessor accessor = new AndroidBrowserBookmarksDataAccessor(getApplicationContext()); 1.1026 + Cursor cur = null; 1.1027 + try { 1.1028 + cur = accessor.getChildren(id); 1.1029 + } catch (NullCursorException e) { 1.1030 + fail("Got null cursor."); 1.1031 + } 1.1032 + try { 1.1033 + if (!cur.moveToFirst()) { 1.1034 + return out; 1.1035 + } 1.1036 + final int guidCol = cur.getColumnIndex(BrowserContract.SyncColumns.GUID); 1.1037 + while (!cur.isAfterLast()) { 1.1038 + out.add(cur.getString(guidCol)); 1.1039 + cur.moveToNext(); 1.1040 + } 1.1041 + } finally { 1.1042 + cur.close(); 1.1043 + } 1.1044 + return out; 1.1045 + } 1.1046 + 1.1047 + /** 1.1048 + * Assert that the children of the provided ID are correct and positioned in the database. 1.1049 + * @param id 1.1050 + * @param guids 1.1051 + */ 1.1052 + protected void assertChildrenAreDirect(long id, String[] guids) { 1.1053 + Logger.debug(getName(), "Fetching children directly from DB..."); 1.1054 + AndroidBrowserBookmarksDataAccessor accessor = new AndroidBrowserBookmarksDataAccessor(getApplicationContext()); 1.1055 + Cursor cur = null; 1.1056 + try { 1.1057 + cur = accessor.getChildren(id); 1.1058 + } catch (NullCursorException e) { 1.1059 + fail("Got null cursor."); 1.1060 + } 1.1061 + try { 1.1062 + if (guids == null || guids.length == 0) { 1.1063 + assertFalse(cur.moveToFirst()); 1.1064 + return; 1.1065 + } 1.1066 + 1.1067 + assertTrue(cur.moveToFirst()); 1.1068 + int i = 0; 1.1069 + final int guidCol = cur.getColumnIndex(BrowserContract.SyncColumns.GUID); 1.1070 + final int posCol = cur.getColumnIndex(BrowserContract.Bookmarks.POSITION); 1.1071 + while (!cur.isAfterLast()) { 1.1072 + assertTrue(i < guids.length); 1.1073 + final String guid = cur.getString(guidCol); 1.1074 + final int pos = cur.getInt(posCol); 1.1075 + Logger.debug(getName(), "Fetched child: " + guid + " has position " + pos); 1.1076 + assertEquals(guids[i], guid); 1.1077 + assertEquals(i, pos); 1.1078 + 1.1079 + ++i; 1.1080 + cur.moveToNext(); 1.1081 + } 1.1082 + assertEquals(guids.length, i); 1.1083 + } finally { 1.1084 + cur.close(); 1.1085 + } 1.1086 + } 1.1087 +} 1.1088 + 1.1089 +/** 1.1090 +TODO 1.1091 + 1.1092 +Test for storing a record that will reconcile to mobile; postcondition is 1.1093 +that there's still a directory called mobile that includes all the items that 1.1094 +it used to. 1.1095 + 1.1096 +mobile folder created without title. 1.1097 +Unsorted put in mobile??? 1.1098 +Tests for children retrieval 1.1099 +Tests for children merge 1.1100 +Tests for modify retrieve parent when child added, removed, reordered (oh, reorder is hard! Any change, then.) 1.1101 +Safety mode? 1.1102 +Test storing folder first, contents first. 1.1103 +Store folder in next session. Verify order recovery. 1.1104 + 1.1105 + 1.1106 +*/