Commit 1985193e authored by Yue Zhang's avatar Yue Zhang Committed by Commit Bot

Add favicons to tabgrid thumbnails

Add a layer of callback in MultiThumbnailCardProvider to fetch
favicon and draw with shadowed circle background in thumbnail.

Bug: 946286
Change-Id: Id4fce1c635d1f639778476730ba7cea131496376
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1574639
Commit-Queue: Yue Zhang <yuezhanggg@google.com>
Reviewed-by: default avatarYusuf Ozuysal <yusufo@chromium.org>
Cr-Commit-Position: refs/heads/master@{#652480}
parent b1603f08
...@@ -13,12 +13,14 @@ import android.graphics.PorterDuff; ...@@ -13,12 +13,14 @@ import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode; import android.graphics.PorterDuffXfermode;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.RectF; import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import org.chromium.base.ApiCompatibilityUtils; import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.Callback; import org.chromium.base.Callback;
import org.chromium.base.task.PostTask; import org.chromium.base.task.PostTask;
import org.chromium.chrome.R; import org.chromium.chrome.R;
import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager; import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tabmodel.TabModelSelector; import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.content_public.browser.UiThreadTaskTraits; import org.chromium.content_public.browser.UiThreadTaskTraits;
...@@ -35,13 +37,17 @@ public class MultiThumbnailCardProvider implements TabListMediator.ThumbnailProv ...@@ -35,13 +37,17 @@ public class MultiThumbnailCardProvider implements TabListMediator.ThumbnailProv
private final TabContentManager mTabContentManager; private final TabContentManager mTabContentManager;
private final TabModelSelector mTabModelSelector; private final TabModelSelector mTabModelSelector;
private final float mPadding;
private final float mRadius; private final float mRadius;
private final float mFaviconCirclePadding;
private final int mSize;
private final Paint mEmptyThumbnailPaint; private final Paint mEmptyThumbnailPaint;
private final Paint mThumbnailFramePaint; private final Paint mThumbnailFramePaint;
private final Paint mTextPaint; private final Paint mTextPaint;
private final int mSize; private final Paint mFaviconBackgroundPaint;
private final List<RectF> mRects = new ArrayList<>(4); private final List<Rect> mFaviconRects = new ArrayList<>(4);
private final List<RectF> mThumbnailRects = new ArrayList<>(4);
private final List<RectF> mFaviconBackgroundRects = new ArrayList<>(4);
private final TabListFaviconProvider mTabListFaviconProvider;
private class MultiThumbnailFetcher { private class MultiThumbnailFetcher {
private final Tab mInitialTab; private final Tab mInitialTab;
...@@ -98,19 +104,28 @@ public class MultiThumbnailCardProvider implements TabListMediator.ThumbnailProv ...@@ -98,19 +104,28 @@ public class MultiThumbnailCardProvider implements TabListMediator.ThumbnailProv
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
if (mTabs.get(i) != null) { if (mTabs.get(i) != null) {
final int index = i; final int index = i;
mTabContentManager.getTabThumbnailWithCallback(mTabs.get(i), result -> { final String url = mTabs.get(i).getUrl();
drawBitmapOnCanvasWithFrame(result, index, mEmptyThumbnailPaint); final boolean isIncognito = mTabs.get(i).isIncognito();
if (mThumbnailsToFetch.decrementAndGet() == 0) { mTabContentManager.getTabThumbnailWithCallback(mTabs.get(i), thumbnail -> {
PostTask.postTask(UiThreadTaskTraits.USER_VISIBLE, drawThumbnailBitmapOnCanvasWithFrame(thumbnail, index);
() -> mFinalCallback.onResult(mMultiThumbnailBitmap)); mTabListFaviconProvider.getFaviconForUrlAsync(
} url, isIncognito, (Drawable favicon) -> {
drawFaviconDrawableOnCanvasWithFrame(favicon, index);
if (mThumbnailsToFetch.decrementAndGet() == 0) {
PostTask.postTask(UiThreadTaskTraits.USER_VISIBLE,
()
-> mFinalCallback.onResult(
mMultiThumbnailBitmap));
}
});
}, mForceUpdate && i == 0); }, mForceUpdate && i == 0);
} else { } else {
drawBitmapOnCanvasWithFrame(null, i, mEmptyThumbnailPaint); drawThumbnailBitmapOnCanvasWithFrame(null, i);
if (mText != null && i == 3) { if (mText != null && i == 3) {
// Draw the text exactly centered on the thumbnail rect. // Draw the text exactly centered on the thumbnail rect.
mCanvas.drawText(mText, (mRects.get(i).left + mRects.get(i).right) / 2, mCanvas.drawText(mText,
(mRects.get(i).top + mRects.get(i).bottom) / 2 (mThumbnailRects.get(i).left + mThumbnailRects.get(i).right) / 2,
(mThumbnailRects.get(i).top + mThumbnailRects.get(i).bottom) / 2
- ((mTextPaint.descent() + mTextPaint.ascent()) / 2), - ((mTextPaint.descent() + mTextPaint.ascent()) / 2),
mTextPaint); mTextPaint);
} }
...@@ -118,24 +133,35 @@ public class MultiThumbnailCardProvider implements TabListMediator.ThumbnailProv ...@@ -118,24 +133,35 @@ public class MultiThumbnailCardProvider implements TabListMediator.ThumbnailProv
} }
} }
private void drawBitmapOnCanvasWithFrame( private void drawThumbnailBitmapOnCanvasWithFrame(Bitmap thumbnail, int index) {
Bitmap result, int index, Paint emptyThumbnailPaint) {
// Draw the rounded rect. If Bitmap is not null, this is used for XferMode. // Draw the rounded rect. If Bitmap is not null, this is used for XferMode.
mCanvas.drawRoundRect(mRects.get(index), mRadius, mRadius, emptyThumbnailPaint); mCanvas.drawRoundRect(
mThumbnailRects.get(index), mRadius, mRadius, mEmptyThumbnailPaint);
if (result == null) return; if (thumbnail == null) return;
result = Bitmap.createScaledBitmap(result, (int) mRects.get(index).width(), thumbnail =
(int) mRects.get(index).height(), true); Bitmap.createScaledBitmap(thumbnail, (int) mThumbnailRects.get(index).width(),
(int) mThumbnailRects.get(index).height(), true);
emptyThumbnailPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); mEmptyThumbnailPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
mCanvas.drawBitmap(result, new Rect(0, 0, result.getWidth(), result.getHeight()), mCanvas.drawBitmap(thumbnail,
mRects.get(index), emptyThumbnailPaint); new Rect(0, 0, thumbnail.getWidth(), thumbnail.getHeight()),
result.recycle(); mThumbnailRects.get(index), mEmptyThumbnailPaint);
thumbnail.recycle();
emptyThumbnailPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER)); mEmptyThumbnailPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));
mCanvas.drawRoundRect(mRects.get(index), mRadius, mRadius, mThumbnailFramePaint); mCanvas.drawRoundRect(
mThumbnailRects.get(index), mRadius, mRadius, mThumbnailFramePaint);
}
private void drawFaviconDrawableOnCanvasWithFrame(Drawable favicon, int index) {
RectF rectF = mFaviconBackgroundRects.get(index);
mCanvas.drawCircle((rectF.left + rectF.right) / 2, (rectF.bottom + rectF.top) / 2,
rectF.width() / 2 - mFaviconCirclePadding, mFaviconBackgroundPaint);
favicon.setBounds(mFaviconRects.get(index));
favicon.draw(mCanvas);
} }
private void fetch() { private void fetch() {
...@@ -147,10 +173,12 @@ public class MultiThumbnailCardProvider implements TabListMediator.ThumbnailProv ...@@ -147,10 +173,12 @@ public class MultiThumbnailCardProvider implements TabListMediator.ThumbnailProv
TabModelSelector tabModelSelector) { TabModelSelector tabModelSelector) {
mTabContentManager = tabContentManager; mTabContentManager = tabContentManager;
mTabModelSelector = tabModelSelector; mTabModelSelector = tabModelSelector;
mPadding = context.getResources().getDimension(R.dimen.tab_list_card_padding);
mRadius = context.getResources().getDimension(R.dimen.tab_list_mini_card_radius); mRadius = context.getResources().getDimension(R.dimen.tab_list_mini_card_radius);
mSize = (int) context.getResources().getDimension( mSize = (int) context.getResources().getDimension(
R.dimen.tab_grid_thumbnail_card_default_size); R.dimen.tab_grid_thumbnail_card_default_size);
mFaviconCirclePadding = context.getResources().getDimension(
R.dimen.tab_grid_thumbnail_favicon_background_padding);
mTabListFaviconProvider = new TabListFaviconProvider(context, Profile.getLastUsedProfile());
// Initialize Paints to use. // Initialize Paints to use.
mEmptyThumbnailPaint = new Paint(); mEmptyThumbnailPaint = new Paint();
...@@ -174,15 +202,49 @@ public class MultiThumbnailCardProvider implements TabListMediator.ThumbnailProv ...@@ -174,15 +202,49 @@ public class MultiThumbnailCardProvider implements TabListMediator.ThumbnailProv
mTextPaint.setAntiAlias(true); mTextPaint.setAntiAlias(true);
mTextPaint.setTextAlign(Paint.Align.CENTER); mTextPaint.setTextAlign(Paint.Align.CENTER);
// Initialize Rects. mFaviconBackgroundPaint = new Paint();
mRects.add( mFaviconBackgroundPaint.setAntiAlias(true);
new RectF(mPadding, mPadding, mSize / 2 - mPadding / 2, mSize / 2 - mPadding / 2)); mFaviconBackgroundPaint.setColor(Color.WHITE);
mRects.add(new RectF( mFaviconBackgroundPaint.setStyle(Paint.Style.FILL);
mSize / 2 + mPadding / 2, mPadding, mSize - mPadding, mSize / 2 - mPadding / 2)); mFaviconBackgroundPaint.setShadowLayer(
mRects.add(new RectF( context.getResources().getDimension(
mPadding, mSize / 2 + mPadding / 2, mSize / 2 - mPadding / 2, mSize - mPadding)); R.dimen.tab_grid_thumbnail_favicon_background_radius),
mRects.add(new RectF(mSize / 2 + mPadding / 2, mSize / 2 + mPadding / 2, mSize - mPadding, 0,
mSize - mPadding)); context.getResources().getDimension(
R.dimen.tab_grid_thumbnail_favicon_background_down_shift),
context.getResources().getColor(R.color.modern_grey_800_alpha_38));
// Initialize Rects for thumbnails.
float thumbnailPadding = context.getResources().getDimension(R.dimen.tab_list_card_padding);
float thumbnailFaviconPadding =
context.getResources().getDimension(R.dimen.tab_grid_thumbnail_favicon_padding);
float thumbnailFaviconBackgroundPadding = context.getResources().getDimension(
R.dimen.tab_grid_thumbnail_favicon_frame_padding);
mThumbnailRects.add(new RectF(thumbnailPadding, thumbnailPadding,
mSize / 2 - thumbnailPadding / 2, mSize / 2 - thumbnailPadding / 2));
mThumbnailRects.add(new RectF(mSize / 2 + thumbnailPadding / 2, thumbnailPadding,
mSize - thumbnailPadding, mSize / 2 - thumbnailPadding / 2));
mThumbnailRects.add(new RectF(thumbnailPadding, mSize / 2 + thumbnailPadding / 2,
mSize / 2 - thumbnailPadding / 2, mSize - thumbnailPadding));
mThumbnailRects.add(
new RectF(mSize / 2 + thumbnailPadding / 2, mSize / 2 + thumbnailPadding / 2,
mSize - thumbnailPadding, mSize - thumbnailPadding));
// Initialize Rects for favicons and favicon backgrounds.
for (int i = 0; i < 4; i++) {
RectF thumbnailRect = mThumbnailRects.get(i);
mFaviconBackgroundRects.add(
new RectF(thumbnailRect.left + thumbnailFaviconBackgroundPadding,
thumbnailRect.top + thumbnailFaviconBackgroundPadding,
thumbnailRect.right - thumbnailFaviconBackgroundPadding,
thumbnailRect.bottom - thumbnailFaviconBackgroundPadding));
mFaviconRects.add(new Rect(Math.round(thumbnailRect.left + thumbnailFaviconPadding),
Math.round(thumbnailRect.top + thumbnailFaviconPadding),
Math.round(thumbnailRect.right - thumbnailFaviconPadding),
Math.round(thumbnailRect.bottom - thumbnailFaviconPadding)));
}
} }
@Override @Override
......
...@@ -29,7 +29,12 @@ import org.chromium.chrome.R; ...@@ -29,7 +29,12 @@ import org.chromium.chrome.R;
R.dimen.tab_list_card_padding, R.dimen.tab_list_mini_card_text_size, R.dimen.tab_list_card_padding, R.dimen.tab_list_mini_card_text_size,
R.dimen.tab_list_mini_card_frame_size, R.dimen.tab_list_mini_card_radius, R.dimen.tab_list_mini_card_frame_size, R.dimen.tab_list_mini_card_radius,
R.drawable.tabstrip_favicon_background, R.dimen.swipe_to_dismiss_threshold, R.drawable.tabstrip_favicon_background, R.dimen.swipe_to_dismiss_threshold,
R.dimen.tab_grid_thumbnail_card_default_size, R.dimen.tab_list_selected_inset_kitkat}; R.dimen.tab_grid_thumbnail_card_default_size, R.dimen.tab_list_selected_inset_kitkat,
R.dimen.tab_grid_thumbnail_favicon_padding,
R.dimen.tab_grid_thumbnail_favicon_frame_padding,
R.dimen.tab_grid_thumbnail_favicon_background_radius,
R.dimen.tab_grid_thumbnail_favicon_background_padding,
R.dimen.tab_grid_thumbnail_favicon_background_down_shift};
private SilenceLintErrors() {} private SilenceLintErrors() {}
} }
...@@ -22,8 +22,8 @@ import org.chromium.chrome.browser.util.ViewUtils; ...@@ -22,8 +22,8 @@ import org.chromium.chrome.browser.util.ViewUtils;
* Provider for processed favicons in Tab list. * Provider for processed favicons in Tab list.
*/ */
public class TabListFaviconProvider { public class TabListFaviconProvider {
private static Drawable sGlobeDrawable; private static Drawable sRoundedGlobeDrawable;
private static Drawable sChromeDrawable; private static Drawable sRoundedChromeDrawable;
private final int mFaviconSize; private final int mFaviconSize;
private final Profile mProfile; private final Profile mProfile;
private final FaviconHelper mFaviconHelper; private final FaviconHelper mFaviconHelper;
...@@ -37,7 +37,7 @@ public class TabListFaviconProvider { ...@@ -37,7 +37,7 @@ public class TabListFaviconProvider {
mFaviconSize = context.getResources().getDimensionPixelSize(R.dimen.default_favicon_size); mFaviconSize = context.getResources().getDimensionPixelSize(R.dimen.default_favicon_size);
mProfile = profile; mProfile = profile;
mFaviconHelper = new FaviconHelper(); mFaviconHelper = new FaviconHelper();
if (sGlobeDrawable == null) { if (sRoundedGlobeDrawable == null) {
Drawable globeDrawable = Drawable globeDrawable =
AppCompatResources.getDrawable(context, R.drawable.ic_globe_24dp); AppCompatResources.getDrawable(context, R.drawable.ic_globe_24dp);
Bitmap globeBitmap = Bitmap globeBitmap =
...@@ -45,12 +45,12 @@ public class TabListFaviconProvider { ...@@ -45,12 +45,12 @@ public class TabListFaviconProvider {
Canvas canvas = new Canvas(globeBitmap); Canvas canvas = new Canvas(globeBitmap);
globeDrawable.setBounds(0, 0, mFaviconSize, mFaviconSize); globeDrawable.setBounds(0, 0, mFaviconSize, mFaviconSize);
globeDrawable.draw(canvas); globeDrawable.draw(canvas);
sGlobeDrawable = processBitmap(globeBitmap); sRoundedGlobeDrawable = processBitmap(globeBitmap);
} }
if (sChromeDrawable == null) { if (sRoundedChromeDrawable == null) {
Bitmap chromeBitmap = Bitmap chromeBitmap =
BitmapFactory.decodeResource(context.getResources(), R.drawable.chromelogo16); BitmapFactory.decodeResource(context.getResources(), R.drawable.chromelogo16);
sChromeDrawable = processBitmap(chromeBitmap); sRoundedChromeDrawable = processBitmap(chromeBitmap);
} }
} }
...@@ -64,7 +64,7 @@ public class TabListFaviconProvider { ...@@ -64,7 +64,7 @@ public class TabListFaviconProvider {
* @return The scaled rounded Globe Drawable as default favicon. * @return The scaled rounded Globe Drawable as default favicon.
*/ */
public Drawable getDefaultFaviconDrawable() { public Drawable getDefaultFaviconDrawable() {
return sGlobeDrawable; return sRoundedGlobeDrawable;
} }
/** /**
...@@ -76,13 +76,15 @@ public class TabListFaviconProvider { ...@@ -76,13 +76,15 @@ public class TabListFaviconProvider {
public void getFaviconForUrlAsync( public void getFaviconForUrlAsync(
String url, boolean isIncognito, Callback<Drawable> faviconCallback) { String url, boolean isIncognito, Callback<Drawable> faviconCallback) {
if (NativePageFactory.isNativePageUrl(url, isIncognito)) { if (NativePageFactory.isNativePageUrl(url, isIncognito)) {
faviconCallback.onResult(sChromeDrawable); faviconCallback.onResult(sRoundedChromeDrawable);
} else { } else {
mFaviconHelper.getLocalFaviconImageForURL( mFaviconHelper.getLocalFaviconImageForURL(
mProfile, url, mFaviconSize, (image, iconUrl) -> { mProfile, url, mFaviconSize, (image, iconUrl) -> {
if (image == null) return; if (image == null) {
Drawable drawable = processBitmap(image); faviconCallback.onResult(sRoundedGlobeDrawable);
faviconCallback.onResult(drawable); } else {
faviconCallback.onResult(processBitmap(image));
}
}); });
} }
} }
...@@ -90,14 +92,14 @@ public class TabListFaviconProvider { ...@@ -90,14 +92,14 @@ public class TabListFaviconProvider {
/** /**
* Synchronously get the processed favicon Drawable. * Synchronously get the processed favicon Drawable.
* @param url The URL whose favicon is requested. * @param url The URL whose favicon is requested.
* @param isIncognito Whether the tab is in cognito or not. * @param isIncognito Whether the tab is incognito or not.
* @param icon The favicon that was received. * @param icon The favicon that was received.
* @return The processed favicon. * @return The processed favicon.
*/ */
public Drawable getFaviconForUrlSync(String url, boolean isIncognito, Bitmap icon) { public Drawable getFaviconForUrlSync(String url, boolean isIncognito, Bitmap icon) {
if (icon == null) { if (icon == null) {
boolean isNativeUrl = NativePageFactory.isNativePageUrl(url, isIncognito); boolean isNativeUrl = NativePageFactory.isNativePageUrl(url, isIncognito);
return isNativeUrl ? sChromeDrawable : sGlobeDrawable; return isNativeUrl ? sRoundedChromeDrawable : sRoundedGlobeDrawable;
} else { } else {
return processBitmap(icon); return processBitmap(icon);
} }
......
...@@ -621,6 +621,11 @@ ...@@ -621,6 +621,11 @@
<dimen name="tab_list_mini_card_frame_size">1dp</dimen> <dimen name="tab_list_mini_card_frame_size">1dp</dimen>
<dimen name="tab_list_mini_card_text_size">12sp</dimen> <dimen name="tab_list_mini_card_text_size">12sp</dimen>
<dimen name="tab_grid_thumbnail_card_default_size">152dp</dimen> <dimen name="tab_grid_thumbnail_card_default_size">152dp</dimen>
<dimen name="tab_grid_thumbnail_favicon_frame_padding">16dp</dimen>
<dimen name="tab_grid_thumbnail_favicon_padding">24dp</dimen>
<dimen name="tab_grid_thumbnail_favicon_background_radius">3dp</dimen>
<dimen name="tab_grid_thumbnail_favicon_background_padding">2dp</dimen>
<dimen name="tab_grid_thumbnail_favicon_background_down_shift">2dp</dimen>
<dimen name="swipe_to_dismiss_threshold">72dp</dimen> <dimen name="swipe_to_dismiss_threshold">72dp</dimen>
<!-- RadioButtonWithDescription dimensions --> <!-- RadioButtonWithDescription dimensions -->
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment