Commit 80877321 authored by Mei Liang's avatar Mei Liang Committed by Commit Bot

Default group favicon for group tab in tab switcher

This CL setup the path to get a composed favicon from native for the
group tab in tab switcher. Native currently has dummy implementation, so
the group tab falls back to use a default favicon.

Change-Id: I4707632a240f05f6b81aac314eaad198e8262e76
Bug: 1064153
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2117649
Commit-Queue: Mei Liang <meiliang@chromium.org>
Reviewed-by: default avatarYusuf Ozuysal <yusufo@chromium.org>
Reviewed-by: default avatarWei-Yin Chen (陳威尹) <wychen@chromium.org>
Reviewed-by: default avatarYue Zhang <yuezhanggg@chromium.org>
Cr-Commit-Position: refs/heads/master@{#756563}
parent 382bb0d3
......@@ -37,6 +37,7 @@ android_resources("java_resources") {
"java/res/drawable/fake_search_box_text_box_bg_incognito.xml",
"java/res/drawable/hovered_tab_grid_card_background.xml",
"java/res/drawable/ic_check_googblue_20dp_animated.xml",
"java/res/drawable/ic_group_icon_16dp.xml",
"java/res/drawable/iph_drag_and_drop_animated_drawable.xml",
"java/res/drawable/iph_drag_and_drop_drawable.xml",
"java/res/drawable/popup_bg_dark.xml",
......
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2020 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:targetApi="21"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16.0"
android:viewportHeight="16.0">
<path
android:fillColor="@color/default_icon_color"
android:pathData="
M6,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2z
M6,14c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2z
M12,14c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2z
M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2z"/>
</vector>
\ No newline at end of file
......@@ -21,12 +21,15 @@ import org.chromium.chrome.browser.ui.favicon.FaviconHelper;
import org.chromium.chrome.browser.ui.favicon.FaviconUtils;
import org.chromium.chrome.tab_ui.R;
import java.util.List;
/**
* Provider for processed favicons in Tab list.
*/
public class TabListFaviconProvider {
private static Drawable sRoundedGlobeDrawable;
private static Drawable sRoundedChromeDrawable;
private static Drawable sRoundedComposedDefaultDrawable;
private final int mFaviconSize;
private final Context mContext;
@ColorInt
......@@ -48,6 +51,8 @@ public class TabListFaviconProvider {
mFaviconSize = context.getResources().getDimensionPixelSize(R.dimen.default_favicon_size);
if (sRoundedGlobeDrawable == null) {
// TODO(crbug.com/1066709): From Android Developer Documentation, we should avoid
// resizing vector drawable.
Drawable globeDrawable =
AppCompatResources.getDrawable(context, R.drawable.ic_globe_24dp);
Bitmap globeBitmap =
......@@ -62,6 +67,10 @@ public class TabListFaviconProvider {
BitmapFactory.decodeResource(mContext.getResources(), R.drawable.chromelogo16);
sRoundedChromeDrawable = processBitmap(chromeBitmap);
}
if (sRoundedComposedDefaultDrawable == null) {
sRoundedComposedDefaultDrawable =
AppCompatResources.getDrawable(context, R.drawable.ic_group_icon_16dp);
}
mDefaultIconColor = mContext.getResources().getColor(R.color.default_icon_color);
mIncognitoIconColor = mContext.getResources().getColor(R.color.default_icon_color_light);
}
......@@ -130,23 +139,44 @@ public class TabListFaviconProvider {
}
}
/**
* Asynchronously get the composed, up to 4, favicon Drawable.
* @param urls List of urls, up to 4, whose favicon are requested to be composed.
* @param isIncognito Whether the processed composed favicon is used for incognito or not.
* @param faviconCallback The callback that requests for the composed favicon.
*/
public void getComposedFaviconImageAsync(
List<String> urls, boolean isIncognito, Callback<Drawable> faviconCallback) {
assert urls != null && urls.size() > 1 && urls.size() <= 4;
mFaviconHelper.getComposedFaviconImage(mProfile, urls, mFaviconSize, (image, iconUrl) -> {
if (image == null) {
faviconCallback.onResult(getDefaultComposedImage(isIncognito));
} else {
faviconCallback.onResult(processBitmap(image));
}
});
}
private Drawable getDefaultComposedImage(boolean isIncognito) {
return getTintedDrawable(sRoundedComposedDefaultDrawable, isIncognito);
}
private Drawable getRoundedChromeDrawable(boolean isIncognito) {
@ColorInt
int color = isIncognito ? mIncognitoIconColor : mDefaultIconColor;
// Since static variable is still loaded when activity is destroyed due to configuration
// changes, e.g. light/dark theme changes, setColorFilter is needed when we retrieve the
// drawable. setColorFilter would be a no-op if color and the mode are the same.
sRoundedChromeDrawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
return sRoundedChromeDrawable;
return getTintedDrawable(sRoundedChromeDrawable, isIncognito);
}
private Drawable getRoundedGlobeDrawable(boolean isIncognito) {
return getTintedDrawable(sRoundedGlobeDrawable, isIncognito);
}
private Drawable getTintedDrawable(Drawable drawable, boolean isIncognito) {
@ColorInt
int color = isIncognito ? mIncognitoIconColor : mDefaultIconColor;
// Since static variable is still loaded when activity is destroyed due to configuration
// changes, e.g. light/dark theme changes, setColorFilter is needed when we retrieve the
// drawable. setColorFilter would be a no-op if color and the mode are the same.
sRoundedGlobeDrawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
return sRoundedGlobeDrawable;
drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
return drawable;
}
}
......@@ -1340,9 +1340,30 @@ class TabListMediator {
void updateFaviconForTab(Tab tab, @Nullable Bitmap icon) {
int modelIndex = mModel.indexFromId(tab.getId());
if (modelIndex == Tab.INVALID_TAB_ID) return;
// For tab group card in grid tab switcher, the favicon is set to be null.
if (mActionsOnAllRelatedTabs && getRelatedTabsForId(tab.getId()).size() > 1) {
mModel.get(modelIndex).model.set(TabProperties.FAVICON, null);
List<Tab> relatedTabList = getRelatedTabsForId(tab.getId());
if (mActionsOnAllRelatedTabs && relatedTabList.size() > 1) {
if (!TabUiFeatureUtilities.isTabGroupsAndroidContinuationEnabled()) {
// For tab group card in grid tab switcher, the favicon is set to be null.
mModel.get(modelIndex).model.set(TabProperties.FAVICON, null);
return;
}
// The order of the url list matches the multi-thumbnail.
List<String> urls = new ArrayList<>();
urls.add(tab.getUrlString());
for (int i = 0; urls.size() < 4 && i < relatedTabList.size(); i++) {
if (tab.getId() == relatedTabList.get(i).getId()) continue;
urls.add(relatedTabList.get(i).getUrlString());
}
// For tab group card in grid tab switcher, the favicon is the composed favicon.
mTabListFaviconProvider.getComposedFaviconImageAsync(
urls, tab.isIncognito(), (drawable) -> {
assert drawable != null;
mModel.get(modelIndex).model.set(TabProperties.FAVICON, drawable);
});
return;
}
......
......@@ -1972,6 +1972,39 @@ public class TabListMediatorUnitTest {
}
}
@Test
@Features.EnableFeatures({TAB_GROUPS_CONTINUATION_ANDROID})
public void testUpdateFaviconForGroup() {
setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER);
mMediator.setActionOnAllRelatedTabsForTesting(true);
mModel.get(0).model.set(TabProperties.FAVICON, null);
doNothing()
.when(mTabListFaviconProvider)
.getComposedFaviconImageAsync(any(), anyBoolean(), mCallbackCaptor.capture());
// Test a group of three.
TabImpl tab3 = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2, tab3));
createTabGroup(tabs, TAB1_ID);
mTabObserverCaptor.getValue().onFaviconUpdated(mTab1, mFaviconBitmap);
List<String> urls = new ArrayList<>(Arrays.asList(TAB1_URL, TAB2_URL, TAB3_URL));
verify(mTabListFaviconProvider).getComposedFaviconImageAsync(eq(urls), anyBoolean(), any());
mCallbackCaptor.getValue().onResult(mFaviconDrawable);
assertThat(mModel.get(0).model.get(TabProperties.FAVICON), equalTo(mFaviconDrawable));
// Test a group of five.
mModel.get(1).model.set(TabProperties.FAVICON, null);
TabImpl tab4 = prepareTab(0, "tab 4", TAB2_URL);
TabImpl tab5 = prepareTab(1, "tab 5", "www.tab5.com");
tabs.addAll(Arrays.asList(tab4, tab5));
createTabGroup(tabs, TAB2_ID);
mTabObserverCaptor.getValue().onFaviconUpdated(mTab2, mFaviconBitmap);
urls = new ArrayList<>(Arrays.asList(TAB2_URL, TAB1_URL, TAB3_URL, TAB2_URL));
verify(mTabListFaviconProvider).getComposedFaviconImageAsync(eq(urls), anyBoolean(), any());
mCallbackCaptor.getValue().onResult(mFaviconDrawable);
assertThat(mModel.get(1).model.get(TabProperties.FAVICON), equalTo(mFaviconDrawable));
}
private void initAndAssertAllProperties() {
List<Tab> tabs = new ArrayList<>();
for (int i = 0; i < mTabModel.getCount(); i++) {
......
......@@ -69,6 +69,20 @@ void FaviconHelper::Destroy(JNIEnv* env) {
delete this;
}
jboolean FaviconHelper::GetComposedFaviconImage(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& j_profile,
const base::android::JavaParamRef<jobjectArray>& j_urls,
jint j_desired_size_in_pixel,
const base::android::JavaParamRef<jobject>& j_favicon_image_callback) {
// TODO(crbug.com/1064153): Real implementation.
ScopedJavaLocalRef<jobject> j_favicon_bitmap;
ScopedJavaLocalRef<jstring> j_icon_url;
Java_FaviconImageCallback_onFaviconAvailable(env, j_favicon_image_callback,
j_favicon_bitmap, j_icon_url);
return true;
}
jboolean FaviconHelper::GetLocalFaviconImageForURL(
JNIEnv* env,
const JavaParamRef<jobject>& j_profile,
......
......@@ -27,6 +27,12 @@ class FaviconHelper {
public:
FaviconHelper();
void Destroy(JNIEnv* env);
jboolean GetComposedFaviconImage(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& j_profile,
const base::android::JavaParamRef<jobjectArray>& j_urls,
jint j_desired_size_in_pixel,
const base::android::JavaParamRef<jobject>& j_favicon_image_callback);
jboolean GetLocalFaviconImageForURL(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& j_profile,
......
......@@ -15,6 +15,7 @@ import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.annotations.CalledByNative;
......@@ -25,6 +26,8 @@ import org.chromium.components.url_formatter.UrlFormatter;
import org.chromium.content_public.browser.WebContents;
import org.chromium.url.GURL;
import java.util.List;
/**
* This is a helper class to use favicon_service.cc's functionality.
*
......@@ -214,6 +217,28 @@ public class FaviconHelper {
FaviconHelperJni.get().touchOnDemandFavicon(mNativeFaviconHelper, profile, iconUrl);
}
/**
* Get a composed, up to 4, Favicon bitmap for the requested arguments.
* @param profile Profile used for the FaviconService construction.
* @param urls The list of URLs whose favicon are requested to compose. Size should be 2 to 4.
* @param desiredSizeInPixel The size of the favicon in pixel we want to get.
* @param faviconImageCallback A method to be called back when the result is available. Note
* that this callback is not called if this method returns false.
* @return True if GetLocalFaviconImageForURL is successfully called.
*/
public boolean getComposedFaviconImage(Profile profile, @NonNull List<String> urls,
int desiredSizeInPixel, FaviconImageCallback faviconImageCallback) {
assert mNativeFaviconHelper != 0;
if (urls.size() <= 1 || urls.size() > 4) {
throw new IllegalStateException(
"Only able to compose 2 to 4 favicon, but requested " + urls.size());
}
return FaviconHelperJni.get().getComposedFaviconImage(mNativeFaviconHelper, profile,
urls.toArray(new String[0]), desiredSizeInPixel, faviconImageCallback);
}
private static boolean isInternalScheme(String url) {
GURL gurl = UrlFormatter.fixupUrl(url);
if (!gurl.isValid()) return false;
......@@ -224,6 +249,8 @@ public class FaviconHelper {
interface Natives {
long init();
void destroy(long nativeFaviconHelper);
boolean getComposedFaviconImage(long nativeFaviconHelper, Profile profile, String[] urls,
int desiredSizeInDip, FaviconImageCallback faviconImageCallback);
boolean getLocalFaviconImageForURL(long nativeFaviconHelper, Profile profile,
String pageUrl, int desiredSizeInDip, FaviconImageCallback faviconImageCallback);
boolean getForeignFaviconImageForURL(long nativeFaviconHelper, Profile profile,
......
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