Bug 1293710 - Obtain a list of "highlights" for the Activity Stream panel. r?ahunt,grisha draft
authorSebastian Kaspari <s.kaspari@gmail.com>
Mon, 29 Aug 2016 15:35:17 +0200
changeset 409812 8c29b67a4018f85ddb7c9c3c39685c76129486c4
parent 409737 dbe4b47941c7b3d6298a0ead5e40dd828096c808
child 409813 6598ad2ecdb01aaa8c3b49f397c026948ccaf6d1
push id28557
push users.kaspari@gmail.com
push dateMon, 05 Sep 2016 07:55:10 +0000
reviewersahunt, grisha
bugs1293710, 1298785, 1298786, 1298783
milestone51.0a1
Bug 1293710 - Obtain a list of "highlights" for the Activity Stream panel. r?ahunt,grisha This query resembles how the Activity Stream add-on for desktop Firefox selectes highlights[1]. The desktop version uses the 'rev_host' (reversed host) column to filter the results based on a blacklist and group results by hostname. This has been omitted here (See bug 1298785 and bug 1298786). Additionally the desktop version uses a block list to filter "highlights" that have been removed manually by the user. This is not added here until needed from the UI side (see bug 1298783). [1] https://github.com/mozilla/activity-stream/blob/9eb9f451b553bb62ae9b8d6b41a8ef94a2e020ea/addon/PlacesProvider.js#L578 MozReview-Commit-ID: 9TyIaK21MNJ
mobile/android/base/java/org/mozilla/gecko/db/BrowserContract.java
mobile/android/base/java/org/mozilla/gecko/db/BrowserDB.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/db/StubBrowserDB.java
mobile/android/base/java/org/mozilla/gecko/db/URLMetadataTable.java
--- a/mobile/android/base/java/org/mozilla/gecko/db/BrowserContract.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/BrowserContract.java
@@ -585,16 +585,20 @@ public class BrowserContract {
 
         public static final String BOOKMARK_ID = "bookmark_id";
         public static final String HISTORY_ID = "history_id";
         public static final String TYPE = "type";
 
         public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "topsites");
     }
 
+    public static final class Highlights {
+        public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "highlights");
+    }
+
     @RobocopTarget
     public static final class SearchHistory implements CommonColumns, HistoryColumns {
         private SearchHistory() {}
 
         public static final String CONTENT_TYPE = "vnd.android.cursor.dir/searchhistory";
         public static final String QUERY = "query";
         public static final String DATE = "date";
         public static final String TABLE_NAME = "searchhistory";
--- a/mobile/android/base/java/org/mozilla/gecko/db/BrowserDB.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/BrowserDB.java
@@ -170,9 +170,17 @@ public interface BrowserDB {
     public abstract void unpinSite(ContentResolver cr, int position);
 
     public abstract boolean hideSuggestedSite(String url);
     public abstract void setSuggestedSites(SuggestedSites suggestedSites);
     public abstract SuggestedSites getSuggestedSites();
     public abstract boolean hasSuggestedImageUrl(String url);
     public abstract String getSuggestedImageUrlForUrl(String url);
     public abstract int getSuggestedBackgroundColorForUrl(String url);
+
+    /**
+     * Obtain a set of links for highlights from bookmarks and history.
+     *
+     * @param context The context to load the cursor.
+     * @param limit Maximum number of results to return.
+     */
+    CursorLoader getHighlights(Context context, int limit);
 }
--- a/mobile/android/base/java/org/mozilla/gecko/db/BrowserProvider.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/BrowserProvider.java
@@ -49,16 +49,18 @@ import android.database.SQLException;
 import android.database.sqlite.SQLiteCursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteQueryBuilder;
 import android.net.Uri;
 import android.support.v4.content.LocalBroadcastManager;
 import android.text.TextUtils;
 import android.util.Log;
 
+import static org.mozilla.gecko.db.DBUtils.qualifyColumn;
+
 public class BrowserProvider extends SharedBrowserDatabaseProvider {
     public static final String ACTION_SHRINK_MEMORY = "org.mozilla.gecko.db.intent.action.SHRINK_MEMORY";
 
     private static final String LOGTAG = "GeckoBrowserProvider";
 
     // How many records to reposition in a single query.
     // This should be less than the SQLite maximum number of query variables
     // (currently 999) divided by the number of variables used per positioning
@@ -121,16 +123,20 @@ public class BrowserProvider extends Sha
     static final int THUMBNAIL_ID = 801;
 
     static final int URL_ANNOTATIONS = 900;
 
     static final int TOPSITES = 1000;
 
     static final int VISITS = 1100;
 
+    static final int METADATA = 1200;
+
+    static final int HIGHLIGHTS = 1300;
+
     static final String DEFAULT_BOOKMARKS_SORT_ORDER = Bookmarks.TYPE
             + " ASC, " + Bookmarks.POSITION + " ASC, " + Bookmarks._ID
             + " ASC";
 
     static final String DEFAULT_HISTORY_SORT_ORDER = History.DATE_LAST_VISITED + " DESC";
     static final String DEFAULT_VISITS_SORT_ORDER = Visits.DATE_VISITED + " DESC";
 
     static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
@@ -283,16 +289,18 @@ public class BrowserProvider extends Sha
         for (Table table : sTables) {
             for (Table.ContentProviderInfo type : table.getContentProviderInfo()) {
                 URI_MATCHER.addURI(BrowserContract.AUTHORITY, type.name, type.id);
             }
         }
 
         // Combined pinned sites, top visited sites, and suggested sites
         URI_MATCHER.addURI(BrowserContract.AUTHORITY, "topsites", TOPSITES);
+
+        URI_MATCHER.addURI(BrowserContract.AUTHORITY, "highlights", HIGHLIGHTS);
     }
 
     private static class ShrinkMemoryReceiver extends BroadcastReceiver {
         private final WeakReference<BrowserProvider> mBrowserProviderWeakReference;
 
         public ShrinkMemoryReceiver(final BrowserProvider browserProvider) {
             mBrowserProviderWeakReference = new WeakReference<>(browserProvider);
         }
@@ -1131,16 +1139,70 @@ public class BrowserProvider extends Sha
 
             db.setTransactionSuccessful();
             return c;
         } finally {
             db.endTransaction();
         }
     }
 
