Bug 1257345 - Implement reading list smartfolder r=mcomella
authorAndrzej Hunt <ahunt@mozilla.com>
Thu, 07 Apr 2016 14:30:16 -0700
changeset 316160 d5064e39467cc518c50603cb5603caad8e9510e0
parent 316159 94e0c7b66782e265c22aa6a90c519ced4a2562de
child 316161 18434342c5638551756e00bec2463c30a4a9abcc
push id9480
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 17:12:58 +0000
treeherdermozilla-aurora@0d6a91c76a9e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmcomella
bugs1257345
milestone48.0a1
Bug 1257345 - Implement reading list smartfolder r=mcomella MozReview-Commit-ID: 7AuWRAZ7Sq5
mobile/android/base/java/org/mozilla/gecko/db/BrowserContract.java
mobile/android/base/java/org/mozilla/gecko/db/BrowserProvider.java
mobile/android/base/java/org/mozilla/gecko/db/LocalBrowserDB.java
mobile/android/base/java/org/mozilla/gecko/home/BookmarksListAdapter.java
mobile/android/base/locales/en-US/android_strings.dtd
mobile/android/base/strings.xml.in
--- a/mobile/android/base/java/org/mozilla/gecko/db/BrowserContract.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/BrowserContract.java
@@ -45,16 +45,17 @@ public class BrowserContract {
     public static final String PARAM_SUGGESTEDSITES_LIMIT = "suggestedsites_limit";
     public static final String PARAM_IS_SYNC = "sync";
     public static final String PARAM_SHOW_DELETED = "show_deleted";
     public static final String PARAM_IS_TEST = "test";
     public static final String PARAM_INSERT_IF_NEEDED = "insert_if_needed";
     public static final String PARAM_INCREMENT_VISITS = "increment_visits";
     public static final String PARAM_EXPIRE_PRIORITY = "priority";
     public static final String PARAM_DATASET_ID = "dataset_id";
+    public static final String PARAM_GROUP_BY = "group_by";
 
     static public enum ExpirePriority {
         NORMAL,
         AGGRESSIVE
     }
 
     static public String getFrecencySortOrder(boolean includesBookmarks, boolean asc) {
         final String age = "(" + Combined.DATE_LAST_VISITED + " - " + System.currentTimeMillis() + ") / 86400000";
@@ -152,26 +153,28 @@ public class BrowserContract {
 
         public static final String VIEW_WITH_ANNOTATIONS = "bookmarks_with_annotations";
 
         public static final int FIXED_ROOT_ID = 0;
         public static final int FAKE_DESKTOP_FOLDER_ID = -1;
         public static final int FIXED_READING_LIST_ID = -2;
         public static final int FIXED_PINNED_LIST_ID = -3;
         public static final int FIXED_SCREENSHOT_FOLDER_ID = -4;
+        public static final int FAKE_READINGLIST_SMARTFOLDER_ID = -5;
 
         public static final String MOBILE_FOLDER_GUID = "mobile";
         public static final String PLACES_FOLDER_GUID = "places";
         public static final String MENU_FOLDER_GUID = "menu";
         public static final String TAGS_FOLDER_GUID = "tags";
         public static final String TOOLBAR_FOLDER_GUID = "toolbar";
         public static final String UNFILED_FOLDER_GUID = "unfiled";
         public static final String FAKE_DESKTOP_FOLDER_GUID = "desktop";
         public static final String PINNED_FOLDER_GUID = "pinned";
         public static final String SCREENSHOT_FOLDER_GUID = "screenshots";
+        public static final String FAKE_READINGLIST_SMARTFOLDER_GUID = "readinglist";
 
         public static final int TYPE_FOLDER = 0;
         public static final int TYPE_BOOKMARK = 1;
         public static final int TYPE_SEPARATOR = 2;
         public static final int TYPE_LIVEMARK = 3;
         public static final int TYPE_QUERY = 4;
 
         public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "bookmarks");
--- a/mobile/android/base/java/org/mozilla/gecko/db/BrowserProvider.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/BrowserProvider.java
@@ -65,16 +65,17 @@ public class BrowserProvider extends Sha
     static final String TABLE_HISTORY = History.TABLE_NAME;
     static final String TABLE_FAVICONS = Favicons.TABLE_NAME;
     static final String TABLE_THUMBNAILS = Thumbnails.TABLE_NAME;
     static final String TABLE_TABS = Tabs.TABLE_NAME;
     static final String TABLE_URL_ANNOTATIONS = UrlAnnotations.TABLE_NAME;
 
     static final String VIEW_COMBINED = Combined.VIEW_NAME;
     static final String VIEW_BOOKMARKS_WITH_FAVICONS = Bookmarks.VIEW_WITH_FAVICONS;
+    static final String VIEW_BOOKMARKS_WITH_ANNOTATIONS = Bookmarks.VIEW_WITH_ANNOTATIONS;
     static final String VIEW_HISTORY_WITH_FAVICONS = History.VIEW_WITH_FAVICONS;
     static final String VIEW_COMBINED_WITH_FAVICONS = Combined.VIEW_WITH_FAVICONS;
 
     // Bookmark matches
     static final int BOOKMARKS = 100;
     static final int BOOKMARKS_ID = 101;
     static final int BOOKMARKS_FOLDER_ID = 102;
     static final int BOOKMARKS_PARENT = 103;
@@ -996,20 +997,25 @@ public class BrowserProvider extends Sha
                 if (TextUtils.isEmpty(sortOrder)) {
                     sortOrder = DEFAULT_BOOKMARKS_SORT_ORDER;
                 } else {
                     debug("Using sort order " + sortOrder + ".");
                 }
 
                 qb.setProjectionMap(BOOKMARKS_PROJECTION_MAP);
 
-                if (hasFaviconsInProjection(projection))
+                if (hasFaviconsInProjection(projection)) {
                     qb.setTables(VIEW_BOOKMARKS_WITH_FAVICONS);
-                else
+                } else if (selection.contains(Bookmarks.ANNOTATION_KEY)) {
+                    qb.setTables(VIEW_BOOKMARKS_WITH_ANNOTATIONS);
+
+                    groupBy = uri.getQueryParameter(BrowserContract.PARAM_GROUP_BY);
+                } else {
                     qb.setTables(TABLE_BOOKMARKS);
+                }
 
                 break;
             }
 
             case HISTORY_ID:
                 selection = DBUtils.concatenateWhere(selection, History._ID + " = ?");
                 selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
                         new String[] { Long.toString(ContentUris.parseId(uri)) });
