// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.omnibox.status;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.text.TextUtils;
import android.view.View;
import androidx.annotation.ColorInt;
import androidx.annotation.ColorRes;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.supplier.OneshotSupplier;
import org.chromium.base.supplier.Supplier;
import org.chromium.chrome.browser.merchant_viewer.MerchantTrustSignalsCoordinator;
import org.chromium.chrome.browser.omnibox.LocationBarDataProvider;
import org.chromium.chrome.browser.omnibox.R;
import org.chromium.chrome.browser.omnibox.SearchEngineUtils;
import org.chromium.chrome.browser.omnibox.UrlBarEditingTextStateProvider;
import org.chromium.chrome.browser.omnibox.status.StatusProperties.PermissionIconResource;
import org.chromium.chrome.browser.omnibox.status.StatusProperties.StatusIconResource;
import org.chromium.chrome.browser.omnibox.status.StatusView.IconTransitionType;
import org.chromium.chrome.browser.omnibox.styles.OmniboxResourceProvider;
import org.chromium.chrome.browser.page_info.ChromePageInfoHighlight;
import org.chromium.chrome.browser.preferences.Pref;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.theme.ThemeUtils;
import org.chromium.chrome.browser.ui.theme.BrandedColorScheme;
import org.chromium.components.browser_ui.settings.SettingsUtils;
import org.chromium.components.browser_ui.site_settings.ContentSettingsResources;
import org.chromium.components.browser_ui.site_settings.SiteSettingsUtil;
import org.chromium.components.content_settings.ContentSettingValues;
import org.chromium.components.content_settings.ContentSettingsType;
import org.chromium.components.content_settings.CookieBlocking3pcdStatus;
import org.chromium.components.content_settings.CookieControlsBridge;
import org.chromium.components.content_settings.CookieControlsObserver;
import org.chromium.components.page_info.PageInfoController;
import org.chromium.components.permissions.PermissionDialogController;
import org.chromium.components.search_engines.TemplateUrlService;
import org.chromium.components.search_engines.TemplateUrlService.TemplateUrlServiceObserver;
import org.chromium.components.security_state.ConnectionSecurityLevel;
import org.chromium.components.user_prefs.UserPrefs;
import org.chromium.content_public.browser.BrowserContextHandle;
import org.chromium.content_public.browser.WebContents;
import org.chromium.ui.base.WindowAndroid;
import org.chromium.ui.modelutil.PropertyModel;
/** Contains the controller logic of the Status component. */
public class StatusMediator
implements PermissionDialogController.Observer,
TemplateUrlServiceObserver,
MerchantTrustSignalsCoordinator.OmniboxIconController,
CookieControlsObserver {
private static final int PERMISSION_ICON_DEFAULT_DISPLAY_TIMEOUT_MS = 8500;
public static final String PERMISSION_ICON_TIMEOUT_MS_PARAM = "PermissionIconTimeoutMs";
static final String COOKIE_CONTROLS_ICON = "COOKIE_CONTROLS_ICON";
private final PropertyModel mModel;
private final OneshotSupplier<TemplateUrlService> mTemplateUrlServiceSupplier;
private final Supplier<Profile> mProfileSupplier;
private final Supplier<MerchantTrustSignalsCoordinator>
mMerchantTrustSignalsCoordinatorSupplier;
private boolean mUrlHasFocus;
private boolean mVerboseStatusSpaceAvailable;
private boolean mPageIsPaintPreview;
private boolean mPageIsOffline;
private boolean mShowStatusIconWhenUrlFocused;
private boolean mIsSecurityViewShown;
private boolean mIsTablet;
private int mUrlMinWidth;
private int mSeparatorMinWidth;
private int mVerboseStatusTextMinWidth;
private @ConnectionSecurityLevel int mPageSecurityLevel;
private @BrandedColorScheme int mBrandedColorScheme = BrandedColorScheme.APP_DEFAULT;
private @DrawableRes int mSecurityIconRes;
private @ColorRes int mSecurityIconTintRes;
private @StringRes int mSecurityIconDescriptionRes;
private @ColorRes int mNavigationIconTintRes;
private Resources mResources;
private Context mContext;
private LocationBarDataProvider mLocationBarDataProvider;
private UrlBarEditingTextStateProvider mUrlBarEditingTextStateProvider;
private final PermissionDialogController mPermissionDialogController;
private final Handler mPermissionTaskHandler = new Handler();
private final Handler mStoreIconHandler = new Handler();
private @ContentSettingsType.EnumType int mLastPermission = ContentSettingsType.DEFAULT;
private final PageInfoIPHController mPageInfoIPHController;
private final WindowAndroid mWindowAndroid;
private boolean mUrlBarTextIsSearch = true;
private boolean mIsStoreIconShowing;
private float mUrlFocusPercent;
private int mPermissionIconDisplayTimeoutMs = PERMISSION_ICON_DEFAULT_DISPLAY_TIMEOUT_MS;
private CookieControlsBridge mCookieControlsBridge;
private boolean mCookieControlsVisible;
private boolean mThirdPartyCookiesBlocked;
private int mBlockingStatus3pcd;
private int mLastTabId;
private boolean mCurrentTabCrashed;
/**
* @param model The {@link PropertyModel} for this mediator.
* @param resources Used to load resources.
* @param context The {@link Context} for this Status component.
* @param urlBarEditingTextStateProvider Provides url bar text state.
* @param isTablet Whether the current device is a tablet.
* @param locationBarDataProvider Provides data to the location bar.
* @param permissionDialogController Controls showing permission dialogs.
* @param templateUrlServiceSupplier Supplies the {@link TemplateUrlService}.
* @param profileSupplier Supplies the current {@link Profile}.
* @param pageInfoIPHController Manages when an IPH bubble for PageInfo is shown.
* @param windowAndroid The current {@link WindowAndroid}.
* @param merchantTrustSignalsCoordinatorSupplier Supplier of {@link
* MerchantTrustSignalsCoordinator}. Can be null if a store icon shouldn't be shown, such as
* when called from a search activity.
*/
public StatusMediator(
PropertyModel model,
Resources resources,
Context context,
UrlBarEditingTextStateProvider urlBarEditingTextStateProvider,
boolean isTablet,
LocationBarDataProvider locationBarDataProvider,
PermissionDialogController permissionDialogController,
OneshotSupplier<TemplateUrlService> templateUrlServiceSupplier,
Supplier<Profile> profileSupplier,
PageInfoIPHController pageInfoIPHController,
WindowAndroid windowAndroid,
@Nullable
Supplier<MerchantTrustSignalsCoordinator>
merchantTrustSignalsCoordinatorSupplier) {
mModel = model;
mLocationBarDataProvider = locationBarDataProvider;
mTemplateUrlServiceSupplier = templateUrlServiceSupplier;
mTemplateUrlServiceSupplier.onAvailable(
(templateUrlService) -> {
templateUrlService.addObserver(this);
updateLocationBarIcon(IconTransitionType.CROSSFADE);
});
mProfileSupplier = profileSupplier;
mResources = resources;
mContext = context;
mUrlBarEditingTextStateProvider = urlBarEditingTextStateProvider;
mPageInfoIPHController = pageInfoIPHController;
mWindowAndroid = windowAndroid;
mMerchantTrustSignalsCoordinatorSupplier = merchantTrustSignalsCoordinatorSupplier;
mIsTablet = isTablet;
mShowStatusIconWhenUrlFocused = mIsTablet;
mPermissionDialogController = permissionDialogController;
mPermissionDialogController.addObserver(this);
updateColorTheme();
setStatusIconShown(/* show= */ !mLocationBarDataProvider.isIncognitoBranded());
updateLocationBarIcon(IconTransitionType.CROSSFADE);
}
public void destroy() {
mPermissionTaskHandler.removeCallbacksAndMessages(null);
mPermissionDialogController.removeObserver(this);
mStoreIconHandler.removeCallbacksAndMessages(null);
if (mMerchantTrustSignalsCoordinatorSupplier != null
&& mMerchantTrustSignalsCoordinatorSupplier.get() != null) {
mMerchantTrustSignalsCoordinatorSupplier.get().setOmniboxIconController(null);
}
if (mTemplateUrlServiceSupplier.hasValue()) {
mTemplateUrlServiceSupplier.get().removeObserver(this);
}
if (mCookieControlsBridge != null) {
mCookieControlsBridge.destroy();
mCookieControlsBridge = null;
}
}
/** Toggle animations of icon changes. */
void setAnimationsEnabled(boolean enabled) {
mModel.set(StatusProperties.ANIMATIONS_ENABLED, enabled);
}
/** Updates the icon, tint, and description of the security chip. */
void updateSecurityIcon(
@DrawableRes int securityIcon, @ColorRes int tintList, @StringRes int desc) {
mSecurityIconRes = securityIcon;
mSecurityIconTintRes = tintList;
mSecurityIconDescriptionRes = desc;
updateLocationBarIcon(IconTransitionType.CROSSFADE);
}
@DrawableRes
int getSecurityIconResource() {
return mSecurityIconRes;
}
/**
* Update the displayed page's security level and whether it's a paint preview or offline page.
*/
void updateVerboseStatus(
@ConnectionSecurityLevel int securityLevel,
boolean pageIsOffline,
boolean pageIsPaintPreview) {
boolean didUpdate = false;
if (mPageSecurityLevel != securityLevel) {
mPageSecurityLevel = securityLevel;
didUpdate = true;
}
if (mPageIsPaintPreview != pageIsPaintPreview) {
mPageIsPaintPreview = pageIsPaintPreview;
didUpdate = true;
}
if (mPageIsOffline != pageIsOffline) {
mPageIsOffline = pageIsOffline;
didUpdate = true;
}
if (didUpdate) {
updateVerbaseStatusTextVisibility();
updateLocationBarIcon(IconTransitionType.CROSSFADE);
updateColorTheme();
}
}
/** Specify minimum width of the separator field. */
void setSeparatorFieldMinWidth(int width) {
mSeparatorMinWidth = width;
}
/** Specify whether status icon should be shown when URL is focused. */
@VisibleForTesting
void setShowIconsWhenUrlFocused(boolean showIconWhenFocused) {
if (mShowStatusIconWhenUrlFocused == showIconWhenFocused) return;
mShowStatusIconWhenUrlFocused = showIconWhenFocused;
updateLocationBarIcon(IconTransitionType.CROSSFADE);
}
/**
* Specify object to receive status click events.
*
* @param listener Specifies target object to receive events.
*/
void setStatusClickListener(View.OnClickListener listener) {
mModel.set(StatusProperties.STATUS_CLICK_LISTENER, listener);
}
/** Update unfocused location bar width to determine shape and content of the Status view. */
void setUnfocusedLocationBarWidth(int width) {
// This unfocused width is used rather than observing #onMeasure() to avoid showing the
// verbose status when the animation to unfocus the URL bar has finished. There is a call to
// LocationBarLayout#onMeasure() after the URL focus animation has finished and before the
// location bar has received its updated width layout param.
int computedSpace = width - mUrlMinWidth - mSeparatorMinWidth;
boolean hasSpaceForStatus = width >= mVerboseStatusTextMinWidth;
if (hasSpaceForStatus) {
mModel.set(StatusProperties.VERBOSE_STATUS_TEXT_WIDTH, computedSpace);
}
if (hasSpaceForStatus != mVerboseStatusSpaceAvailable) {
mVerboseStatusSpaceAvailable = hasSpaceForStatus;
updateVerbaseStatusTextVisibility();
}
}
/** Report URL focus change. */
void setUrlHasFocus(boolean urlHasFocus) {
if (mUrlHasFocus == urlHasFocus) return;
mUrlHasFocus = urlHasFocus;
updateVerbaseStatusTextVisibility();
updateStatusVisibility();
updateLocationBarIcon(IconTransitionType.CROSSFADE);
// Set the default match to be a search on an unfocus event to avoid the globe sticking
// around for subsequent focus events.
if (!mUrlHasFocus) updateLocationBarIconForDefaultMatchCategory(true);
}
void setStatusIconShown(boolean show) {
mModel.set(StatusProperties.SHOW_STATUS_ICON, show);
}
void setStatusIconAlpha(float alpha) {
mModel.set(StatusProperties.STATUS_ICON_ALPHA, alpha);
}
void updateStatusVisibility() {
// This logic doesn't apply to tablets.
if (mIsTablet) return;
boolean shouldShowLogo = !mLocationBarDataProvider.isIncognitoBranded();
setShowIconsWhenUrlFocused(shouldShowLogo);
if (!shouldShowLogo) return;
if (mProfileSupplier.hasValue() && isNtpVisible()) {
setStatusIconShown(shouldShowLogo && (mUrlHasFocus || mUrlFocusPercent > 0));
} else {
setStatusIconShown(true);
}
}
/**
* Sets the visibility of the status icon background.
*
* @param show True to make it visible.
*/
void setStatusIconBackgroundVisibility(boolean show) {
mModel.set(StatusProperties.SHOW_STATUS_ICON_BACKGROUND, show);
}
/**
* Set the url focus change percent.
*
* @param percent The current focus percent.
*/
void setUrlFocusChangePercent(float percent) {
// On tablets, the status icon should always be shown so the following logic doesn't apply.
assert !mIsTablet : "This logic shouldn't be called on tablets";
boolean couldAffectIcon =
(mUrlFocusPercent == 0.0f && percent > 0.0f)
|| (percent == 0.0f && mUrlFocusPercent > 0.0f);
mUrlFocusPercent = percent;
updateStatusVisibility();
// Only fade the animation on the new tab page.
if (mProfileSupplier.hasValue() && isNtpVisible()) {
setStatusIconAlpha(percent);
} else {
setStatusIconAlpha(1f);
}
if (couldAffectIcon) {
updateLocationBarIcon(IconTransitionType.CROSSFADE);
}
}
/** Specify minimum width of an URL field. */
void setUrlMinWidth(int width) {
mUrlMinWidth = width;
}
/** Set the {@link BrandedColorScheme}. */
void setBrandedColorScheme(@BrandedColorScheme int brandedColorScheme) {
if (mBrandedColorScheme != brandedColorScheme) {
mBrandedColorScheme = brandedColorScheme;
updateColorTheme();
}
}
/** Specify minimum width of the verbose status text field. */
void setVerboseStatusTextMinWidth(int width) {
mVerboseStatusTextMinWidth = width;
}
/** Update visibility of the verbose status text field. */
private void updateVerbaseStatusTextVisibility() {
int statusText = 0;
if (mPageIsPaintPreview) {
statusText = R.string.location_bar_paint_preview_page_status;
} else if (mPageIsOffline) {
statusText = R.string.location_bar_verbose_status_offline;
}
// Decide whether presenting verbose status text makes sense.
boolean newVisibility =
shouldShowVerboseStatusText()
&& mVerboseStatusSpaceAvailable
&& (!mUrlHasFocus)
&& (statusText != 0);
// Update status content only if it is visible.
// Note: PropertyModel will help us avoid duplicate updates with the
// same value.
if (newVisibility) {
mModel.set(StatusProperties.VERBOSE_STATUS_TEXT_STRING_RES, statusText);
}
mModel.set(StatusProperties.VERBOSE_STATUS_TEXT_VISIBLE, newVisibility);
}
/** Update color theme for all status components. */
private void updateColorTheme() {
final @ColorInt int separatorColor =
OmniboxResourceProvider.getStatusSeparatorColor(mContext, mBrandedColorScheme);
mModel.set(StatusProperties.SEPARATOR_COLOR, separatorColor);
mNavigationIconTintRes = ThemeUtils.getThemedToolbarIconTintRes(mBrandedColorScheme);
final @ColorInt int textColor = getTextColor();
if (textColor != 0) {
mModel.set(StatusProperties.VERBOSE_STATUS_TEXT_COLOR, textColor);
}
updateLocationBarIcon(IconTransitionType.CROSSFADE);
}
private @ColorInt int getTextColor() {
if (mPageIsPaintPreview) {
return OmniboxResourceProvider.getStatusPreviewTextColor(mContext, mBrandedColorScheme);
}
if (mPageIsOffline) {
return OmniboxResourceProvider.getStatusOfflineTextColor(mContext, mBrandedColorScheme);
}
return 0;
}
/** Reports whether security icon is shown. */
@VisibleForTesting
boolean isSecurityViewShown() {
return mIsSecurityViewShown;
}
/** Get a CookieControlsBridge instance for testing purposes. */
@VisibleForTesting
CookieControlsBridge getCookieControlsBridge() {
return mCookieControlsBridge;
}
/** Set a CookieControlsBridge instance for testing purposes. */
@VisibleForTesting
void setCookieControlsBridge(CookieControlsBridge cookieControlsBridge) {
mCookieControlsBridge = cookieControlsBridge;
}
/** Compute verbose status text for the current page. */
private boolean shouldShowVerboseStatusText() {
return mPageIsOffline || mPageIsPaintPreview;
}
private boolean isNtpVisible() {
return mLocationBarDataProvider.getNewTabPageDelegate().isCurrentlyVisible();
}
/**
* Update selection of icon presented on the location bar.
*
* <ul>
* <li>Navigation button is:
* <ul>
* <li>shown only on large form factor devices (tablets and up)
* <li>shown only if URL is focused.
* </ul>
* <li>Security icon is:
* <ul>
* <li>shown only if specified,
* <li>not shown if URL is focused.
* </ul>
* </ul>
*/
void updateLocationBarIcon(@IconTransitionType int transitionType) {
// Reset the last saved permission.
mLastPermission = ContentSettingsType.DEFAULT;
// Reset the store icon status.
mIsStoreIconShowing = false;
// Update the accessibility description before continuing since we need it either way.
mModel.set(StatusProperties.STATUS_ICON_DESCRIPTION_RES, getAccessibilityDescriptionRes());
// No need to proceed further if we've already updated it for the search engine icon.
if (maybeUpdateStatusIconForSearchEngineIcon()) return;
int icon = 0;
int tint = 0;
int toast = 0;
mIsSecurityViewShown = false;
if (mUrlHasFocus) {
if (mShowStatusIconWhenUrlFocused) {
icon =
mUrlBarTextIsSearch
? R.drawable.ic_suggestion_magnifier
: R.drawable.ic_globe_24dp;
tint = mNavigationIconTintRes;
}
} else if (mSecurityIconRes != 0) {
mIsSecurityViewShown = true;
icon = mSecurityIconRes;
tint = mSecurityIconTintRes;
toast = R.string.menu_page_info;
}
// If the icon is missing, fallback to the info icon.
StatusIconResource statusIcon = icon == 0 ? null : new StatusIconResource(icon, tint);
if (statusIcon != null) {
statusIcon.setTransitionType(transitionType);
}
mModel.set(StatusProperties.STATUS_ICON_RESOURCE, statusIcon);
mModel.set(StatusProperties.STATUS_ACCESSIBILITY_TOAST_RES, toast);
mModel.set(
StatusProperties.STATUS_ACCESSIBILITY_DOUBLE_TAP_DESCRIPTION_RES,
R.string.accessibility_toolbar_view_site_info);
}
/**
* @return True if the security icon has been set for the search engine icon.
*/
@VisibleForTesting
boolean maybeUpdateStatusIconForSearchEngineIcon() {
// Show the logo unfocused if we're on the NTP.
if (!shouldDisplaySearchEngineIcon()) return false;
mModel.set(
StatusProperties.STATUS_ICON_RESOURCE, getStatusIconResourceForSearchEngineIcon());
return true;
}
/**
* Returns whether the search engine icon should be displayed in the current context. This is
* independent from alpha/visibility.
*/
boolean shouldDisplaySearchEngineIcon() {
if (mLocationBarDataProvider.isIncognitoBranded()) {
return false;
}
if (mUrlHasFocus && mShowStatusIconWhenUrlFocused) {
return true;
}
return (mUrlHasFocus || mUrlFocusPercent > 0)
&& isNtpVisible()
&& mProfileSupplier.hasValue();
}
/** Returns status icon resource for the user-selected default search engine. */
private @NonNull StatusIconResource getStatusIconResourceForSearchEngineIcon() {
// If the current url text is a valid url, then swap the dse icon for a globe.
if (!mUrlBarTextIsSearch) {
return SearchEngineUtils.getFallbackNavigationIcon(mBrandedColorScheme);
}
if (!mProfileSupplier.hasValue()) {
return SearchEngineUtils.getFallbackSearchIcon(mBrandedColorScheme);
}
var profile = mProfileSupplier.get();
return SearchEngineUtils.getForProfile(profile).getSearchEngineLogo(mBrandedColorScheme);
}
/** Return the resource id for the accessibility description or 0 if none apply. */
private int getAccessibilityDescriptionRes() {
if (mUrlHasFocus && !mLocationBarDataProvider.isIncognitoBranded()) {
return 0;
}
return (mSecurityIconRes != 0) ? mSecurityIconDescriptionRes : 0;
}
/**
* Informs StatusMediator that the default match may have changed categories, updating the
* status icon if it has.
*/
/* package */ void updateLocationBarIconForDefaultMatchCategory(boolean defaultMatchIsSearch) {
if (defaultMatchIsSearch != mUrlBarTextIsSearch) {
mUrlBarTextIsSearch = defaultMatchIsSearch;
updateLocationBarIcon(IconTransitionType.CROSSFADE);
}
}
@VisibleForTesting
protected String resolveUrlBarTextWithAutocomplete(CharSequence urlBarText) {
String currentAutocompleteText = mUrlBarEditingTextStateProvider.getTextWithAutocomplete();
String urlTextWithAutocomplete;
if (TextUtils.isEmpty(urlBarText)) {
// TODO (crbug.com/1012870): This is to workaround the UrlBar text being empty but the
// autocomplete text still pointing at the previous url's autocomplete text.
urlTextWithAutocomplete = "";
} else if (TextUtils.indexOf(currentAutocompleteText, urlBarText) > -1) {
// TODO(crbug.com/40103581): This is to workaround the UrlBar text pointing to the
// "current" url and the the autocomplete text pointing to the "previous" url.
urlTextWithAutocomplete = currentAutocompleteText;
} else {
// If the above cases don't apply, then we should use the UrlBar text itself.
urlTextWithAutocomplete = urlBarText.toString();
}
return urlTextWithAutocomplete;
}
public void onIncognitoStateChanged() {
boolean incognitoBadgeVisible = mLocationBarDataProvider.isIncognitoBranded();
mModel.set(StatusProperties.INCOGNITO_BADGE_VISIBLE, incognitoBadgeVisible);
mModel.set(StatusProperties.STATUS_ICON_RESOURCE, null);
setStatusIconAlpha(1f);
setStatusIconShown(false);
}
// PermissionDialogController.Observer interface
@Override
public void onDialogResult(
WindowAndroid window,
@ContentSettingsType.EnumType int[] permissions,
@ContentSettingValues int result) {
if (window != mWindowAndroid) {
return;
}
@ContentSettingsType.EnumType
int permission = SiteSettingsUtil.getHighestPriorityPermission(permissions);
// The permission is not available in the settings page. Do not show an icon.
if (permission == ContentSettingsType.DEFAULT) return;
resetCustomIconsStatus();
mLastPermission = permission;
boolean isIncognitoBranded = mLocationBarDataProvider.isIncognitoBranded();
Drawable permissionDrawable =
ContentSettingsResources.getIconForOmnibox(
mContext, mLastPermission, result, isIncognitoBranded);
PermissionIconResource permissionIconResource =
new PermissionIconResource(permissionDrawable, isIncognitoBranded);
permissionIconResource.setTransitionType(IconTransitionType.ROTATE);
// We only want to notify the IPH controller after the icon transition is finished.
// IPH is controlled by the FeatureEngagement system through finch with a field trial
// testing configuration.
permissionIconResource.setAnimationFinishedCallback(this::startIPH);
// Set the timer to switch the icon back afterwards.
mPermissionTaskHandler.removeCallbacksAndMessages(null);
mModel.set(StatusProperties.STATUS_ICON_RESOURCE, permissionIconResource);
Runnable finishIconAnimation = () -> updateLocationBarIcon(IconTransitionType.ROTATE);
mPermissionTaskHandler.postDelayed(finishIconAnimation, mPermissionIconDisplayTimeoutMs);
}
// CookieControlsObserver interface
@Override
public void onHighlightCookieControl(boolean shouldHighlight) {
if (shouldHighlight) {
animateCookieControlsIcon(
() -> {
if (mBlockingStatus3pcd == CookieBlocking3pcdStatus.NOT_IN3PCD) {
mPageInfoIPHController.showCookieControlsIPH(
getIPHTimeout(), R.string.cookie_controls_iph_message);
}
});
}
}
@Override
public void onStatusChanged(
boolean controlsVisible,
boolean protectionsOn,
int enforcement,
int blockingStatus,
long expiration) {
mCookieControlsVisible = controlsVisible;
mThirdPartyCookiesBlocked = protectionsOn;
mBlockingStatus3pcd = blockingStatus;
}
private void animateCookieControlsIcon(Runnable onAnimationFinished) {
// Check if the web content is valid before attempting to animate.
if (mLocationBarDataProvider.getTab().getWebContents() == null) {
return;
}
resetCustomIconsStatus();
boolean isIncognitoBranded = mLocationBarDataProvider.isIncognitoBranded();
Drawable eyeCrossedIcon =
SettingsUtils.getTintedIcon(
mContext,
R.drawable.ic_eye_crossed,
isIncognitoBranded
? R.color.default_icon_color_blue_light
: R.color.default_icon_color_accent1_tint_list);
PermissionIconResource permissionIconResource =
new PermissionIconResource(
eyeCrossedIcon, isIncognitoBranded, COOKIE_CONTROLS_ICON);
permissionIconResource.setTransitionType(IconTransitionType.ROTATE);
permissionIconResource.setAnimationFinishedCallback(
() -> {
if (mCookieControlsBridge != null) {
mCookieControlsBridge.onEntryPointAnimated();
}
onAnimationFinished.run();
});
// Set the timer to switch the icon back afterwards.
mPermissionTaskHandler.removeCallbacksAndMessages(null);
mModel.set(StatusProperties.STATUS_ICON_RESOURCE, permissionIconResource);
mPermissionTaskHandler.postDelayed(
() -> updateLocationBarIcon(IconTransitionType.ROTATE),
mPermissionIconDisplayTimeoutMs);
}
private void startIPH() {
if (!mProfileSupplier.hasValue()) return;
mPageInfoIPHController.onPermissionDialogShown(mProfileSupplier.get(), getIPHTimeout());
}
void setStoreIconController() {
if (mMerchantTrustSignalsCoordinatorSupplier != null
&& mMerchantTrustSignalsCoordinatorSupplier.get() != null) {
mMerchantTrustSignalsCoordinatorSupplier.get().setOmniboxIconController(this);
}
}
// MerchantTrustSignalsCoordinator.OmniboxIconController interface
@Override
public void showStoreIcon(
WindowAndroid window,
String url,
Drawable drawable,
@StringRes int stringId,
boolean canShowIph) {
if ((window != mWindowAndroid)
|| (!url.equals(mLocationBarDataProvider.getCurrentGurl().getSpec()))
|| (mLocationBarDataProvider.isOffTheRecord())) {
return;
}
resetCustomIconsStatus();
// Use {@link PermissionIconResource} instead of {@link StatusIconResource} to encapsulate
// the icon with a circle background.
StatusIconResource storeIconResource = new PermissionIconResource(drawable, false);
storeIconResource.setTransitionType(IconTransitionType.ROTATE);
storeIconResource.setAnimationFinishedCallback(
() -> {
if (canShowIph) {
mPageInfoIPHController.showStoreIconIPH(getIPHTimeout(), stringId);
}
});
mModel.set(StatusProperties.STATUS_ICON_RESOURCE, storeIconResource);
mStoreIconHandler.postDelayed(
() -> {
updateLocationBarIcon(IconTransitionType.ROTATE);
},
mPermissionIconDisplayTimeoutMs);
mIsStoreIconShowing = true;
}
// Reset all customized icons' status to avoid different icons' conflicts.
@VisibleForTesting
void resetCustomIconsStatus() {
mPermissionTaskHandler.removeCallbacksAndMessages(null);
mLastPermission = ContentSettingsType.DEFAULT;
mStoreIconHandler.removeCallbacksAndMessages(null);
mIsStoreIconShowing = false;
}
/**
* @return A timeout for the IPH bubble. The bubble is shown after the permission icon animation
* finishes and should disappear when it animates out.
*/
private int getIPHTimeout() {
return mPermissionIconDisplayTimeoutMs - (2 * StatusView.ICON_ROTATION_DURATION_MS);
}
/** Notifies that the page info was opened. */
void onPageInfoOpened() {
resetCustomIconsStatus();
updateLocationBarIcon(IconTransitionType.CROSSFADE);
}
public int getLastPermission() {
return mLastPermission;
}
boolean isStoreIconShowing() {
return mIsStoreIconShowing;
}
/**
* @return {@link ChromePageInfoHighlight} which provides the PageInfo highlight row info when
* user clicks the omnibox icon.
*/
ChromePageInfoHighlight getPageInfoHighlight() {
if (mLastPermission != PageInfoController.NO_HIGHLIGHTED_PERMISSION) {
return ChromePageInfoHighlight.forPermission(mLastPermission);
} else if (mIsStoreIconShowing) {
return ChromePageInfoHighlight.forStoreInfo(true);
} else {
return ChromePageInfoHighlight.noHighlight();
}
}
@Override
public void onTemplateURLServiceChanged() {
updateLocationBarIcon(IconTransitionType.CROSSFADE);
}
void setTranslationX(float translationX) {
mModel.set(StatusProperties.TRANSLATION_X, translationX);
}
void setTooltipText(@StringRes int tooltipTextResId) {
mModel.set(StatusProperties.STATUS_VIEW_TOOLTIP_TEXT, tooltipTextResId);
}
void setHoverHighlight(@DrawableRes int hoverHighlightResId) {
mModel.set(StatusProperties.STATUS_VIEW_HOVER_HIGHLIGHT, hoverHighlightResId);
}
public void onUrlChanged() {
var currentTab = mLocationBarDataProvider.getTab();
if (mProfileSupplier.hasValue() && currentTab != null) {
WebContents webContents = currentTab.getWebContents();
Profile profile = mProfileSupplier.get();
if (webContents != null && profile != null) {
BrowserContextHandle originalBrowserContext =
profile.isOffTheRecord() ? profile.getOriginalProfile() : null;
if (mCookieControlsBridge == null) {
mCookieControlsBridge =
new CookieControlsBridge(this, webContents, originalBrowserContext);
} else if (mLastTabId != currentTab.getId() || mCurrentTabCrashed) {
mCookieControlsBridge.updateWebContents(webContents, originalBrowserContext);
mCurrentTabCrashed = false;
}
}
mLastTabId = currentTab.getId();
}
}
public void onPageLoadStopped() {
Profile profile = mProfileSupplier.get();
if (profile == null) {
return;
}
if (mPageSecurityLevel != ConnectionSecurityLevel.SECURE) {
return;
}
if (mBlockingStatus3pcd != CookieBlocking3pcdStatus.NOT_IN3PCD) {
if (!mCookieControlsVisible || !mThirdPartyCookiesBlocked) return;
if (UserPrefs.get(profile).getInteger(Pref.TRACKING_PROTECTION_ONBOARDING_ACK_ACTION)
== 0) {
return;
}
animateCookieControlsIcon(() -> {});
}
}
public void onTabCrashed() {
mCurrentTabCrashed = true;
}
}