[WIP]
Bug 972193 - Part 1: introduce BrowserDB.getBookmarkFolders
MozReview-Commit-ID: A5x4HXa7Pe0
--- a/mobile/android/base/java/org/mozilla/gecko/db/BrowserDB.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/BrowserDB.java
@@ -3,16 +3,17 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.db;
import java.io.File;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
+import java.util.TreeMap;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.annotation.RobocopTarget;
import org.mozilla.gecko.db.BrowserContract.ExpirePriority;
import org.mozilla.gecko.distribution.Distribution;
import org.mozilla.gecko.favicons.decoders.LoadFaviconResult;
import android.content.ContentProviderOperation;
@@ -27,16 +28,34 @@ import android.graphics.drawable.BitmapD
* that implements this, you should go through GeckoProfile. E.g.,
* <code>GeckoProfile.get(context).getDB()</code>.
*
* GeckoProfile itself will construct an appropriate subclass using
* a factory that the containing application can set with
* {@link GeckoProfile#setBrowserDBFactory(BrowserDB.Factory)}.
*/
public interface BrowserDB {
+ public class BookmarkFolderTree {
+ // Using a TreeMap allows walking the folders in alphabetical order
+ // In most cases the Key will correspond to the folder's name, however this is not true
+ // in all cases. We use artificial keys for the Mobile/Desktop folder to guarantee
+ // ordering within the root.
+ public TreeMap<String, BookmarkFolderTree> children = new TreeMap<>();
+
+ public final int id;
+ public final int parent;
+ public final String name;
+
+ public BookmarkFolderTree(int bookmarkID, int parentID, String bookmarkName) {
+ this.id = bookmarkID;
+ this.parent = parentID;
+ this.name = bookmarkName;
+ }
+ }
+
public interface Factory {
public BrowserDB get(String profileName, File profileDir);
}
public static enum FilterFlags {
EXCLUDE_PINNED_SITES
}
@@ -116,16 +135,18 @@ public interface BrowserDB {
public abstract void updateBookmark(ContentResolver cr, int id, String uri, String title, String keyword);
public abstract boolean hasBookmarkWithGuid(ContentResolver cr, String guid);
/**
* Can return <code>null</code>.
*/
public abstract Cursor getBookmarksInFolder(ContentResolver cr, long folderId);
+ public BookmarkFolderTree getBookmarkFolders(ContentResolver cr, Context context);
+
/**
* Get the favicon from the database, if any, associated with the given favicon URL. (That is,
* the URL of the actual favicon image, not the URL of the page with which the favicon is associated.)
* @param cr The ContentResolver to use.
* @param faviconURL The URL of the favicon to fetch from the database.
* @return The decoded Bitmap from the database, if any. null if none is stored.
*/
public abstract LoadFaviconResult getFaviconForUrl(ContentResolver cr, String faviconURL);
--- a/mobile/android/base/java/org/mozilla/gecko/db/LocalBrowserDB.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/LocalBrowserDB.java
@@ -1714,9 +1714,133 @@ public class LocalBrowserDB implements B
rb.add(-1);
rb.add("");
rb.add("");
rb.add(TopSites.TYPE_BLANK);
return new MergeCursor(new Cursor[] {topSitesCursor, blanksCursor});
}
+
+ private static void debugDumpFolder(BookmarkFolderTree folder, String prefix) {
+ if (folder == null) {
+ return;
+ }
+
+ Log.w("FOLDERS", prefix + folder.name);
+ for (BookmarkFolderTree f : folder.children.values()) {
+ debugDumpFolder(f, prefix + "*");
+ }
+ }
+
+ public BookmarkFolderTree getBookmarkFolders(ContentResolver cr, Context context) {
+ final Cursor c = cr.query(mBookmarksUriWithProfile,
+ new String[] {
+ Bookmarks._ID,
+ Bookmarks.PARENT,
+ Bookmarks.GUID,
+ Bookmarks.TITLE
+ },
+ Bookmarks.TYPE + " = " + Bookmarks.TYPE_FOLDER +
+ " AND " + Bookmarks.GUID + " IS NOT ?" +
+ " AND " + Bookmarks.GUID + " IS NOT ?" +
+ " AND " + Bookmarks._ID + " IS NOT " + Bookmarks.FIXED_READING_LIST_ID,
+ new String[] {
+ Bookmarks.PINNED_FOLDER_GUID,
+ Bookmarks.TAGS_FOLDER_GUID
+ },
+ null);
+
+ if (c == null) {
+ return null;
+ }
+
+ final HashMap<Integer, BookmarkFolderTree> nodes = new HashMap<>();
+
+ final String desktopFolderName = "Desktop";
+ final BookmarkFolderTree fakeDesktop = new BookmarkFolderTree(Bookmarks.FAKE_DESKTOP_FOLDER_ID, Bookmarks.FIXED_ROOT_ID, desktopFolderName);
+ nodes.put(Bookmarks.FAKE_DESKTOP_FOLDER_ID, fakeDesktop);
+
+ final int idIndex = c.getColumnIndexOrThrow(Bookmarks._ID);
+ final int parentIndex = c.getColumnIndexOrThrow(Bookmarks.PARENT);
+ final int nameIndex = c.getColumnIndexOrThrow(Bookmarks.TITLE);
+ final int guidIndex = c.getColumnIndexOrThrow(Bookmarks.GUID);
+
+ try {
+ if (!c.moveToFirst()) {
+ return null;
+ }
+
+ do {
+ final String guid = c.getString(guidIndex);
+ final int parentId;
+
+ switch (guid) {
+ // By default these are subfolders of places, however we want to make them
+ // subfolders of Desktop Bookmarks.
+ case Bookmarks.TOOLBAR_FOLDER_GUID:
+ case Bookmarks.UNFILED_FOLDER_GUID:
+ case Bookmarks.MENU_FOLDER_GUID:
+ parentId = Bookmarks.FAKE_DESKTOP_FOLDER_ID;
+ break;
+
+ default:
+ parentId = c.getInt(parentIndex);
+ }
+
+ final String folderName = c.getString(nameIndex);
+ final int id = c.getInt(idIndex);
+
+ BookmarkFolderTree folder = new BookmarkFolderTree(id, parentId, folderName);
+
+ // We want to ignore any folders that aren't either a subfolder of one of the 3 desktop folders,
+ // or a subfolder of mobile. I.e. we want to discard all folders with places as their parent
+ // (which includes the pinned folder, and "All Bookmarks"), except for those 3 desktop folders (handled
+ // above), and mobile bookmarks and places itself (which has itself as its parent) -
+ // handled here.
+ if (parentId != Bookmarks.FIXED_ROOT_ID ||
+ guid.equals(Bookmarks.MOBILE_FOLDER_GUID) ||
+ id == Bookmarks.FIXED_ROOT_ID) {
+ nodes.put(folder.id, folder);
+ }
+ } while (c.moveToNext());
+ } finally {
+ c.close();
+ }
+
+ // Build the tree!
+ for (BookmarkFolderTree folder : nodes.values()) {
+ BookmarkFolderTree parent = nodes.get(folder.parent);
+
+ if (parent == null || parent == folder) {
+ // We need to avoid parenting:
+ // (1) the root / places folder: it is its own parent, attaching it would create a loop
+ // (2) orphaned folders - this can happen if sync has problems
+ continue;
+ }
+
+ // We want to ensure that Mobile bookmarks appear before desktop bookmarks.
+ if (parent.id == Bookmarks.FIXED_ROOT_ID) {
+ if (folder.id == Bookmarks.FAKE_DESKTOP_FOLDER_ID) {
+ parent.children.put("B", folder);
+ } else {
+ // Mobile folder: it's the only remaining folder (we've filtered out all
+ // other first-level folders in our first pass). We could uniquely identify
+ // it by the GUID, however adding an additional field to track GUID in
+ // BookmarkFolderTree seems wasteful.
+ parent.children.put("A", folder);
+ }
+ } else {
+ parent.children.put(folder.name, folder);
+ }
+ }
+
+ final BookmarkFolderTree root = nodes.get(0);
+
+ if (root == null) {
+ throw new IllegalStateException("root / places bookmarks folder must exist");
+ }
+
+ debugDumpFolder(root, "*");
+
+ return root;
+ }
}
--- a/mobile/android/base/java/org/mozilla/gecko/db/StubBrowserDB.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/StubBrowserDB.java
@@ -287,16 +287,20 @@ public class StubBrowserDB implements Br
public void clearHistory(ContentResolver cr, boolean clearSearchHistory) {
}
@RobocopTarget
public Cursor getBookmarksInFolder(ContentResolver cr, long folderId) {
return null;
}
+ public BookmarkFolderTree getBookmarkFolders(ContentResolver cr, Context context) {
+ return null;
+ }
+
public Cursor getReadingList(ContentResolver cr) {
return null;
}
public Cursor getReadingListUnfetched(ContentResolver cr) {
return null;
}