+    /**
+     * Obtain a set of links for highlights (from bookmarks and history).
+     *
+     * Based on the query for Activity^ Stream (desktop):
+     * https://github.com/mozilla/activity-stream/blob/9eb9f451b553bb62ae9b8d6b41a8ef94a2e020ea/addon/PlacesProvider.js#L578
+     */
+    public Cursor getHighlights(final SQLiteDatabase db, String limit) {
+        final int totalLimit = limit == null ? 20 : Integer.parseInt(limit);
+
+        final long threeDaysAgo = System.currentTimeMillis() - (1000 * 60 * 60 * 24 * 3);
+        final long bookmarkLimit = 1;
+
+        // Select recent bookmarks that have not been visited much
+        final String bookmarksQuery = "SELECT * FROM (SELECT " +
+                DBUtils.qualifyColumn(Bookmarks.TABLE_NAME, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " +
+                "-1 AS " + Combined.HISTORY_ID + ", " +
+                DBUtils.qualifyColumn(Bookmarks.TABLE_NAME, Bookmarks.URL) + ", " +
+                DBUtils.qualifyColumn(Bookmarks.TABLE_NAME, Bookmarks.TITLE) + " " +
+                "FROM " + Bookmarks.TABLE_NAME + " " +
+                "LEFT JOIN " + History.TABLE_NAME + " ON " +
+                    DBUtils.qualifyColumn(Bookmarks.TABLE_NAME, Bookmarks.URL) + " = " +
+                    DBUtils.qualifyColumn(History.TABLE_NAME, History.URL) + " " +
+                "WHERE " + DBUtils.qualifyColumn(Bookmarks.TABLE_NAME, Bookmarks.DATE_CREATED) + " > " + threeDaysAgo + " " +
+                "AND " + DBUtils.qualifyColumn(History.TABLE_NAME, History.VISITS) + " <= 3 " +
+                "AND " + DBUtils.qualifyColumn(Bookmarks.TABLE_NAME, Bookmarks.IS_DELETED)  + " = 0 " +
+                // TODO: Implement block list (bug 1298783)
+                "ORDER BY " + DBUtils.qualifyColumn(Bookmarks.TABLE_NAME, Bookmarks.DATE_CREATED) + " DESC " +
+                "LIMIT " + bookmarkLimit + ")";
+
+        final long last30Minutes = System.currentTimeMillis() - (1000 * 60 * 30);
+        final long historyLimit = totalLimit - bookmarkLimit;
+
+        // Select recent history that has not been visited much.
+        final String historyQuery = "SELECT * FROM (SELECT " +
+                History._ID + " AS " + Combined.HISTORY_ID + ", " +
+                "-1 AS " + Combined.BOOKMARK_ID + ", " +
+                History.URL + ", " +
+                History.TITLE + " " +
+                "FROM " + History.TABLE_NAME + " " +
+                "WHERE " + History.DATE_LAST_VISITED + " < " + last30Minutes + " " +
+                "AND " + History.VISITS + " <= 3 " +
+                "AND " + History.TITLE + " NOT NULL AND " + History.TITLE + " != '' " +
+                "AND " + History.IS_DELETED + " = 0 " +
+                // TODO: Implement block list (bug 1298783)
+                // TODO: Implement domain black list (bug 1298786)
+                // TODO: Group by host (bug 1298785)
+                "ORDER BY " + History.DATE_LAST_VISITED + " DESC " +
+                "LIMIT " + historyLimit + ")";
+
+        final String query = "SELECT DISTINCT * FROM (" + bookmarksQuery + " UNION ALL " + historyQuery + ");";
+
+        return db.rawQuery(query, null);
+    }
+
     @Override
     public Cursor query(Uri uri, String[] projection, String selection,
             String[] selectionArgs, String sortOrder) {
         final int match = URI_MATCHER.match(uri);
 
         if (match == TOPSITES) {
             if (uri.getBooleanQueryParameter(BrowserContract.PARAM_TOPSITES_DISABLE_PINNED, false)) {
                 return getPlainTopSites(uri);
@@ -1287,16 +1349,22 @@ public class BrowserProvider extends Sha
                 if (hasFaviconsInProjection(projection))
                     qb.setTables(VIEW_COMBINED_WITH_FAVICONS);
                 else
                     qb.setTables(Combined.VIEW_NAME);
 
                 break;
             }
 
+            case HIGHLIGHTS: {
+                debug("Highlights query: " + uri);
+
+                return getHighlights(db, limit);
+            }
+
             default: {
                 Table table = findTableFor(match);
                 if (table == null) {
                     throw new UnsupportedOperationException("Unknown query URI " + uri);
                 }
                 trace("Update TABLE: " + uri);
                 return table.query(db, uri, match, projection, selection, selectionArgs, sortOrder, groupBy, limit);
             }
--- a/mobile/android/base/java/org/mozilla/gecko/db/LocalBrowserDB.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/LocalBrowserDB.java
@@ -30,16 +30,17 @@ import org.mozilla.gecko.R;
 import org.mozilla.gecko.db.BrowserContract.Bookmarks;
 import org.mozilla.gecko.db.BrowserContract.Combined;
 import org.mozilla.gecko.db.BrowserContract.ExpirePriority;
 import org.mozilla.gecko.db.BrowserContract.Favicons;
 import org.mozilla.gecko.db.BrowserContract.History;
 import org.mozilla.gecko.db.BrowserContract.SyncColumns;
 import org.mozilla.gecko.db.BrowserContract.Thumbnails;
 import org.mozilla.gecko.db.BrowserContract.TopSites;
+import org.mozilla.gecko.db.BrowserContract.Highlights;
 import org.mozilla.gecko.distribution.Distribution;
 import org.mozilla.gecko.icons.decoders.FaviconDecoder;
 import org.mozilla.gecko.icons.decoders.LoadFaviconResult;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.restrictions.Restrictions;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.util.GeckoJarReader;
 import org.mozilla.gecko.util.StringUtils;
@@ -110,16 +111,17 @@ public class LocalBrowserDB implements B
     private final Uri mParentsUriWithProfile;
     private final Uri mHistoryUriWithProfile;
     private final Uri mHistoryExpireUriWithProfile;
     private final Uri mCombinedUriWithProfile;
     private final Uri mUpdateHistoryUriWithProfile;
     private final Uri mFaviconsUriWithProfile;
     private final Uri mThumbnailsUriWithProfile;
     private final Uri mTopSitesUriWithProfile;
+    private final Uri mHighlightsUriWithProfile;
     private final Uri mSearchHistoryUri;
 
     private LocalSearches searches;
     private LocalTabsAccessor tabsAccessor;
     private LocalURLMetadata urlMetadata;
     private LocalUrlAnnotations urlAnnotations;
 
     private static final String[] DEFAULT_BOOKMARK_COLUMNS =
@@ -136,16 +138,17 @@ public class LocalBrowserDB implements B
 
         mBookmarksUriWithProfile = DBUtils.appendProfile(profile, Bookmarks.CONTENT_URI);
         mParentsUriWithProfile = DBUtils.appendProfile(profile, Bookmarks.PARENTS_CONTENT_URI);
         mHistoryUriWithProfile = DBUtils.appendProfile(profile, History.CONTENT_URI);
         mHistoryExpireUriWithProfile = DBUtils.appendProfile(profile, History.CONTENT_OLD_URI);
         mCombinedUriWithProfile = DBUtils.appendProfile(profile, Combined.CONTENT_URI);
         mFaviconsUriWithProfile = DBUtils.appendProfile(profile, Favicons.CONTENT_URI);
         mTopSitesUriWithProfile = DBUtils.appendProfile(profile, TopSites.CONTENT_URI);
+        mHighlightsUriWithProfile = DBUtils.appendProfile(profile, Highlights.CONTENT_URI);
         mThumbnailsUriWithProfile = DBUtils.appendProfile(profile, Thumbnails.CONTENT_URI);
 
         mSearchHistoryUri = BrowserContract.SearchHistory.CONTENT_URI;
 
         mUpdateHistoryUriWithProfile =
                 mHistoryUriWithProfile.buildUpon()
                                       .appendQueryParameter(BrowserContract.PARAM_INCREMENT_VISITS, "true")
                                       .appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true")
@@ -1825,11 +1828,19 @@ public class LocalBrowserDB implements B
         rb.add(-1);
         rb.add(-1);
         rb.add(-1);
         rb.add("");
         rb.add("");
         rb.add(TopSites.TYPE_BLANK);
 
         return new MergeCursor(new Cursor[] {topSitesCursor, blanksCursor});
+    }
 
+    @Override
+    public CursorLoader getHighlights(Context context, int limit) {
+        final Uri uri = mHighlightsUriWithProfile.buildUpon()
+                .appendQueryParameter(BrowserContract.PARAM_LIMIT, String.valueOf(limit))
+                .build();
+
+        return new CursorLoader(context, uri, null, null, null, null);
     }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/db/StubBrowserDB.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/StubBrowserDB.java
@@ -356,16 +356,21 @@ public class StubBrowserDB implements Br
     public String getSuggestedImageUrlForUrl(String url) {
         return null;
     }
 
     public int getSuggestedBackgroundColorForUrl(String url) {
         return 0;
     }
 
+    @Override
+    public CursorLoader getHighlights(Context context, int limit) {
+        return null;
+    }
+
     public Cursor getTopSites(ContentResolver cr, int suggestedRangeLimit, int limit) {
         return null;
     }
 
     public CursorLoader getActivityStreamTopSites(Context context, int limit) {
         return null;
     }
 
--- a/mobile/android/base/java/org/mozilla/gecko/db/URLMetadataTable.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/URLMetadataTable.java
@@ -12,17 +12,17 @@ import org.mozilla.gecko.db.BrowserContr
 import android.database.sqlite.SQLiteDatabase;
 import android.net.Uri;
 
 // Holds metadata info about urls. Supports some helper functions for getting back a HashMap of key value data.
 public class URLMetadataTable extends BaseTable {
     private static final String LOGTAG = "GeckoURLMetadataTable";
 
     private static final String TABLE = "metadata"; // Name of the table in the db
-    private static final int TABLE_ID_NUMBER = 1200;
+    private static final int TABLE_ID_NUMBER = BrowserProvider.METADATA;
 
     // Uri for querying this table
     public static final Uri CONTENT_URI = Uri.withAppendedPath(BrowserContract.AUTHORITY_URI, "metadata");
 
     // Columns in the table
     public static final String ID_COLUMN = "id";
     public static final String URL_COLUMN = "url";
     public static final String TILE_IMAGE_URL_COLUMN = "tileImage";