blob: b27fda8653bcaec2f5a765cfe8e878a02eb7e6e3 [file] [log] [blame]
// Copyright 2020 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.styles;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Drawable.ConstantState;
import android.util.SparseArray;
import android.util.TypedValue;
import androidx.annotation.ColorInt;
import androidx.annotation.ColorRes;
import androidx.annotation.DimenRes;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Px;
import androidx.annotation.StringRes;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.content.res.AppCompatResources;
import com.google.android.material.color.MaterialColors;
import org.chromium.base.ThreadUtils;
import org.chromium.chrome.browser.night_mode.NightModeUtils;
import org.chromium.chrome.browser.omnibox.OmniboxFeatures;
import org.chromium.chrome.browser.omnibox.R;
import org.chromium.chrome.browser.theme.ThemeUtils;
import org.chromium.chrome.browser.ui.theme.BrandedColorScheme;
import org.chromium.components.browser_ui.styles.SemanticColorUtils;
import org.chromium.ui.util.ColorUtils;
/** Provides resources specific to Omnibox. */
public class OmniboxResourceProvider {
private static final String TAG = "OmniboxResourceProvider";
private static SparseArray<ConstantState> sDrawableCache = new SparseArray<>();
private static SparseArray<String> sStringCache = new SparseArray<>();
/**
* As {@link androidx.appcompat.content.res.AppCompatResources#getDrawable(Context, int)} but
* potentially augmented with caching. If caching is enabled, there is a single, unbounded cache
* of ConstantState shared by all contexts.
*/
public static @NonNull Drawable getDrawable(Context context, @DrawableRes int res) {
ThreadUtils.assertOnUiThread();
boolean cacheResources = OmniboxFeatures.shouldCacheSuggestionResources();
ConstantState constantState = cacheResources ? sDrawableCache.get(res, null) : null;
if (constantState != null) {
return constantState.newDrawable(context.getResources());
}
Drawable drawable = AppCompatResources.getDrawable(context, res);
if (cacheResources) {
sDrawableCache.put(res, drawable.getConstantState());
}
return drawable;
}
/**
* As {@link android.content.res.Resources#getString(int, Object...)} but potentially augmented
* with caching. If caching is enabled, there is a single, unbounded string cache shared by all
* contexts. When dealing with strings with format params, the raw string is cached and
* formatted on demand using the default locale.
*/
public static @NonNull String getString(Context context, @StringRes int res, Object... args) {
ThreadUtils.assertOnUiThread();
boolean cacheResources = OmniboxFeatures.shouldCacheSuggestionResources();
String string = cacheResources ? sStringCache.get(res, null) : null;
if (string == null) {
string = context.getResources().getString(res);
if (cacheResources) {
sStringCache.put(res, string);
}
}
return args.length == 0
? string
: String.format(context.getResources().getConfiguration().getLocales().get(0),
string, args);
}
/**
* Clears the drawable cache to avoid, e.g. caching a now incorrectly colored drawable
* resource.
*/
public static void invalidateDrawableCache() {
sDrawableCache.clear();
}
@VisibleForTesting
public static SparseArray<ConstantState> getDrawableCacheForTesting() {
return sDrawableCache;
}
@VisibleForTesting
public static SparseArray<String> getStringCacheForTesting() {
return sStringCache;
}
/**
* Returns a drawable for a given attribute depending on a {@link BrandedColorScheme}
*
* @param context The {@link Context} used to retrieve resources.
* @param brandedColorScheme {@link BrandedColorScheme} to use.
* @param attributeResId A resource ID of an attribute to resolve.
* @return A background drawable resource ID providing ripple effect.
*/
public static Drawable resolveAttributeToDrawable(
Context context, @BrandedColorScheme int brandedColorScheme, int attributeResId) {
Context wrappedContext = maybeWrapContext(context, brandedColorScheme);
@DrawableRes
int resourceId = resolveAttributeToDrawableRes(wrappedContext, attributeResId);
return getDrawable(wrappedContext, resourceId);
}
/**
* Returns the ColorScheme based on the incognito state and the background color.
*
* @param context The {@link Context}.
* @param isIncognito Whether incognito mode is enabled.
* @param primaryBackgroundColor The primary background color of the omnibox.
* @return The {@link BrandedColorScheme}.
*/
public static @BrandedColorScheme int getBrandedColorScheme(
Context context, boolean isIncognito, @ColorInt int primaryBackgroundColor) {
if (isIncognito) return BrandedColorScheme.INCOGNITO;
if (ThemeUtils.isUsingDefaultToolbarColor(context, isIncognito, primaryBackgroundColor)) {
return BrandedColorScheme.APP_DEFAULT;
}
return ColorUtils.shouldUseLightForegroundOnBackground(primaryBackgroundColor)
? BrandedColorScheme.DARK_BRANDED_THEME
: BrandedColorScheme.LIGHT_BRANDED_THEME;
}
/**
* Returns the primary text color for the url bar.
*
* @param context The context to retrieve the resources from.
* @param brandedColorScheme The {@link BrandedColorScheme}.
* @return Primary url bar text color.
*/
public static @ColorInt int getUrlBarPrimaryTextColor(
Context context, @BrandedColorScheme int brandedColorScheme) {
final Resources resources = context.getResources();
@ColorInt
int color;
if (brandedColorScheme == BrandedColorScheme.LIGHT_BRANDED_THEME) {
color = resources.getColor(R.color.branded_url_text_on_light_bg);
} else if (brandedColorScheme == BrandedColorScheme.DARK_BRANDED_THEME) {
color = resources.getColor(R.color.branded_url_text_on_dark_bg);
} else if (brandedColorScheme == BrandedColorScheme.INCOGNITO) {
color = resources.getColor(R.color.url_bar_primary_text_incognito);
} else {
color = MaterialColors.getColor(context, R.attr.colorOnSurface, TAG);
}
return color;
}
/**
* Returns the secondary text color for the url bar.
*
* @param context The context to retrieve the resources from.
* @param brandedColorScheme The {@link BrandedColorScheme}.
* @return Secondary url bar text color.
*/
public static @ColorInt int getUrlBarSecondaryTextColor(
Context context, @BrandedColorScheme int brandedColorScheme) {
final Resources resources = context.getResources();
@ColorInt
int color;
if (brandedColorScheme == BrandedColorScheme.LIGHT_BRANDED_THEME) {
color = resources.getColor(R.color.branded_url_text_variant_on_light_bg);
} else if (brandedColorScheme == BrandedColorScheme.DARK_BRANDED_THEME) {
color = resources.getColor(R.color.branded_url_text_variant_on_dark_bg);
} else if (brandedColorScheme == BrandedColorScheme.INCOGNITO) {
color = resources.getColor(R.color.url_bar_secondary_text_incognito);
} else {
color = MaterialColors.getColor(context, R.attr.colorOnSurfaceVariant, TAG);
}
return color;
}
/**
* Returns the hint text color for the url bar.
*
* @param context The context to retrieve the resources from.
* @param brandedColorScheme The {@link BrandedColorScheme}.
* @return The url bar hint text color.
*/
public static @ColorInt int getUrlBarHintTextColor(
Context context, @BrandedColorScheme int brandedColorScheme) {
return getUrlBarSecondaryTextColor(context, brandedColorScheme);
}
/**
* Returns the danger semantic color.
*
* @param context The context to retrieve the resources from.
* @param brandedColorScheme The {@link BrandedColorScheme}.
* @return The danger semantic color to be used on the url bar.
*/
public static @ColorInt int getUrlBarDangerColor(
Context context, @BrandedColorScheme int brandedColorScheme) {
// Danger color has semantic meaning and it doesn't change with dynamic colors.
@ColorRes
int colorId = R.color.default_red;
if (brandedColorScheme == BrandedColorScheme.DARK_BRANDED_THEME
|| brandedColorScheme == BrandedColorScheme.INCOGNITO) {
colorId = R.color.default_red_light;
} else if (brandedColorScheme == BrandedColorScheme.LIGHT_BRANDED_THEME) {
colorId = R.color.default_red_dark;
}
return context.getResources().getColor(colorId);
}
/**
* Returns the secure semantic color.
*
* @param context The context to retrieve the resources from.
* @param brandedColorScheme The {@link BrandedColorScheme}.
* @return The secure semantic color to be used on the url bar.
*/
public static @ColorInt int getUrlBarSecureColor(
Context context, @BrandedColorScheme int brandedColorScheme) {
// Secure color has semantic meaning and it doesn't change with dynamic colors.
@ColorRes
int colorId = R.color.default_green;
if (brandedColorScheme == BrandedColorScheme.DARK_BRANDED_THEME
|| brandedColorScheme == BrandedColorScheme.INCOGNITO) {
colorId = R.color.default_green_light;
} else if (brandedColorScheme == BrandedColorScheme.LIGHT_BRANDED_THEME) {
colorId = R.color.default_green_dark;
}
return context.getResources().getColor(colorId);
}
/**
* Returns the primary text color for the suggestions.
*
* @param context The context to retrieve the resources from.
* @param brandedColorScheme The {@link BrandedColorScheme}.
* @return Primary suggestion text color.
*/
public static @ColorInt int getSuggestionPrimaryTextColor(
Context context, @BrandedColorScheme int brandedColorScheme) {
// Suggestions are only shown when the omnibox is focused, hence LIGHT_THEME and DARK_THEME
// are ignored as they don't change the result.
return brandedColorScheme == BrandedColorScheme.INCOGNITO
? context.getColor(R.color.default_text_color_light)
: MaterialColors.getColor(context, R.attr.colorOnSurface, TAG);
}
/**
* Returns the secondary text color for the suggestions.
*
* @param context The context to retrieve the resources from.
* @param brandedColorScheme The {@link BrandedColorScheme}.
* @return Secondary suggestion text color.
*/
public static @ColorInt int getSuggestionSecondaryTextColor(
Context context, @BrandedColorScheme int brandedColorScheme) {
// Suggestions are only shown when the omnibox is focused, hence LIGHT_THEME and DARK_THEME
// are ignored as they don't change the result.
return brandedColorScheme == BrandedColorScheme.INCOGNITO
? context.getColor(R.color.default_text_color_secondary_light)
: MaterialColors.getColor(context, R.attr.colorOnSurfaceVariant, TAG);
}
/**
* Returns the URL text color for the suggestions.
*
* @param context The context to retrieve the resources from.
* @param brandedColorScheme The {@link BrandedColorScheme}.
* @return URL suggestion text color.
*/
public static @ColorInt int getSuggestionUrlTextColor(
Context context, @BrandedColorScheme int brandedColorScheme) {
// Suggestions are only shown when the omnibox is focused, hence LIGHT_THEME and DARK_THEME
// are ignored as they don't change the result.
final @ColorInt int color = brandedColorScheme == BrandedColorScheme.INCOGNITO
? context.getColor(R.color.suggestion_url_color_incognito)
: SemanticColorUtils.getDefaultTextColorLink(context);
return color;
}
/**
* Returns the separator line color for the status view.
*
* @param context The context to retrieve the resources from.
* @param brandedColorScheme The {@link BrandedColorScheme}.
* @return Status view separator color.
*/
public static @ColorInt int getStatusSeparatorColor(
Context context, @BrandedColorScheme int brandedColorScheme) {
if (brandedColorScheme == BrandedColorScheme.LIGHT_BRANDED_THEME) {
return context.getColor(R.color.locationbar_status_separator_color_dark);
}
if (brandedColorScheme == BrandedColorScheme.DARK_BRANDED_THEME) {
return context.getColor(R.color.locationbar_status_separator_color_light);
}
if (brandedColorScheme == BrandedColorScheme.INCOGNITO) {
return context.getColor(R.color.locationbar_status_separator_color_incognito);
}
return MaterialColors.getColor(context, R.attr.colorOutline, TAG);
}
/**
* Returns the preview text color for the status view.
*
* @param context The context to retrieve the resources from.
* @param brandedColorScheme The {@link BrandedColorScheme}.
* @return Status view preview text color.
*/
public static @ColorInt int getStatusPreviewTextColor(
Context context, @BrandedColorScheme int brandedColorScheme) {
if (brandedColorScheme == BrandedColorScheme.LIGHT_BRANDED_THEME) {
return context.getColor(R.color.locationbar_status_preview_color_dark);
}
if (brandedColorScheme == BrandedColorScheme.DARK_BRANDED_THEME) {
return context.getColor(R.color.locationbar_status_preview_color_light);
}
if (brandedColorScheme == BrandedColorScheme.INCOGNITO) {
return context.getColor(R.color.locationbar_status_preview_color_incognito);
}
return MaterialColors.getColor(context, R.attr.colorPrimary, TAG);
}
/**
* Returns the offline text color for the status view.
*
* @param context The context to retrieve the resources from.
* @param brandedColorScheme The {@link BrandedColorScheme}.
* @return Status view offline text color.
*/
public static @ColorInt int getStatusOfflineTextColor(
Context context, @BrandedColorScheme int brandedColorScheme) {
if (brandedColorScheme == BrandedColorScheme.LIGHT_BRANDED_THEME) {
return context.getColor(R.color.locationbar_status_offline_color_dark);
}
if (brandedColorScheme == BrandedColorScheme.DARK_BRANDED_THEME) {
return context.getColor(R.color.locationbar_status_offline_color_light);
}
if (brandedColorScheme == BrandedColorScheme.INCOGNITO) {
return context.getColor(R.color.locationbar_status_offline_color_incognito);
}
return context.getColor(R.color.default_text_color_secondary_list);
}
/**
* Wraps the context if necessary to force dark resources for incognito.
*
* @param context The {@link Context} to be wrapped.
* @param brandedColorScheme Current color scheme.
* @return Context with resources appropriate to the {@link BrandedColorScheme}.
*/
private static Context maybeWrapContext(
Context context, @BrandedColorScheme int brandedColorScheme) {
// Only wraps the context in case of incognito.
if (brandedColorScheme == BrandedColorScheme.INCOGNITO) {
return NightModeUtils.wrapContextWithNightModeConfig(
context, R.style.Theme_Chromium_TabbedMode, /*nightMode=*/true);
}
return context;
}
/**
* Resolves the attribute based on the current theme.
*
* @param context The {@link Context} used to retrieve resources.
* @param attributeResId Resource ID of the attribute to resolve.
* @return Resource ID of the expected drawable.
*/
private static @DrawableRes int resolveAttributeToDrawableRes(
Context context, int attributeResId) {
TypedValue themeRes = new TypedValue();
context.getTheme().resolveAttribute(attributeResId, themeRes, true);
return themeRes.resourceId;
}
/** Gets the margin, in pixels, on either side of an omnibox suggestion. */
public static @Px int getSideSpacing(@NonNull Context context) {
return context.getResources().getDimensionPixelSize(
selectMarginDimen(R.dimen.omnibox_suggestion_side_spacing,
R.dimen.omnibox_suggestion_side_spacing_smaller,
R.dimen.omnibox_suggestion_side_spacing_smallest));
}
/** Gets the start padding for an omnibox suggestion's decoration icon. */
public static @Px int getIconStartPadding(Context context) {
return context.getResources().getDimensionPixelSize(
selectMarginDimen(R.dimen.omnibox_suggestion_24dp_icon_margin_start_modern,
R.dimen.omnibox_suggestion_24dp_icon_margin_start_modern,
R.dimen.omnibox_suggestion_24dp_icon_margin_start));
}
/** Gets the start padding for a large omnibox suggestion decoration icon. */
public static @Px int getLargeIconStartPadding(Context context) {
return context.getResources().getDimensionPixelSize(
selectMarginDimen(R.dimen.omnibox_suggestion_36dp_icon_margin_start_smaller,
R.dimen.omnibox_suggestion_36dp_icon_margin_start_smallest,
R.dimen.omnibox_suggestion_36dp_icon_margin_start));
}
/** Gets the end padding for a large omnibox suggestion decoration icon. */
public static @Px int getLargeIconEndPadding(Context context) {
return context.getResources().getDimensionPixelSize(
selectMarginDimen(R.dimen.omnibox_suggestion_36dp_icon_margin_end_smaller,
R.dimen.omnibox_suggestion_36dp_icon_margin_end_smallest,
R.dimen.omnibox_suggestion_36dp_icon_margin_end));
}
/** Get the top margin for a suggestion that is the beginning of a group. */
public static int getSuggestionGroupTopMargin(Context context) {
return context.getResources().getDimensionPixelSize(
selectMarginDimen(R.dimen.omnibox_suggestion_group_vertical_margin,
R.dimen.omnibox_suggestion_group_vertical_smaller_margin,
R.dimen.omnibox_suggestion_group_vertical_smallest_margin));
}
/** Get the top padding for the MV carousel. */
public static @Px int getCarouselTopPadding(Context context) {
if (OmniboxFeatures.shouldShowSmallerMargins()) {
return 0;
}
int topPadding = context.getResources().getDimensionPixelSize(
R.dimen.omnibox_carousel_suggestion_padding);
if (OmniboxFeatures.shouldShowModernizeVisualUpdate(context)) {
topPadding -= context.getResources().getDimensionPixelSize(
R.dimen.tile_view_icon_background_margin_top_modern);
}
return topPadding;
}
/** Get the bottom padding for the MV carousel. */
public static @Px int getCarouselBottomPadding(Context context) {
if (OmniboxFeatures.shouldShowSmallerMargins()) {
return 0;
}
@DimenRes
int dimenRes = OmniboxFeatures.shouldShowModernizeVisualUpdate(context)
? R.dimen.omnibox_carousel_suggestion_small_bottom_padding
: R.dimen.omnibox_carousel_suggestion_padding;
return context.getResources().getDimensionPixelSize(dimenRes);
}
/** Get the top margin for first suggestion in the omnibox with "active color" enabled. */
public static int getActiveOmniboxTopSmallMargin(Context context) {
return context.getResources().getDimensionPixelSize(
selectMarginDimen(R.dimen.omnibox_suggestion_list_active_top_small_margin,
R.dimen.omnibox_suggestion_list_active_top_smaller_margin,
R.dimen.omnibox_suggestion_list_active_top_small_margin));
}
/** Gets the start padding for a header suggestion. */
public static int getHeaderStartPadding(Context context) {
return context.getResources().getDimensionPixelSize(
selectMarginDimen(R.dimen.omnibox_suggestion_header_padding_start_modern,
R.dimen.omnibox_suggestion_header_padding_start_modern_smaller,
R.dimen.omnibox_suggestion_header_padding_start_modern_smallest));
}
/** */
public static @DimenRes int selectMarginDimen(
@DimenRes int regular, @DimenRes int smaller, @DimenRes int smallest) {
if (OmniboxFeatures.shouldShowSmallestMargins()) {
return smallest;
} else if (OmniboxFeatures.shouldShowSmallerMargins()) {
return smaller;
}
return regular;
}
}