--- a/mobile/android/base/java/org/mozilla/gecko/db/LocalBrowserDB.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/LocalBrowserDB.java
@@ -773,91 +773,129 @@ public class LocalBrowserDB implements B
                 (DEFAULT_BOOKMARK_COLUMNS[5].equals(Bookmarks.PARENT)) &&
                 (DEFAULT_BOOKMARK_COLUMNS.length == 6))) {
             // If DEFAULT_BOOKMARK_COLUMNS changes we need to update all the MatrixCursor rows
             // to contain appropriate data.
             throw new IllegalStateException("Fake folder MatrixCursor creation code must be updated to match DEFAULT_BOOKMARK_COLUMNS");
         }
     }
 
+    /**
+     * Retrieve the list of reader-view bookmarks, i.e. the equivalent of the former reading-list.
+     * This is the result of a join of bookmarks with reader-view annotations (as stored in
+     * UrlAnnotations).
+     */
+    private Cursor getReadingListBookmarks(ContentResolver cr) {
+        // group by URL to avoid having duplicate bookmarks listed. It's possible to have multiple
+        // bookmarks pointing to the same URL (this would most commonly happen by manually
+        // copying bookmarks on desktop, followed by syncing with mobile), and we don't want
+        // to show the same URL multiple times in the reading list folder.
+        final Uri bookmarksGroupedByUri = mBookmarksUriWithProfile.buildUpon()
+                .appendQueryParameter(BrowserContract.PARAM_GROUP_BY, Bookmarks.URL)
+                .build();
+
+        return cr.query(bookmarksGroupedByUri,
+                DEFAULT_BOOKMARK_COLUMNS,
+                Bookmarks.ANNOTATION_KEY + " == ? AND " +
+                Bookmarks.ANNOTATION_VALUE + " == ? AND " +
+                "(" + Bookmarks.TYPE + " = ? AND " + Bookmarks.URL + " IS NOT NULL)",
+                new String[] {
+                        BrowserContract.UrlAnnotations.Key.READER_VIEW.getDbValue(),
+                        BrowserContract.UrlAnnotations.READER_VIEW_SAVED_VALUE,
+                        String.valueOf(Bookmarks.TYPE_BOOKMARK) },
+                null);
+    }
+
     @Override
     @RobocopTarget
     public Cursor getBookmarksInFolder(ContentResolver cr, long folderId) {
         final boolean addDesktopFolder;
         final boolean addScreenshotsFolder;
+        final boolean addReadingListFolder;
 
         // We always want to show mobile bookmarks in the root view.
         if (folderId == Bookmarks.FIXED_ROOT_ID) {
             folderId = getFolderIdFromGuid(cr, Bookmarks.MOBILE_FOLDER_GUID);
 
             // We'll add a fake "Desktop Bookmarks" folder to the root view if desktop
             // bookmarks exist, so that the user can still access non-mobile bookmarks.
             addDesktopFolder = desktopBookmarksExist(cr);
             addScreenshotsFolder = AppConstants.SCREENSHOTS_IN_BOOKMARKS_ENABLED;
+            addReadingListFolder = true;
         } else {
             addDesktopFolder = false;
             addScreenshotsFolder = false;
+            addReadingListFolder = false;
         }
 
         final Cursor c;
+
+        // (You can't switch on a long in Java, hence the if statements)
         if (folderId == Bookmarks.FAKE_DESKTOP_FOLDER_ID) {
             // Since the "Desktop Bookmarks" folder doesn't actually exist, we
             // just fake it by querying specifically certain known desktop folders.
             c = cr.query(mBookmarksUriWithProfile,
                          DEFAULT_BOOKMARK_COLUMNS,
                          Bookmarks.GUID + " = ? OR " +
                          Bookmarks.GUID + " = ? OR " +
                          Bookmarks.GUID + " = ?",
                          new String[] { Bookmarks.TOOLBAR_FOLDER_GUID,
                                         Bookmarks.MENU_FOLDER_GUID,
                                         Bookmarks.UNFILED_FOLDER_GUID },
                          null);
         } else if (folderId == Bookmarks.FIXED_SCREENSHOT_FOLDER_ID) {
             c = getUrlAnnotations().getScreenshots(cr);
+        } else if (folderId == Bookmarks.FAKE_READINGLIST_SMARTFOLDER_ID) {
+            c = getReadingListBookmarks(cr);
         } else {
             // Right now, we only support showing folder and bookmark type of
             // entries. We should add support for other types though (bug 737024)
             c = cr.query(mBookmarksUriWithProfile,
                          DEFAULT_BOOKMARK_COLUMNS,
                          Bookmarks.PARENT + " = ? AND " +
                          "(" + Bookmarks.TYPE + " = ? OR " +
                             "(" + Bookmarks.TYPE + " = ? AND " + Bookmarks.URL + " IS NOT NULL))",
                          new String[] { String.valueOf(folderId),
                                         String.valueOf(Bookmarks.TYPE_FOLDER),
                                         String.valueOf(Bookmarks.TYPE_BOOKMARK) },
                          null);
         }
 
-        final List<Cursor> cursorsToMerge = getSpecialFoldersCursorList(addDesktopFolder, addScreenshotsFolder);
+        final List<Cursor> cursorsToMerge = getSpecialFoldersCursorList(addDesktopFolder, addScreenshotsFolder, addReadingListFolder);
         if (cursorsToMerge.size() >= 1) {
             cursorsToMerge.add(c);
             final Cursor[] arr = (Cursor[]) Array.newInstance(Cursor.class, cursorsToMerge.size());
             return new MergeCursor(cursorsToMerge.toArray(arr));
         } else {
             return c;
         }
     }
 
     @CheckResult
