Bug 1286203 - Introduce proxy for partner bookmarks and load icons. r=ahunt,grisha
This patch does multiple things:
* Introduce a proxy content provider for the partner bookmarks provider: This allows
us to hide the id transformation in the proxy and will later be used to filter
"deleted" bookmarks from the actual content provider (
bug 1286794).
* Modifies LoadFaviconTask to support loading icons from a content provider.
* Introduces a new flag to not download icons from guessed default favicon URLs.
MozReview-Commit-ID: C59ahPcZosn
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -274,16 +274,20 @@
android:theme="@style/Gecko.Preferences"
android:configChanges="orientation|screenSize|locale|layoutDirection"
android:excludeFromRecents="true"/>
<provider android:name="org.mozilla.gecko.db.BrowserProvider"
android:authorities="@ANDROID_PACKAGE_NAME@.db.browser"
android:exported="false"/>
+ <provider android:name="org.mozilla.gecko.distribution.PartnerBookmarksProviderProxy"
+ android:authorities="@ANDROID_PACKAGE_NAME@.partnerbookmarks"
+ android:exported="false"/>
+
<!-- Share overlay activity
Setting launchMode="singleTop" ensures onNewIntent is called when the Activity is
reused. Ideally we create a new instance but Android L breaks this (bug 1137928). -->
<activity android:name="org.mozilla.gecko.overlays.ui.ShareDialog"
android:label="@string/overlay_share_label"
android:theme="@style/OverlayActivity"
android:configChanges="keyboard|keyboardHidden|mcc|mnc|locale|layoutDirection"
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/distribution/PartnerBookmarksProviderClient.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.distribution;
-
-import android.content.ContentResolver;
-import android.database.Cursor;
-import android.net.Uri;
-
-import org.mozilla.gecko.db.BrowserContract;
-
-/**
- * Client for reading Android's PartnerBookmarksProvider.
- *
- * Note: This client is only invoked for distributions. Without a distribution the content provider
- * will not be read and no bookmarks will be added to the UI.
- */
-public class PartnerBookmarksProviderClient {
- /**
- * The contract between the partner bookmarks provider and applications. Contains the definition
- * for the supported URIs and columns.
- */
- private static class PartnerContract {
- public static final Uri CONTENT_URI = Uri.parse("content://com.android.partnerbookmarks/bookmarks");
-
- public static final int TYPE_BOOKMARK = 1;
- public static final int TYPE_FOLDER = 2;
-
- public static final int PARENT_ROOT_ID = 0;
-
- public static final String ID = "_id";
- public static final String TYPE = "type";
- public static final String URL = "url";
- public static final String TITLE = "title";
- public static final String FAVICON = "favicon";
- public static final String TOUCHICON = "touchicon";
- public static final String PARENT = "parent";
- }
-
- public static Cursor getBookmarksInFolder(ContentResolver contentResolver, int folderId) {
- // Use root folder id or transform negative id into actual (positive) folder id.
- final long actualFolderId = folderId == BrowserContract.Bookmarks.FIXED_ROOT_ID
- ? PartnerContract.PARENT_ROOT_ID
- : BrowserContract.Bookmarks.FAKE_PARTNER_BOOKMARKS_START - folderId;
-
- return contentResolver.query(
- PartnerContract.CONTENT_URI,
- new String[] {
- // Transform ids into negative values starting with FAKE_PARTNER_BOOKMARKS_START.
- "(" + BrowserContract.Bookmarks.FAKE_PARTNER_BOOKMARKS_START + " - " + PartnerContract.ID + ") as " + BrowserContract.Bookmarks._ID,
- PartnerContract.TITLE + " as " + BrowserContract.Bookmarks.TITLE,
- PartnerContract.URL + " as " + BrowserContract.Bookmarks.URL,
- // Transform parent ids to negative ids as well
- "(" + BrowserContract.Bookmarks.FAKE_PARTNER_BOOKMARKS_START + " - " + PartnerContract.PARENT + ") as " + BrowserContract.Bookmarks.PARENT,
- // Convert types (we use 0-1 and the partner provider 1-2)
- "(2 - " + PartnerContract.TYPE + ") as " + BrowserContract.Bookmarks.TYPE,
- // Use the ID of the entry as GUID
- PartnerContract.ID + " as " + BrowserContract.Bookmarks.GUID
- },
- PartnerContract.PARENT + " = ?"
- // Only select entries with valid type
- + " AND (" + BrowserContract.Bookmarks.TYPE + " = 1 OR " + BrowserContract.Bookmarks.TYPE + " = 2)"
- // Only select entries with non empty title
- + " AND " + BrowserContract.Bookmarks.TITLE + " <> ''",
- new String[] { String.valueOf(actualFolderId) },
- // Same order we use in our content provider (without position)
- BrowserContract.Bookmarks.TYPE + " ASC, " + BrowserContract.Bookmarks._ID + " ASC");
- }
-}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/distribution/PartnerBookmarksProviderProxy.java
@@ -0,0 +1,192 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.distribution;
+
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+
+import org.mozilla.gecko.db.BrowserContract;
+
+/**
+ * A proxy for the partner bookmarks provider. Bookmark and folder ids of the partner bookmarks providers
+ * will be transformed so that they do not overlap with the ids from the local database.
+ *
+ * Bookmarks in folder:
+ * content://{PACKAGE_ID}.partnerbookmarks/bookmarks/{folderId}
+ * Icon of bookmark:
+ * content://{PACKAGE_ID}.partnerbookmarks/icons/{bookmarkId}
+ */
+public class PartnerBookmarksProviderProxy extends ContentProvider {
+ /**
+ * The contract between the partner bookmarks provider and applications. Contains the definition
+ * for the supported URIs and columns.
+ */
+ public static class PartnerContract {
+ public static final Uri CONTENT_URI = Uri.parse("content://com.android.partnerbookmarks/bookmarks");
+
+ public static final int TYPE_BOOKMARK = 1;
+ public static final int TYPE_FOLDER = 2;
+
+ public static final int PARENT_ROOT_ID = 0;
+
+ public static final String ID = "_id";
+ public static final String TYPE = "type";
+ public static final String URL = "url";
+ public static final String TITLE = "title";
+ public static final String FAVICON = "favicon";
+ public static final String TOUCHICON = "touchicon";
+ public static final String PARENT = "parent";
+ }
+
+ private static final String AUTHORITY_PREFIX = ".partnerbookmarks";
+
+ private static final int URI_MATCH_BOOKMARKS = 1000;
+ private static final int URI_MATCH_ICON = 1001;
+
+ private static String getAuthority(Context context) {
+ return context.getPackageName() + AUTHORITY_PREFIX;
+ }
+
+ public static Uri getUriForBookmarks(Context context, long folderId) {
+ return new Uri.Builder()
+ .scheme("content")
+ .authority(getAuthority(context))
+ .appendPath("bookmarks")
+ .appendPath(String.valueOf(folderId))
+ .build();
+ }
+
+ public static Uri getUriForIcon(Context context, long bookmarkId) {
+ return new Uri.Builder()
+ .scheme("content")
+ .authority(getAuthority(context))
+ .appendPath("icons")
+ .appendPath(String.valueOf(bookmarkId))
+ .build();
+ }
+
+ private final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+
+ @Override
+ public boolean onCreate() {
+ String authority = getAuthority(assertAndGetContext());
+
+ uriMatcher.addURI(authority, "bookmarks/*", URI_MATCH_BOOKMARKS);
+ uriMatcher.addURI(authority, "icons/*", URI_MATCH_ICON);
+
+ return true;
+ }
+
+ @Override
+ public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
+ final int match = uriMatcher.match(uri);
+
+ final ContentResolver contentResolver = assertAndGetContext().getContentResolver();
+
+ switch (match) {
+ case URI_MATCH_BOOKMARKS:
+ final long bookmarkId = ContentUris.parseId(uri);
+ if (bookmarkId == -1) {
+ throw new IllegalArgumentException("Bookmark id is not a number");
+ }
+ return getBookmarksInFolder(contentResolver, bookmarkId);
+
+ case URI_MATCH_ICON:
+ return getIcon(contentResolver, ContentUris.parseId(uri));
+
+ default:
+ throw new UnsupportedOperationException("Unknown URI " + uri.toString());
+ }
+ };
+
+ private Cursor getBookmarksInFolder(ContentResolver contentResolver, long folderId) {
+ // Use root folder id or transform negative id into actual (positive) folder id.
+ final long actualFolderId = folderId == BrowserContract.Bookmarks.FIXED_ROOT_ID
+ ? PartnerContract.PARENT_ROOT_ID
+ : BrowserContract.Bookmarks.FAKE_PARTNER_BOOKMARKS_START - folderId;
+
+ return contentResolver.query(
+ PartnerContract.CONTENT_URI,
+ new String[] {
+ // Transform ids into negative values starting with FAKE_PARTNER_BOOKMARKS_START.
+ "(" + BrowserContract.Bookmarks.FAKE_PARTNER_BOOKMARKS_START + " - " + PartnerContract.ID + ") as " + BrowserContract.Bookmarks._ID,
+ "(" + BrowserContract.Bookmarks.FAKE_PARTNER_BOOKMARKS_START + " - " + PartnerContract.ID + ") as " + BrowserContract.Combined.BOOKMARK_ID,
+ PartnerContract.TITLE + " as " + BrowserContract.Bookmarks.TITLE,
+ PartnerContract.URL + " as " + BrowserContract.Bookmarks.URL,
+ // Transform parent ids to negative ids as well
+ "(" + BrowserContract.Bookmarks.FAKE_PARTNER_BOOKMARKS_START + " - " + PartnerContract.PARENT + ") as " + BrowserContract.Bookmarks.PARENT,
+ // Convert types (we use 0-1 and the partner provider 1-2)
+ "(2 - " + PartnerContract.TYPE + ") as " + BrowserContract.Bookmarks.TYPE,
+ // Use the ID of the entry as GUID
+ PartnerContract.ID + " as " + BrowserContract.Bookmarks.GUID
+ },
+ PartnerContract.PARENT + " = ?"
+ // Only select entries with valid type
+ + " AND (" + BrowserContract.Bookmarks.TYPE + " = ? OR " + BrowserContract.Bookmarks.TYPE + " = ?)"
+ // Only select entries with non empty title
+ + " AND " + BrowserContract.Bookmarks.TITLE + " <> ''",
+ new String[] {
+ String.valueOf(actualFolderId),
+ String.valueOf(PartnerContract.TYPE_BOOKMARK),
+ String.valueOf(PartnerContract.TYPE_FOLDER)
+ },
+ // Same order we use in our content provider (without position)
+ BrowserContract.Bookmarks.TYPE + " ASC, " + BrowserContract.Bookmarks._ID + " ASC");
+ }
+
+ private Cursor getIcon(ContentResolver contentResolver, long bookmarkId) {
+ final long actualId = BrowserContract.Bookmarks.FAKE_PARTNER_BOOKMARKS_START - bookmarkId;
+
+ return contentResolver.query(
+ PartnerContract.CONTENT_URI,
+ new String[] {
+ PartnerContract.TOUCHICON,
+ PartnerContract.FAVICON
+ },
+ PartnerContract.ID + " = ?",
+ new String[] {
+ String.valueOf(actualId)
+ },
+ null);
+ }
+
+ private Context assertAndGetContext() {
+ final Context context = super.getContext();
+
+ if (context == null) {
+ throw new AssertionError("Context is null");
+ }
+
+ return context;
+ }
+
+ @Override
+ public String getType(@NonNull Uri uri) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Uri insert(@NonNull Uri uri, ContentValues values) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException();
+ }
+}
--- a/mobile/android/base/java/org/mozilla/gecko/favicons/LoadFaviconTask.java
+++ b/mobile/android/base/java/org/mozilla/gecko/favicons/LoadFaviconTask.java
@@ -1,22 +1,26 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.favicons;
import android.content.ContentResolver;
import android.content.Context;
+import android.database.Cursor;
import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.db.BrowserDB;
+import org.mozilla.gecko.distribution.PartnerBookmarksProviderProxy;
import org.mozilla.gecko.favicons.decoders.FaviconDecoder;
import org.mozilla.gecko.favicons.decoders.LoadFaviconResult;
import org.mozilla.gecko.util.GeckoJarReader;
import org.mozilla.gecko.util.IOUtils;
import org.mozilla.gecko.util.ProxySelector;
import org.mozilla.gecko.util.ThreadUtils;
import java.io.IOException;
@@ -52,16 +56,22 @@ public class LoadFaviconTask {
/**
* Bypass all caches - this is used to directly retrieve the requested icon. Without this flag,
* favicons will first be pushed into the memory cache (and possibly permanent cache if using FLAG_PERSIST),
* where they will be downscaled to the maximum cache size, before being retrieved from the cache (resulting
* in a possibly smaller icon size).
*/
public static final int FLAG_BYPASS_CACHE_WHEN_DOWNLOADING_ICONS = 2;
+ /**
+ * If downloading from the favicon URL failed then do NOT try to guess the default URL and
+ * download from the default URL.
+ */
+ public static final int FLAG_NO_DOWNLOAD_FROM_GUESSED_DEFAULT_URL = 4;
+
private static final int MAX_REDIRECTS_TO_FOLLOW = 5;
// The default size of the buffer to use for downloading Favicons in the event no size is given
// by the server.
public static final int DEFAULT_FAVICON_BUFFER_SIZE = 25000;
private static final AtomicInteger nextFaviconLoadId = new AtomicInteger(0);
private final Context context;
private final int id;
@@ -193,16 +203,83 @@ public class LoadFaviconTask {
// Just about anything could happen here.
Log.w(LOGTAG, "Error fetching favicon from JAR.", e);
return null;
}
}
return null;
}
+ /**
+ * Fetch icon from a content provider following the partner bookmarks provider contract.
+ */
+ private Bitmap fetchContentProviderFavicon(String uri, int targetWidthAndHeight) {
+ if (TextUtils.isEmpty(uri)) {
+ return null;
+ }
+
+ if (!uri.startsWith("content://")) {
+ return null;
+ }
+
+ Cursor cursor = context.getContentResolver().query(
+ Uri.parse(uri),
+ new String[] {
+ PartnerBookmarksProviderProxy.PartnerContract.TOUCHICON,
+ PartnerBookmarksProviderProxy.PartnerContract.FAVICON,
+ },
+ null,
+ null,
+ null
+ );
+
+ if (cursor == null) {
+ return null;
+ }
+
+ try {
+ if (!cursor.moveToFirst()) {
+ return null;
+ }
+
+ Bitmap icon = decodeFromCursor(cursor, PartnerBookmarksProviderProxy.PartnerContract.TOUCHICON, targetWidthAndHeight);
+ if (icon != null) {
+ return icon;
+ }
+
+ icon = decodeFromCursor(cursor, PartnerBookmarksProviderProxy.PartnerContract.FAVICON, targetWidthAndHeight);
+ if (icon != null) {
+ return icon;
+ }
+ } finally {
+ cursor.close();
+ }
+
+ return null;
+ }
+
+ private Bitmap decodeFromCursor(Cursor cursor, String column, int targetWidthAndHeight) {
+ final int index = cursor.getColumnIndex(column);
+ if (index == -1) {
+ return null;
+ }
+
+ if (cursor.isNull(index)) {
+ return null;
+ }
+
+ final byte[] data = cursor.getBlob(index);
+ LoadFaviconResult result = FaviconDecoder.decodeFavicon(data, 0, data.length);
+ if (result == null) {
+ return null;
+ }
+
+ return result.getBestBitmap(targetWidthAndHeight);
+ }
+
// Runs in background thread.
// Does not attempt to fetch from JARs.
private LoadFaviconResult downloadFavicon(URI targetFaviconURI) {
if (targetFaviconURI == null) {
return null;
}
// Only get favicons for HTTP/HTTPS.
@@ -418,16 +495,24 @@ public class LoadFaviconTask {
// Let's see if it's in a JAR.
image = fetchJARFavicon(faviconURL);
if (imageIsValid(image)) {
// We don't want to put this into the DB.
Favicons.putFaviconInMemCache(faviconURL, image);
return image;
}
+ // Download from a content provider
+ image = fetchContentProviderFavicon(faviconURL);
+ if (imageIsValid(image)) {
+ // We don't want to put this into the DB.
+ Favicons.putFaviconInMemCache(faviconURL, image);
+ return image;
+ }
+
try {
loadedBitmaps = downloadFavicon(new URI(faviconURL));
} catch (URISyntaxException e) {
Log.e(LOGTAG, "The provided favicon URL is not valid");
return null;
} catch (Exception e) {
Log.e(LOGTAG, "Couldn't download favicon.", e);
}
@@ -435,40 +520,22 @@ public class LoadFaviconTask {
if (loadedBitmaps != null) {
if ((flags & FLAG_BYPASS_CACHE_WHEN_DOWNLOADING_ICONS) == 0) {
// Fetching bytes to store can fail. saveFaviconToDb will
// do the right thing, but we still choose to cache the
// downloaded icon in memory.
saveFaviconToDb(db, loadedBitmaps.getBytesForDatabaseStorage());
return pushToCacheAndGetResult(loadedBitmaps);
} else {
- final Map<Integer, Bitmap> iconMap = new HashMap<>();
- final List<Integer> sizes = new ArrayList<>();
-
- while (loadedBitmaps.getBitmaps().hasNext()) {
- final Bitmap b = loadedBitmaps.getBitmaps().next();
+ return loadedBitmaps.getBestBitmap(targetWidthAndHeight);
+ }
+ }
- // It's possible to receive null, most likely due to OOM or a zero-sized image,
- // from BitmapUtils.decodeByteArray(byte[], int, int, BitmapFactory.Options)
- if (b != null) {
- iconMap.put(b.getWidth(), b);
- sizes.add(b.getWidth());
- }
- }
-
- int bestSize = Favicons.selectBestSizeFromList(sizes, targetWidthAndHeight);
-
- if (bestSize == -1) {
- // No icons found: this could occur if we weren't able to process any of the
- // supplied icons.
- return null;
- }
-
- return iconMap.get(bestSize);
- }
+ if ((FLAG_NO_DOWNLOAD_FROM_GUESSED_DEFAULT_URL & flags) == FLAG_NO_DOWNLOAD_FROM_GUESSED_DEFAULT_URL) {
+ return null;
}
if (isUsingDefaultURL) {
Favicons.putFaviconInFailedCache(faviconURL);
return null;
}
if (isCancelled()) {
--- a/mobile/android/base/java/org/mozilla/gecko/favicons/decoders/LoadFaviconResult.java
+++ b/mobile/android/base/java/org/mozilla/gecko/favicons/decoders/LoadFaviconResult.java
@@ -1,19 +1,26 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.favicons.decoders;
import android.graphics.Bitmap;
import android.util.Log;
+import android.util.SparseArray;
+
+import org.mozilla.gecko.favicons.Favicons;
import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
/**
* Class representing the result of loading a favicon.
* This operation will produce either a collection of favicons, a single favicon, or no favicon.
* It is necessary to model single favicons differently to a collection of one favicon (An entity
* that may not exist with this scheme) since the in-database representation of these things differ.
* (In particular, collections of favicons are stored in encoded ICO format, whereas single icons are
* stored as decoded bitmap blobs.)
@@ -68,9 +75,34 @@ public class LoadFaviconResult {
} catch (OutOfMemoryError e) {
Log.w(LOGTAG, "Out of memory re-compressing favicon.");
}
Log.w(LOGTAG, "Favicon re-compression failed.");
return null;
}
+ public Bitmap getBestBitmap(int targetWidthAndHeight) {
+ final SparseArray<Bitmap> iconMap = new SparseArray<>();
+ final List<Integer> sizes = new ArrayList<>();
+
+ while (bitmapsDecoded.hasNext()) {
+ final Bitmap b = bitmapsDecoded.next();
+
+ // It's possible to receive null, most likely due to OOM or a zero-sized image,
+ // from BitmapUtils.decodeByteArray(byte[], int, int, BitmapFactory.Options)
+ if (b != null) {
+ iconMap.put(b.getWidth(), b);
+ sizes.add(b.getWidth());
+ }
+ }
+
+ int bestSize = Favicons.selectBestSizeFromList(sizes, targetWidthAndHeight);
+
+ if (bestSize == -1) {
+ // No icons found: this could occur if we weren't able to process any of the
+ // supplied icons.
+ return null;
+ }
+
+ return iconMap.get(bestSize);
+ }
}
--- a/mobile/android/base/java/org/mozilla/gecko/home/BookmarksPanel.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/BookmarksPanel.java
@@ -10,17 +10,17 @@ import java.util.LinkedList;
import java.util.List;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.GeckoSharedPrefs;
import org.mozilla.gecko.R;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.db.BrowserContract.Bookmarks;
import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.distribution.PartnerBookmarksProviderClient;
+import org.mozilla.gecko.distribution.PartnerBookmarksProviderProxy;
import org.mozilla.gecko.home.BookmarksListAdapter.FolderInfo;
import org.mozilla.gecko.home.BookmarksListAdapter.OnRefreshFolderListener;
import org.mozilla.gecko.home.BookmarksListAdapter.RefreshType;
import org.mozilla.gecko.home.HomeContextMenuInfo.RemoveItemType;
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
import org.mozilla.gecko.preferences.GeckoPreferences;
import android.app.Activity;
@@ -230,17 +230,17 @@ public class BookmarksPanel extends Home
final ContentResolver contentResolver = getContext().getContentResolver();
Cursor partnerCursor = null;
Cursor userCursor = null;
if (GeckoSharedPrefs.forProfile(getContext()).getBoolean(GeckoPreferences.PREFS_READ_PARTNER_BOOKMARKS_PROVIDER, false)
&& (isRootFolder || mFolderInfo.id <= Bookmarks.FAKE_PARTNER_BOOKMARKS_START)) {
- partnerCursor = PartnerBookmarksProviderClient.getBookmarksInFolder(contentResolver, mFolderInfo.id);
+ partnerCursor = contentResolver.query(PartnerBookmarksProviderProxy.getUriForBookmarks(getContext(), mFolderInfo.id), null, null, null, null, null);
}
if (isRootFolder || mFolderInfo.id > Bookmarks.FAKE_PARTNER_BOOKMARKS_START) {
userCursor = mDB.getBookmarksInFolder(contentResolver, mFolderInfo.id);
}
if (partnerCursor == null && userCursor == null) {
--- a/mobile/android/base/java/org/mozilla/gecko/home/TwoLinePageRow.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/TwoLinePageRow.java
@@ -4,16 +4,19 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.home;
import java.lang.ref.WeakReference;
import org.mozilla.gecko.AboutPages;
import org.mozilla.gecko.R;
+import org.mozilla.gecko.db.BrowserContract;
+import org.mozilla.gecko.distribution.PartnerBookmarksProviderProxy;
+import org.mozilla.gecko.favicons.LoadFaviconTask;
import org.mozilla.gecko.reader.SavedReaderViewHelper;
import org.mozilla.gecko.reader.ReaderModeUtils;
import org.mozilla.gecko.Tab;
import org.mozilla.gecko.Tabs;
import org.mozilla.gecko.db.BrowserContract.Combined;
import org.mozilla.gecko.db.BrowserContract.URLColumns;
import org.mozilla.gecko.favicons.Favicons;
import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
@@ -265,18 +268,18 @@ public class TwoLinePageRow extends Line
*/
public void update(String title, String url) {
update(title, url, 0, false);
}
protected void update(String title, String url, long bookmarkId, boolean hasReaderCacheItem) {
if (mShowIcons) {
// The bookmark id will be 0 (null in database) when the url
- // is not a bookmark.
- final boolean isBookmark = bookmarkId != 0;
+ // is not a bookmark and negative for 'fake' bookmarks.
+ final boolean isBookmark = bookmarkId > 0;
updateStatusIcon(isBookmark, hasReaderCacheItem);
} else {
updateStatusIcon(false, false);
}
// Use the URL instead of an empty title for consistency with the normal URL
// bar view - this is the equivalent of getDisplayTitle() in Tab.java
@@ -288,19 +291,33 @@ public class TwoLinePageRow extends Line
}
// Blank the Favicon, so we don't show the wrong Favicon if we scroll and miss DB.
mFavicon.clearImage();
Favicons.cancelFaviconLoad(mLoadFaviconJobId);
// Displayed RecentTabsPanel URLs may refer to pages opened in reader mode, so we
// remove the about:reader prefix to ensure the Favicon loads properly.
- final String pageURL = AboutPages.isAboutReader(url) ?
- ReaderModeUtils.getUrlFromAboutReader(url) : url;
- mLoadFaviconJobId = Favicons.getSizedFaviconForPageFromLocal(getContext(), pageURL, mFaviconListener);
+ final String pageURL = AboutPages.isAboutReader(url) ? ReaderModeUtils.getUrlFromAboutReader(url) : url;
+
+ if (bookmarkId < BrowserContract.Bookmarks.FAKE_PARTNER_BOOKMARKS_START) {
+ mLoadFaviconJobId = Favicons.getSizedFavicon(
+ getContext(),
+ pageURL,
+ PartnerBookmarksProviderProxy.getUriForIcon(getContext(), bookmarkId).toString(),
+ Favicons.LoadType.PRIVILEGED,
+ Favicons.defaultFaviconSize,
+ // We want to load the favicon from the content provider but we do not want the
+ // favicon loader to fallback to loading a favicon from the web using a guessed
+ // default URL.
+ LoadFaviconTask.FLAG_NO_DOWNLOAD_FROM_GUESSED_DEFAULT_URL,
+ mFaviconListener);
+ } else {
+ mLoadFaviconJobId = Favicons.getSizedFaviconForPageFromLocal(getContext(), pageURL, mFaviconListener);
+ }
updateDisplayedUrl(url, hasReaderCacheItem);
}
/**
* Update the data displayed by this row.
* <p>
* This method must be invoked on the UI thread.
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -263,17 +263,17 @@ gbjar.sources += ['java/org/mozilla/geck
'delegates/BrowserAppDelegate.java',
'delegates/BrowserAppDelegateWithReference.java',
'delegates/OfflineTabStatusDelegate.java',
'delegates/ScreenshotDelegate.java',
'delegates/TabsTrayVisibilityAwareDelegate.java',
'DevToolsAuthHelper.java',
'distribution/Distribution.java',
'distribution/DistributionStoreCallback.java',
- 'distribution/PartnerBookmarksProviderClient.java',
+ 'distribution/PartnerBookmarksProviderProxy.java',
'distribution/PartnerBrowserCustomizationsClient.java',
'distribution/ReferrerDescriptor.java',
'distribution/ReferrerReceiver.java',
'dlc/BaseAction.java',
'dlc/catalog/DownloadContent.java',
'dlc/catalog/DownloadContentBootstrap.java',
'dlc/catalog/DownloadContentBuilder.java',
'dlc/catalog/DownloadContentCatalog.java',