[WIP]
Bug 1232439 (972193) - Part 1: introduce BrowserDB.getBookmarkFolders
Rebased by Tom Klein.
MozReview-Commit-ID: 53xErLzTiq5
--- 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.icons.decoders.LoadFaviconResult;
import android.content.ContentProviderClient;
@@ -25,16 +26,34 @@ import android.graphics.drawable.BitmapD
import android.support.v4.content.CursorLoader;
/**
* Interface for interactions with all databases. If you want an instance
* that implements this, you should go through GeckoProfile. E.g.,
* <code>BrowserDB.from(context)</code>.
*/
public abstract class 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 static enum FilterFlags {
EXCLUDE_PINNED_SITES
}
public abstract Searches getSearches();
public abstract TabsAccessor getTabsAccessor();
public abstract URLMetadata getURLMetadata();
@RobocopTarget public abstract UrlAnnotations getUrlAnnotations();
@@ -116,16 +135,18 @@ public abstract class BrowserDB {
public abstract boolean insertPageMetadata(ContentProviderClient contentProviderClient, String pageUrl, boolean hasImage, String metadataJSON);
public abstract int deletePageMetadata(ContentProviderClient contentProviderClient, String pageUrl);
/**
* Can return <code>null</code>.
*/
public abstract Cursor getBookmarksInFolder(ContentResolver cr, long folderId);
+ public abstract BookmarkFolderTree getBookmarkFolders(ContentResolver cr);
+
public abstract int getBookmarkCountForFolder(ContentResolver cr, long folderId);
/**
* 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.
--- a/mobile/android/base/java/org/mozilla/gecko/db/LocalBrowserDB.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/LocalBrowserDB.java
@@ -1999,9 +1999,132 @@ public class LocalBrowserDB extends Brow
@Override
public void blockActivityStreamSite(ContentResolver cr, String url) {
final ContentValues values = new ContentValues();
values.put(ActivityStreamBlocklist.URL, url);
cr.insert(mActivityStreamBlockedUriWithProfile, values);
}
+ private static void debugDumpFolder(BookmarkFolderTree folder, String prefix) {
+ if (folder == null) {
+ return;
+ }
+
+ Log.d("***FOLDERS", prefix + folder.name);
+ for (BookmarkFolderTree f : folder.children.values()) {
+ debugDumpFolder(f, prefix + "*");
+ }
+ }
+
+ public BookmarkFolderTree getBookmarkFolders(ContentResolver cr) {
+ 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;
+ }
}