-    private ArrayList<Cursor> getSpecialFoldersCursorList(final boolean addDesktopFolder, final boolean addScreenshotsFolder) {
-        if (addDesktopFolder || addScreenshotsFolder) {
+    private ArrayList<Cursor> getSpecialFoldersCursorList(final boolean addDesktopFolder,
+            final boolean addScreenshotsFolder, final boolean addReadingListFolder) {
+        if (addDesktopFolder || addScreenshotsFolder || addReadingListFolder) {
             // Avoid calling this twice.
             assertDefaultBookmarkColumnOrdering();
         }
 
         // Capacity is number of cursors added below plus one for non-special data.
-        final ArrayList<Cursor> out = new ArrayList<>(3);
+        final ArrayList<Cursor> out = new ArrayList<>(4);
         if (addDesktopFolder) {
             out.add(getSpecialFolderCursor(Bookmarks.FAKE_DESKTOP_FOLDER_ID, Bookmarks.FAKE_DESKTOP_FOLDER_GUID));
         }
 
         if (addScreenshotsFolder) {
             out.add(getSpecialFolderCursor(Bookmarks.FIXED_SCREENSHOT_FOLDER_ID, Bookmarks.SCREENSHOT_FOLDER_GUID));
         }
 
+        if (addReadingListFolder) {
+            out.add(getSpecialFolderCursor(Bookmarks.FAKE_READINGLIST_SMARTFOLDER_ID, Bookmarks.FAKE_READINGLIST_SMARTFOLDER_GUID));
+        }
+
         return out;
     }
 
     @CheckResult
     private MatrixCursor getSpecialFolderCursor(final int folderId, final String folderGuid) {
         final MatrixCursor out = new MatrixCursor(DEFAULT_BOOKMARK_COLUMNS);
         out.addRow(new Object[] {
                 folderId,
--- a/mobile/android/base/java/org/mozilla/gecko/home/BookmarksListAdapter.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/BookmarksListAdapter.java
@@ -272,16 +272,18 @@ class BookmarksListAdapter extends Multi
         } else if (guid.equals(Bookmarks.MENU_FOLDER_GUID)) {
             return res.getString(R.string.bookmarks_folder_menu);
         } else if (guid.equals(Bookmarks.TOOLBAR_FOLDER_GUID)) {
             return res.getString(R.string.bookmarks_folder_toolbar);
         } else if (guid.equals(Bookmarks.UNFILED_FOLDER_GUID)) {
             return res.getString(R.string.bookmarks_folder_unfiled);
         } else if (guid.equals(Bookmarks.SCREENSHOT_FOLDER_GUID)) {
             return res.getString(R.string.screenshot_folder_label_in_bookmarks);
+        } else if (guid.equals(Bookmarks.FAKE_READINGLIST_SMARTFOLDER_GUID)) {
+            return res.getString(R.string.readinglist_smartfolder_label_in_bookmarks);
         }
 
         // If for some reason we have a folder with a special GUID, but it's not one of
         // the special folders we expect in the UI, just return the title from the DB.
         return c.getString(c.getColumnIndexOrThrow(Bookmarks.TITLE));
     }
 
     /**
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -62,16 +62,17 @@
 <!ENTITY bookmark_removed "Bookmark removed">
 <!ENTITY bookmark_updated "Bookmark updated">
 <!ENTITY bookmark_options "Options">
 <!ENTITY screenshot_added_to_bookmarks "Screenshot added to bookmarks">
 <!-- Localization note (screenshot_folder_label_in_bookmarks): We save links to screenshots
      the user takes. The folder we store these links in is located in the bookmarks list
      and is labeled by this String. -->
 <!ENTITY screenshot_folder_label_in_bookmarks "Screenshots">
+<!ENTITY readinglist_smartfolder_label_in_bookmarks "Reading List">
 
 <!ENTITY history_today_section "Today">
 <!ENTITY history_yesterday_section "Yesterday">
 <!ENTITY history_week_section3 "Last 7 days">
 <!ENTITY history_older_section3 "Older than 6 months">
 
 <!ENTITY search "Search">
 <!ENTITY reload "Reload">
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -92,16 +92,17 @@
   <string name="bookmark_remove">&bookmark_remove;</string>
   <string name="bookmark_added">&bookmark_added;</string>
   <string name="bookmark_already_added">&bookmark_already_added;</string>
   <string name="bookmark_removed">&bookmark_removed;</string>
   <string name="bookmark_updated">&bookmark_updated;</string>
   <string name="bookmark_options">&bookmark_options;</string>
   <string name="screenshot_added_to_bookmarks">&screenshot_added_to_bookmarks;</string>
   <string name="screenshot_folder_label_in_bookmarks">&screenshot_folder_label_in_bookmarks;</string>
+  <string name="readinglist_smartfolder_label_in_bookmarks">&readinglist_smartfolder_label_in_bookmarks;</string>
 
   <string name="history_today_section">&history_today_section;</string>
   <string name="history_yesterday_section">&history_yesterday_section;</string>
   <string name="history_week_section">&history_week_section3;</string>
   <string name="history_older_section">&history_older_section3;</string>
 
   <string name="share">&share;</string>
   <string name="share_title">&share_title;</string>