blob: 494817b46457d680c961fc98ac4ca0a4c99f1250 [file] [log] [blame]
// Copyright 2015 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.components.browser_ui.site_settings;
import static org.chromium.components.browser_ui.settings.SearchUtils.handleSearchNavigation;
import static org.chromium.components.browser_ui.site_settings.WebsitePreferenceBridge.SITE_WILDCARD;
import static org.chromium.components.content_settings.PrefNames.COOKIE_CONTROLS_MODE;
import static org.chromium.components.content_settings.PrefNames.DESKTOP_SITE_WINDOW_SETTING_ENABLED;
import static org.chromium.components.content_settings.PrefNames.ENABLE_QUIET_NOTIFICATION_PERMISSION_UI;
import static org.chromium.components.content_settings.PrefNames.NOTIFICATIONS_VIBRATE_ENABLED;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Browser;
import android.provider.Settings;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.style.ForegroundColorSpan;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.ColorInt;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.browser.customtabs.CustomTabsIntent;
import androidx.preference.Preference;
import androidx.preference.Preference.OnPreferenceChangeListener;
import androidx.preference.Preference.OnPreferenceClickListener;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceManager.OnPreferenceTreeClickListener;
import androidx.preference.PreferenceScreen;
import androidx.recyclerview.widget.RecyclerView;
import org.chromium.base.ContextUtils;
import org.chromium.base.IntentUtils;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.build.annotations.UsedByReflection;
import org.chromium.components.browser_ui.modaldialog.AppModalPresenter;
import org.chromium.components.browser_ui.settings.CardPreference;
import org.chromium.components.browser_ui.settings.ChromeBaseCheckBoxPreference;
import org.chromium.components.browser_ui.settings.ChromeBasePreference;
import org.chromium.components.browser_ui.settings.ChromeSwitchPreference;
import org.chromium.components.browser_ui.settings.CustomDividerFragment;
import org.chromium.components.browser_ui.settings.ExpandablePreferenceGroup;
import org.chromium.components.browser_ui.settings.FragmentSettingsLauncher;
import org.chromium.components.browser_ui.settings.ManagedPreferenceDelegate;
import org.chromium.components.browser_ui.settings.ManagedPreferencesUtils;
import org.chromium.components.browser_ui.settings.SearchUtils;
import org.chromium.components.browser_ui.settings.SettingsLauncher;
import org.chromium.components.browser_ui.settings.SettingsUtils;
import org.chromium.components.browser_ui.site_settings.AddExceptionPreference.SiteAddedCallback;
import org.chromium.components.browser_ui.site_settings.AutoDarkMetrics.AutoDarkSettingsChangeSource;
import org.chromium.components.browser_ui.styles.SemanticColorUtils;
import org.chromium.components.browser_ui.util.TraceEventVectorDrawableCompat;
import org.chromium.components.browser_ui.widget.RadioButtonWithDescription;
import org.chromium.components.browser_ui.widget.RadioButtonWithDescriptionLayout;
import org.chromium.components.content_settings.ContentSettingValues;
import org.chromium.components.content_settings.ContentSettingsType;
import org.chromium.components.content_settings.CookieControlsMode;
import org.chromium.components.embedder_support.util.UrlUtilities;
import org.chromium.components.prefs.PrefService;
import org.chromium.components.user_prefs.UserPrefs;
import org.chromium.content_public.browser.BrowserContextHandle;
import org.chromium.content_public.browser.ContentFeatureList;
import org.chromium.content_public.browser.ContentFeatureMap;
import org.chromium.ui.modaldialog.DialogDismissalCause;
import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.ui.modaldialog.ModalDialogManager.ModalDialogType;
import org.chromium.ui.modaldialog.ModalDialogProperties;
import org.chromium.ui.modaldialog.ModalDialogProperties.ButtonType;
import org.chromium.ui.modaldialog.ModalDialogProperties.Controller;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.text.NoUnderlineClickableSpan;
import org.chromium.ui.text.SpanApplier;
import org.chromium.ui.text.SpanApplier.SpanInfo;
import org.chromium.ui.widget.Toast;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
/**
* Shows a list of sites in a particular Site Settings category. For example, this could show all
* the websites with microphone permissions. When the user selects a site, SingleWebsiteSettings is
* launched to allow the user to see or modify the settings for that particular website.
*/
@UsedByReflection("site_settings_preferences.xml")
public class SingleCategorySettings extends BaseSiteSettingsFragment
implements OnPreferenceChangeListener,
OnPreferenceClickListener,
SiteAddedCallback,
OnPreferenceTreeClickListener,
FragmentSettingsLauncher,
TriStateCookieSettingsPreference.OnCookiesDetailsRequested,
CustomDividerFragment,
WebsitePreference.OnStorageAccessWebsiteDetailsRequested {
@IntDef({
GlobalToggleLayout.BINARY_TOGGLE,
GlobalToggleLayout.TRI_STATE_TOGGLE,
GlobalToggleLayout.TRI_STATE_COOKIE_TOGGLE
})
@Retention(RetentionPolicy.SOURCE)
private @interface GlobalToggleLayout {
int BINARY_TOGGLE = 0;
int TRI_STATE_TOGGLE = 1;
int TRI_STATE_COOKIE_TOGGLE = 2;
}
// The key to use to pass which category this preference should display,
// e.g. Location/Popups/All sites (if blank).
public static final String EXTRA_CATEGORY = "category";
public static final String EXTRA_TITLE = "title";
public static final String POLICY = "policy";
private SettingsLauncher mSettingsLauncher;
@Override
public void setSettingsLauncher(SettingsLauncher settingsLauncher) {
mSettingsLauncher = settingsLauncher;
}
/**
* If present, the list of websites will be filtered by domain using {@link
* UrlUtilities#getDomainAndRegistry}.
*/
public static final String EXTRA_SELECTED_DOMAINS = "selected_domains";
// The list that contains preferences.
private RecyclerView mListView;
// The item for searching the list of items.
private MenuItem mSearchItem;
// The Site Settings Category we are showing.
private SiteSettingsCategory mCategory;
// If not blank, represents a substring to use to search for site names.
private String mSearch;
// Whether to group by allowed/blocked list.
private boolean mGroupByAllowBlock;
// Whether the Blocked list should be shown expanded.
private boolean mBlockListExpanded;
// Whether the Allowed list should be shown expanded.
private boolean mAllowListExpanded = true;
// Whether the Managed list should be shown expanded.
private boolean mManagedListExpanded;
// Whether this is the first time this screen is shown.
private boolean mIsInitialRun = true;
// The number of sites that are on the Allowed list.
private int mAllowedSiteCount;
// The websites that are currently displayed to the user.
private List<WebsitePreference> mWebsites;
// Whether tri-state ContentSetting is required.
private @GlobalToggleLayout int mGlobalToggleLayout = GlobalToggleLayout.BINARY_TOGGLE;
// The "notifications_quiet_ui" preference to allow hiding/showing it.
private ChromeBaseCheckBoxPreference mNotificationsQuietUiPref;
// The three-way settings pref for notification and geolocation permissions.
private TriStatePermissionPreference mNotificationsTriStatePref;
private TriStatePermissionPreference mLocationTriStatePref;
// The "desktop_site_window" preference to allow hiding/showing it.
private ChromeBaseCheckBoxPreference mDesktopSiteWindowPref;
private CardPreference mCardPreference;
@Nullable private Set<String> mSelectedDomains;
private static final String TP_LEARN_MORE_URL =
"https://support.google.com/chrome/?p=tracking_protection";
@Override
public void onCookiesDetailsRequested(@CookieControlsMode int cookieSettingsState) {
Bundle fragmentArgs = new Bundle();
fragmentArgs.putInt(FPSCookieSettings.EXTRA_COOKIE_PAGE_STATE, cookieSettingsState);
mSettingsLauncher.launchSettingsActivity(
getActivity(), FPSCookieSettings.class, fragmentArgs);
}
@Override
public void onStorageAccessWebsiteDetailsRequested(WebsitePreference website) {
Bundle fragmentArgs = new Bundle();
fragmentArgs.putSerializable(
StorageAccessSubpageSettings.EXTRA_STORAGE_ACCESS_STATE, website.site());
fragmentArgs.putBoolean(
StorageAccessSubpageSettings.EXTRA_ALLOWED, !isOnBlockList(website));
mSettingsLauncher.launchSettingsActivity(
getActivity(), StorageAccessSubpageSettings.class, fragmentArgs);
}
// Note: these values must match the SiteLayout enum in enums.xml.
@IntDef({SiteLayout.MOBILE, SiteLayout.DESKTOP})
@Retention(RetentionPolicy.SOURCE)
public @interface SiteLayout {
int MOBILE = 0;
int DESKTOP = 1;
int NUM_ENTRIES = 2;
}
// Keys for common ContentSetting toggle for categories. These toggles are mutually
// exclusive: a category should only show one of them, at most.
public static final String CARD_PREFERENCE_KEY = "card_preference";
public static final String BINARY_TOGGLE_KEY = "binary_toggle";
public static final String TRI_STATE_TOGGLE_KEY = "tri_state_toggle";
public static final String TRI_STATE_COOKIE_TOGGLE = "tri_state_cookie_toggle";
// Keys for category-specific preferences (toggle, link, button etc.), dynamically shown.
public static final String NOTIFICATIONS_VIBRATE_TOGGLE_KEY = "notifications_vibrate";
public static final String NOTIFICATIONS_QUIET_UI_TOGGLE_KEY = "notifications_quiet_ui";
public static final String NOTIFICATIONS_TRI_STATE_PREF_KEY = "notifications_tri_state_toggle";
public static final String LOCATION_TRI_STATE_PREF_KEY = "location_tri_state_toggle";
public static final String DESKTOP_SITE_WINDOW_TOGGLE_KEY = "desktop_site_window";
public static final String EXPLAIN_PROTECTED_MEDIA_KEY = "protected_content_learn_more";
public static final String ADD_EXCEPTION_KEY = "add_exception";
public static final String INFO_TEXT_KEY = "info_text";
public static final String ANTI_ABUSE_WHEN_ON_HEADER = "anti_abuse_when_on_header";
public static final String ANTI_ABUSE_WHEN_ON_SECTION_ONE = "anti_abuse_when_on_section_one";
public static final String ANTI_ABUSE_WHEN_ON_SECTION_TWO = "anti_abuse_when_on_section_two";
public static final String ANTI_ABUSE_WHEN_ON_SECTION_THREE =
"anti_abuse_when_on_section_three";
public static final String ANTI_ABUSE_THINGS_TO_CONSIDER_HEADER =
"anti_abuse_things_to_consider_header";
public static final String ANTI_ABUSE_THINGS_TO_CONSIDER_SECTION_ONE =
"anti_abuse_things_to_consider_section_one";
// Keys for Allowed/Blocked preference groups/headers.
public static final String ALLOWED_GROUP = "allowed_group";
public static final String BLOCKED_GROUP = "blocked_group";
public static final String MANAGED_GROUP = "managed_group";
private class ResultsPopulator implements WebsitePermissionsFetcher.WebsitePermissionsCallback {
@Override
public void onWebsitePermissionsAvailable(Collection<Website> sites) {
// This method may be called after the activity has been destroyed.
// In that case, bail out.
if (getActivity() == null) return;
mWebsites = null;
resetList();
sites = applyFilters(sites);
int chooserDataType = mCategory.getObjectChooserDataType();
if (chooserDataType == -1) {
addWebsites(sites);
} else {
addChosenObjects(sites);
}
}
private Collection<Website> applyFilters(Collection<Website> sites) {
@SiteSettingsCategory.Type int type = mCategory.getType();
if (type == SiteSettingsCategory.Type.THIRD_PARTY_COOKIES
|| type == SiteSettingsCategory.Type.SITE_DATA) {
Collection<Website> filtered = new ArrayList<>();
boolean isThirdPartyCategory =
type == SiteSettingsCategory.Type.THIRD_PARTY_COOKIES;
for (Website site : sites) {
if (site.representsThirdPartiesOnSite() == isThirdPartyCategory) {
filtered.add(site);
}
}
return filtered;
}
return sites;
}
}
/** Called by common settings code to determine if a Preference is managed. */
private class SingleCategoryManagedPreferenceDelegate
extends ForwardingManagedPreferenceDelegate {
SingleCategoryManagedPreferenceDelegate(ManagedPreferenceDelegate base) {
super(base);
}
@Override
public boolean isPreferenceControlledByPolicy(Preference preference) {
// TODO(bauerb): Align the ManagedPreferenceDelegate and
// SiteSettingsCategory interfaces better to avoid this indirection.
return mCategory.isManaged() && !mCategory.isManagedByCustodian();
}
@Override
public boolean isPreferenceControlledByCustodian(Preference preference) {
return mCategory.isManagedByCustodian();
}
}
private void getInfoForOrigins() {
if (!mCategory.enabledInAndroid(getActivity())) {
// No need to fetch any data if we're not going to show it, but we do need to update
// the global toggle to reflect updates in Android settings (e.g. Location).
resetList();
return;
}
WebsitePermissionsFetcher fetcher =
new WebsitePermissionsFetcher(getSiteSettingsDelegate(), false);
fetcher.fetchPreferencesForCategory(mCategory, new ResultsPopulator());
}
/**
* Returns whether a website is on the Blocked list for the category currently showing.
*
* @param website The website to check.
*/
private boolean isOnBlockList(WebsitePreference website) {
BrowserContextHandle browserContextHandle =
getSiteSettingsDelegate().getBrowserContextHandle();
@ContentSettingValues
Integer contentSetting =
website.site()
.getContentSetting(
browserContextHandle, mCategory.getContentSettingsType());
if (contentSetting != null) {
return ContentSettingValues.BLOCK == contentSetting;
}
return false;
}
/**
* Update the Category Header for the Allowed list.
*
* @param numAllowed The number of sites that are on the Allowed list
* @param toggleValue The value the global toggle will have once precessing ends.
*/
private void updateAllowedHeader(int numAllowed, boolean toggleValue) {
ExpandablePreferenceGroup allowedGroup =
getPreferenceScreen().findPreference(ALLOWED_GROUP);
if (allowedGroup == null) return;
if (numAllowed == 0) {
if (allowedGroup != null) getPreferenceScreen().removePreference(allowedGroup);
return;
}
if (!mGroupByAllowBlock) return;
int resourceId;
if (mCategory.getType() == SiteSettingsCategory.Type.REQUEST_DESKTOP_SITE) {
// REQUEST_DESKTOP_SITE has its own Allowed list header.
resourceId = R.string.website_settings_allowed_group_heading_request_desktop_site;
} else if (toggleValue) {
resourceId = R.string.website_settings_allowed_group_heading;
} else {
// When the toggle is set to Blocked, the Allowed list header should read 'Exceptions',
// not 'Allowed' (because it shows exceptions from the rule).
resourceId = R.string.website_settings_exceptions_group_heading;
}
allowedGroup.setTitle(getHeaderTitle(resourceId, numAllowed));
allowedGroup.setExpanded(mAllowListExpanded);
}
private void updateBlockedHeader(int numBlocked) {
ExpandablePreferenceGroup blockedGroup =
getPreferenceScreen().findPreference(BLOCKED_GROUP);
if (numBlocked == 0) {
if (blockedGroup != null) getPreferenceScreen().removePreference(blockedGroup);
return;
}
if (!mGroupByAllowBlock) return;
// Set the title and arrow icons for the header.
int resourceId;
if (mCategory.getType() == SiteSettingsCategory.Type.SOUND) {
resourceId = R.string.website_settings_blocked_group_heading_sound;
} else if (mCategory.getType() == SiteSettingsCategory.Type.REQUEST_DESKTOP_SITE) {
resourceId = R.string.website_settings_blocked_group_heading_request_desktop_site;
} else {
resourceId = R.string.website_settings_blocked_group_heading;
}
blockedGroup.setTitle(getHeaderTitle(resourceId, numBlocked));
blockedGroup.setExpanded(mBlockListExpanded);
}
private void updateManagedHeader(int numManaged) {
ExpandablePreferenceGroup managedGroup =
getPreferenceScreen().findPreference(MANAGED_GROUP);
if (numManaged == 0) {
if (managedGroup != null) getPreferenceScreen().removePreference(managedGroup);
return;
}
if (!mGroupByAllowBlock) return;
// Set the title and arrow icons for the header.
int resourceId = R.string.website_settings_managed_group_heading;
managedGroup.setTitle(getHeaderTitle(resourceId, numManaged));
managedGroup.setExpanded(mManagedListExpanded);
}
private CharSequence getHeaderTitle(int resourceId, int count) {
SpannableStringBuilder spannable = new SpannableStringBuilder(getString(resourceId));
String prefCount = String.format(Locale.getDefault(), " - %d", count);
spannable.append(prefCount);
// Color the first part of the title blue.
ForegroundColorSpan blueSpan =
new ForegroundColorSpan(
SemanticColorUtils.getDefaultTextColorAccent1(getContext()));
spannable.setSpan(
blueSpan,
0,
spannable.length() - prefCount.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
// Gray out the total count of items.
final @ColorInt int gray = SemanticColorUtils.getDefaultTextColorSecondary(getContext());
spannable.setSpan(
new ForegroundColorSpan(gray),
spannable.length() - prefCount.length(),
spannable.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return spannable;
}
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Read which category we should be showing.
BrowserContextHandle browserContextHandle =
getSiteSettingsDelegate().getBrowserContextHandle();
if (getArguments() != null) {
mCategory =
SiteSettingsCategory.createFromPreferenceKey(
browserContextHandle, getArguments().getString(EXTRA_CATEGORY, ""));
}
if (mCategory.getType() == SiteSettingsCategory.Type.ALL_SITES
|| mCategory.getType() == SiteSettingsCategory.Type.USE_STORAGE
|| mCategory.getType() == SiteSettingsCategory.Type.ZOOM) {
throw new IllegalArgumentException("Use AllSiteSettings instead.");
}
int contentType = mCategory.getContentSettingsType();
if (mCategory.getType() == SiteSettingsCategory.Type.THIRD_PARTY_COOKIES) {
mGlobalToggleLayout = GlobalToggleLayout.TRI_STATE_COOKIE_TOGGLE;
} else if (WebsitePreferenceBridge.requiresTriStateContentSetting(contentType)) {
mGlobalToggleLayout = GlobalToggleLayout.TRI_STATE_TOGGLE;
}
ViewGroup view = (ViewGroup) super.onCreateView(inflater, container, savedInstanceState);
mListView = getListView();
// Disable animations of preference changes.
mListView.setItemAnimator(null);
return view;
}
@Override
public boolean hasDivider() {
// Remove dividers between preferences.
return false;
}
/** Returns the category being displayed. For testing. */
public SiteSettingsCategory getCategoryForTest() {
return mCategory;
}
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
// Handled in onActivityCreated. Moving the addPreferencesFromResource call up to here
// causes animation jank (crbug.com/985734).
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
SettingsUtils.addPreferencesFromResource(this, R.xml.website_preferences);
String title = getArguments().getString(EXTRA_TITLE);
if (title != null) getActivity().setTitle(title);
mSelectedDomains =
getArguments().containsKey(EXTRA_SELECTED_DOMAINS)
? new HashSet<>(getArguments().getStringArrayList(EXTRA_SELECTED_DOMAINS))
: null;
configureGlobalToggles();
if (mCategory.getType() == SiteSettingsCategory.Type.REQUEST_DESKTOP_SITE) {
RecordUserAction.record("DesktopSiteContentSetting.SettingsPage.Entered");
getSiteSettingsDelegate().notifyRequestDesktopSiteSettingsPageOpened();
}
setHasOptionsMenu(true);
super.onActivityCreated(savedInstanceState);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
menu.clear();
inflater.inflate(R.menu.website_preferences_menu, menu);
mSearchItem = menu.findItem(R.id.search);
SearchUtils.initializeSearchView(
mSearchItem,
mSearch,
getActivity(),
(query) -> {
boolean queryHasChanged =
mSearch == null
? query != null && !query.isEmpty()
: !mSearch.equals(query);
mSearch = query;
if (queryHasChanged) getInfoForOrigins();
});
if (getSiteSettingsDelegate().isHelpAndFeedbackEnabled()) {
MenuItem help =
menu.add(
Menu.NONE,
R.id.menu_id_site_settings_help,
Menu.NONE,
R.string.menu_help);
help.setIcon(
TraceEventVectorDrawableCompat.create(
getResources(),
R.drawable.ic_help_and_feedback,
getContext().getTheme()));
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.menu_id_site_settings_help) {
if (mCategory.getType() == SiteSettingsCategory.Type.PROTECTED_MEDIA) {
getSiteSettingsDelegate()
.launchProtectedContentHelpAndFeedbackActivity(getActivity());
} else {
getSiteSettingsDelegate().launchSettingsHelpAndFeedbackActivity(getActivity());
}
return true;
}
if (handleSearchNavigation(item, mSearchItem, mSearch, getActivity())) {
boolean queryHasChanged = mSearch != null && !mSearch.isEmpty();
mSearch = null;
if (queryHasChanged) getInfoForOrigins();
return true;
}
return false;
}
@Override
public boolean onPreferenceTreeClick(Preference preference) {
// Do not show the toast if the System Location setting is disabled.
if (getPreferenceScreen().findPreference(BINARY_TOGGLE_KEY) != null
&& mCategory.isManaged()) {
showManagedToast();
return false;
}
if (preference instanceof WebsitePreference) {
WebsitePreference websitePreference = (WebsitePreference) preference;
if (websitePreference.isManaged()) {
showManagedToast();
return false;
}
if (websitePreference.getParent().getKey().equals(MANAGED_GROUP)) {
websitePreference.setFragment(SingleWebsiteSettings.class.getName());
websitePreference.putSiteAddressIntoExtras(
SingleWebsiteSettings.EXTRA_SITE_ADDRESS);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
&& mCategory.getType() == SiteSettingsCategory.Type.NOTIFICATIONS) {
// In Android O+, users can manage Notification channels through App Info. If this
// is the case we send the user directly to Android Settings to modify the
// Notification exception.
String channelId =
getSiteSettingsDelegate()
.getChannelIdForOrigin(
websitePreference.site().getAddress().getOrigin());
Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
intent.putExtra(Settings.EXTRA_CHANNEL_ID, channelId);
intent.putExtra(
Settings.EXTRA_APP_PACKAGE, preference.getContext().getPackageName());
startActivityForResult(
intent, SingleWebsiteSettings.REQUEST_CODE_NOTIFICATION_CHANNEL_SETTINGS);
} else {
buildPreferenceDialog(websitePreference.site()).show();
if (mCategory.getType() == SiteSettingsCategory.Type.REQUEST_DESKTOP_SITE) {
RecordUserAction.record(
"DesktopSiteContentSetting.SettingsPage.SiteException.Opened");
}
}
}
return super.onPreferenceTreeClick(preference);
}
// OnPreferenceChangeListener:
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
BrowserContextHandle browserContextHandle =
getSiteSettingsDelegate().getBrowserContextHandle();
PrefService prefService = UserPrefs.get(browserContextHandle);
if (BINARY_TOGGLE_KEY.equals(preference.getKey())) {
assert !mCategory.isManaged();
boolean toggleValue = (boolean) newValue;
@SiteSettingsCategory.Type int type = mCategory.getType();
if (type == SiteSettingsCategory.Type.SITE_DATA && !toggleValue) {
showDisableSiteDataConfirmationDialog();
return false;
}
WebsitePreferenceBridge.setCategoryEnabled(
browserContextHandle,
SiteSettingsCategory.contentSettingsType(type),
toggleValue);
if (type == SiteSettingsCategory.Type.NOTIFICATIONS) {
updateNotificationsSecondaryControls();
} else if (type == SiteSettingsCategory.Type.DEVICE_LOCATION) {
updateLocationSecondaryControls();
} else if (type == SiteSettingsCategory.Type.AUTO_DARK_WEB_CONTENT) {
AutoDarkMetrics.recordAutoDarkSettingsChangeSource(
AutoDarkSettingsChangeSource.SITE_SETTINGS_GLOBAL, toggleValue);
} else if (type == SiteSettingsCategory.Type.REQUEST_DESKTOP_SITE) {
recordSiteLayoutChanged(toggleValue);
updateDesktopSiteWindowSetting();
}
getInfoForOrigins();
} else if (TRI_STATE_TOGGLE_KEY.equals(preference.getKey())) {
@ContentSettingValues int setting = (int) newValue;
WebsitePreferenceBridge.setDefaultContentSetting(
browserContextHandle, mCategory.getContentSettingsType(), setting);
getInfoForOrigins();
} else if (TRI_STATE_COOKIE_TOGGLE.equals(preference.getKey())) {
setThirdPartyCookieSettingsPreference((int) newValue);
getInfoForOrigins();
} else if (NOTIFICATIONS_VIBRATE_TOGGLE_KEY.equals(preference.getKey())) {
prefService.setBoolean(NOTIFICATIONS_VIBRATE_ENABLED, (boolean) newValue);
} else if (NOTIFICATIONS_QUIET_UI_TOGGLE_KEY.equals(preference.getKey())) {
if ((boolean) newValue) {
prefService.setBoolean(ENABLE_QUIET_NOTIFICATION_PERMISSION_UI, true);
} else {
// Clear the pref so if the default changes later the user will get the new default.
prefService.clearPref(ENABLE_QUIET_NOTIFICATION_PERMISSION_UI);
}
} else if (DESKTOP_SITE_WINDOW_TOGGLE_KEY.equals(preference.getKey())) {
prefService.setBoolean(DESKTOP_SITE_WINDOW_SETTING_ENABLED, (boolean) newValue);
RecordHistogram.recordBooleanHistogram(
"Android.RequestDesktopSite.WindowSettingChanged", (boolean) newValue);
}
return true;
}
private void showDisableSiteDataConfirmationDialog() {
assert mCategory.getType() == SiteSettingsCategory.Type.SITE_DATA;
var manager =
new ModalDialogManager(new AppModalPresenter(getContext()), ModalDialogType.APP);
var controller =
new Controller() {
@Override
public void onClick(PropertyModel model, @ButtonType int buttonType) {
switch (buttonType) {
case ButtonType.POSITIVE:
WebsitePreferenceBridge.setCategoryEnabled(
getSiteSettingsDelegate().getBrowserContextHandle(),
mCategory.getContentSettingsType(),
false);
getInfoForOrigins();
manager.dismissDialog(
model, DialogDismissalCause.POSITIVE_BUTTON_CLICKED);
break;
case ButtonType.NEGATIVE:
manager.dismissDialog(
model, DialogDismissalCause.NEGATIVE_BUTTON_CLICKED);
break;
default:
assert false;
break;
}
}
@Override
public void onDismiss(PropertyModel model, int dismissalCause) {}
};
var resources = getContext().getResources();
var builder =
new PropertyModel.Builder(ModalDialogProperties.ALL_KEYS)
.with(ModalDialogProperties.CONTROLLER, controller)
.with(
ModalDialogProperties.TITLE,
resources,
R.string.website_settings_site_data_page_block_confirm_dialog_title)
.with(
ModalDialogProperties.MESSAGE_PARAGRAPH_1,
resources.getString(
R.string
.website_settings_site_data_page_block_confirm_dialog_description))
.with(
ModalDialogProperties.POSITIVE_BUTTON_TEXT,
resources,
R.string
.website_settings_site_data_page_block_confirm_dialog_confirm_button)
.with(
ModalDialogProperties.BUTTON_STYLES,
ModalDialogProperties.ButtonStyles.PRIMARY_FILLED_NEGATIVE_OUTLINE)
.with(
ModalDialogProperties.NEGATIVE_BUTTON_TEXT,
resources,
R.string
.website_settings_site_data_page_block_confirm_dialog_cancel_button);
var model = builder.build();
manager.showDialog(model, ModalDialogType.APP);
}
private void setThirdPartyCookieSettingsPreference(@CookieControlsMode int mode) {
assert mCategory.getType() == SiteSettingsCategory.Type.THIRD_PARTY_COOKIES;
getSiteSettingsDelegate().dismissPrivacySandboxSnackbar();
// Display the Privacy Sandbox snackbar whenever third-party cookies are blocked.
if (mode == CookieControlsMode.BLOCK_THIRD_PARTY) {
getSiteSettingsDelegate().maybeDisplayPrivacySandboxSnackbar();
}
PrefService prefService =
UserPrefs.get(getSiteSettingsDelegate().getBrowserContextHandle());
prefService.setInteger(COOKIE_CONTROLS_MODE, mode);
}
private String getAddExceptionDialogMessage() {
BrowserContextHandle browserContextHandle =
getSiteSettingsDelegate().getBrowserContextHandle();
int resource = 0;
switch (mCategory.getType()) {
case SiteSettingsCategory.Type.AUTOMATIC_DOWNLOADS:
resource = R.string.website_settings_add_site_description_automatic_downloads;
break;
case SiteSettingsCategory.Type.BACKGROUND_SYNC:
resource = R.string.website_settings_add_site_description_background_sync;
break;
case SiteSettingsCategory.Type.JAVASCRIPT:
resource =
WebsitePreferenceBridge.isCategoryEnabled(
browserContextHandle, ContentSettingsType.JAVASCRIPT)
? R.string.website_settings_add_site_description_javascript_block
: R.string.website_settings_add_site_description_javascript_allow;
break;
case SiteSettingsCategory.Type.SOUND:
resource =
WebsitePreferenceBridge.isCategoryEnabled(
browserContextHandle, ContentSettingsType.SOUND)
? R.string.website_settings_add_site_description_sound_block
: R.string.website_settings_add_site_description_sound_allow;
break;
case SiteSettingsCategory.Type.SITE_DATA:
resource =
WebsitePreferenceBridge.isCategoryEnabled(
browserContextHandle, ContentSettingsType.COOKIES)
? R.string
.website_settings_site_data_page_add_block_exception_description
: R.string
.website_settings_site_data_page_add_allow_exception_description;
break;
case SiteSettingsCategory.Type.THIRD_PARTY_COOKIES:
resource =
getCookieControlsMode() == CookieControlsMode.OFF
? R.string
.website_settings_third_party_cookies_page_add_block_exception_description
: R.string
.website_settings_third_party_cookies_page_add_allow_exception_description;
break;
case SiteSettingsCategory.Type.AUTO_DARK_WEB_CONTENT:
assert WebsitePreferenceBridge.isCategoryEnabled(
browserContextHandle, ContentSettingsType.AUTO_DARK_WEB_CONTENT);
resource = R.string.website_settings_add_site_description_auto_dark_block;
break;
case SiteSettingsCategory.Type.FEDERATED_IDENTITY_API:
resource =
WebsitePreferenceBridge.isCategoryEnabled(
browserContextHandle,
ContentSettingsType.FEDERATED_IDENTITY_API)
? R.string
.website_settings_add_site_description_federated_identity_block
: R.string
.website_settings_add_site_description_federated_identity_allow;
break;
case SiteSettingsCategory.Type.REQUEST_DESKTOP_SITE:
resource =
WebsitePreferenceBridge.isCategoryEnabled(
browserContextHandle,
ContentSettingsType.REQUEST_DESKTOP_SITE)
? R.string
.website_settings_blocked_group_heading_request_desktop_site
: R.string
.website_settings_allowed_group_heading_request_desktop_site;
break;
}
assert resource > 0;
return getString(resource);
}
// OnPreferenceClickListener:
@Override
public boolean onPreferenceClick(Preference preference) {
if (ALLOWED_GROUP.equals(preference.getKey())) {
mAllowListExpanded = !mAllowListExpanded;
} else if (BLOCKED_GROUP.equals(preference.getKey())) {
mBlockListExpanded = !mBlockListExpanded;
} else {
mManagedListExpanded = !mManagedListExpanded;
}
getInfoForOrigins();
return true;
}
@Override
public void onResume() {
super.onResume();
if (mSearch == null && mSearchItem != null) {
SearchUtils.clearSearch(mSearchItem, getActivity());
mSearch = null;
}
getInfoForOrigins();
}
// AddExceptionPreference.SiteAddedCallback:
@Override
public void onAddSite(String primaryPattern, String secondaryPattern) {
BrowserContextHandle browserContextHandle =
getSiteSettingsDelegate().getBrowserContextHandle();
int setting = ContentSettingValues.DEFAULT;
switch (mGlobalToggleLayout) {
case GlobalToggleLayout.TRI_STATE_COOKIE_TOGGLE:
setting =
getCookieControlsMode() == CookieControlsMode.OFF
? ContentSettingValues.BLOCK
: ContentSettingValues.ALLOW;
break;
case GlobalToggleLayout.TRI_STATE_TOGGLE:
case GlobalToggleLayout.BINARY_TOGGLE:
setting =
(WebsitePreferenceBridge.isCategoryEnabled(
browserContextHandle, mCategory.getContentSettingsType()))
? ContentSettingValues.BLOCK
: ContentSettingValues.ALLOW;
break;
default:
assert false;
}
WebsitePreferenceBridge.setContentSettingCustomScope(
browserContextHandle,
mCategory.getContentSettingsType(),
primaryPattern,
secondaryPattern,
setting);
String hostname = primaryPattern.equals(SITE_WILDCARD) ? secondaryPattern : primaryPattern;
Toast.makeText(
getContext(),
getContext().getString(R.string.website_settings_add_site_toast, hostname),
Toast.LENGTH_SHORT)
.show();
getInfoForOrigins();
if (mCategory.getType() == SiteSettingsCategory.Type.SOUND) {
if (setting == ContentSettingValues.BLOCK) {
RecordUserAction.record("SoundContentSetting.MuteBy.PatternException");
} else {
RecordUserAction.record("SoundContentSetting.UnmuteBy.PatternException");
}
}
DesktopSiteMetrics.recordDesktopSiteSettingsManuallyAdded(
mCategory.getType(), setting, hostname);
}
/** Reset the preference screen an initialize it again. */
private void resetList() {
// This will remove the combo box at the top and all the sites listed below it.
getPreferenceScreen().removeAll();
// And this will add the filter preference back (combo box).
SettingsUtils.addPreferencesFromResource(this, R.xml.website_preferences);
configureGlobalToggles();
BrowserContextHandle browserContextHandle =
getSiteSettingsDelegate().getBrowserContextHandle();
@ContentSettingsType.EnumType int type = mCategory.getContentSettingsType();
boolean allowSpecifyingExceptions = false;
switch (mCategory.getType()) {
case SiteSettingsCategory.Type.SOUND:
case SiteSettingsCategory.Type.JAVASCRIPT:
case SiteSettingsCategory.Type.SITE_DATA:
case SiteSettingsCategory.Type.FEDERATED_IDENTITY_API:
case SiteSettingsCategory.Type.REQUEST_DESKTOP_SITE:
allowSpecifyingExceptions = true;
break;
case SiteSettingsCategory.Type.BACKGROUND_SYNC:
case SiteSettingsCategory.Type.AUTOMATIC_DOWNLOADS:
allowSpecifyingExceptions =
!WebsitePreferenceBridge.isCategoryEnabled(browserContextHandle, type);
break;
case SiteSettingsCategory.Type.THIRD_PARTY_COOKIES:
allowSpecifyingExceptions = getCookieControlsMode() != CookieControlsMode.OFF;
break;
default:
break;
}
if (allowSpecifyingExceptions) {
getPreferenceScreen()
.addPreference(
new AddExceptionPreference(
getStyledContext(),
ADD_EXCEPTION_KEY,
getAddExceptionDialogMessage(),
mCategory,
this));
}
}
private boolean addWebsites(Collection<Website> sites) {
filterSelectedDomains(sites);
List<WebsitePreference> websites = new ArrayList<>();
ForwardingManagedPreferenceDelegate websiteDelegate = createWebsiteManagedPrefDelegate();
// Find origins matching the current search.
// Check if the source of the exception for each website is a policy
// to set the managed state needed for the UI.
for (Website site : sites) {
if (mSearch == null || mSearch.isEmpty() || site.getTitle().contains(mSearch)) {
WebsitePreference preference =
new WebsitePreference(
getStyledContext(), getSiteSettingsDelegate(), site, mCategory);
if (mCategory.getType() == SiteSettingsCategory.Type.STORAGE_ACCESS) {
preference.setStorageAccessSettingsPageListener(this);
}
websites.add(preference);
preference.setManagedPreferenceDelegate(websiteDelegate);
}
}
mAllowedSiteCount = 0;
if (websites.size() == 0 || !shouldAddExceptionsForCategory()) {
updateBlockedHeader(0);
updateAllowedHeader(0, true);
updateManagedHeader(0);
return false;
}
Collections.sort(websites);
int blocked = 0;
int managed = 0;
if (!mGroupByAllowBlock) {
// We're not grouping sites into Allowed/Blocked lists, so show all in order
// (will be alphabetical).
for (WebsitePreference website : websites) {
getPreferenceScreen().addPreference(website);
}
} else {
// Group sites into Allowed/Blocked lists.
PreferenceGroup allowedGroup = getPreferenceScreen().findPreference(ALLOWED_GROUP);
PreferenceGroup blockedGroup = getPreferenceScreen().findPreference(BLOCKED_GROUP);
PreferenceGroup managedGroup = getPreferenceScreen().findPreference(MANAGED_GROUP);
Set<String> delegatedOrigins =
mCategory.getType() == SiteSettingsCategory.Type.NOTIFICATIONS
? getSiteSettingsDelegate().getAllDelegatedNotificationOrigins()
: Collections.emptySet();
for (WebsitePreference website : websites) {
if (delegatedOrigins.contains(website.site().getAddress().getOrigin())) {
managedGroup.addPreference(website);
managed += 1;
} else if (isOnBlockList(website)) {
blockedGroup.addPreference(website);
blocked += 1;
} else {
allowedGroup.addPreference(website);
mAllowedSiteCount += 1;
}
}
// For the ads permission, the Allowed list should appear first. Default
// collapsed settings should not change.
if (mCategory.getType() == SiteSettingsCategory.Type.ADS) {
blockedGroup.setOrder(allowedGroup.getOrder() + 1);
}
// The default, when the lists are shown for the first time, is for the
// Blocked and Managed list to be collapsed and Allowed expanded -- because
// the data in the Allowed list is normally more useful than the data in
// the Blocked/Managed lists. A collapsed initial Blocked/Managed list works
// well *except* when there's nothing in the Allowed list because then
// there's only Blocked/Managed items to show and it doesn't make sense for
// those items to be hidden. So, in those cases (and only when the lists are
// shown for the first time) do we ignore the collapsed directive. The user
// can still collapse and expand the Blocked/Managed list at will.
if (mIsInitialRun) {
if (mAllowedSiteCount == 0) {
if (blocked == 0 && managed > 0) {
mManagedListExpanded = true;
} else {
mBlockListExpanded = true;
}
}
mIsInitialRun = false;
}
if (!mBlockListExpanded) blockedGroup.removeAll();
if (!mAllowListExpanded) allowedGroup.removeAll();
if (!mManagedListExpanded) managedGroup.removeAll();
}
mWebsites = websites;
updateBlockedHeader(blocked);
updateAllowedHeader(mAllowedSiteCount, !isBlocked());
updateManagedHeader(managed);
return websites.size() != 0;
}
private Context getStyledContext() {
return getPreferenceManager().getContext();
}
private void filterSelectedDomains(Collection<Website> websites) {
if (mSelectedDomains == null) {
return;
}
for (Iterator<Website> it = websites.iterator(); it.hasNext(); ) {
String domain =
UrlUtilities.getDomainAndRegistry(it.next().getAddress().getOrigin(), true);
if (!mSelectedDomains.contains(domain)) {
it.remove();
}
}
}
private boolean addChosenObjects(Collection<Website> sites) {
Map<String, Pair<ArrayList<ChosenObjectInfo>, ArrayList<Website>>> objects =
new HashMap<>();
// Find chosen objects matching the current search and collect the list of sites
// that have permission to access each.
for (Website site : sites) {
for (ChosenObjectInfo info : site.getChosenObjectInfo()) {
if (mSearch == null
|| mSearch.isEmpty()
|| info.getName().toLowerCase(Locale.getDefault()).contains(mSearch)) {
Pair<ArrayList<ChosenObjectInfo>, ArrayList<Website>> entry =
objects.get(info.getObject());
if (entry == null) {
entry =
Pair.create(
new ArrayList<ChosenObjectInfo>(),
new ArrayList<Website>());
objects.put(info.getObject(), entry);
}
entry.first.add(info);
entry.second.add(site);
}
}
}
updateBlockedHeader(0);
updateAllowedHeader(0, true);
updateManagedHeader(0);
for (Pair<ArrayList<ChosenObjectInfo>, ArrayList<Website>> entry : objects.values()) {
Preference preference = new Preference(getStyledContext());
Bundle extras = preference.getExtras();
extras.putInt(ChosenObjectSettings.EXTRA_CATEGORY, mCategory.getContentSettingsType());
extras.putString(EXTRA_TITLE, getActivity().getTitle().toString());
extras.putSerializable(ChosenObjectSettings.EXTRA_OBJECT_INFOS, entry.first);
extras.putSerializable(ChosenObjectSettings.EXTRA_SITES, entry.second);
preference.setIcon(
SettingsUtils.getTintedIcon(
getContext(),
ContentSettingsResources.getIcon(mCategory.getContentSettingsType())));
preference.setTitle(entry.first.get(0).getName());
preference.setFragment(ChosenObjectSettings.class.getCanonicalName());
getPreferenceScreen().addPreference(preference);
}
return objects.size() != 0;
}
private boolean isBlocked() {
switch (mGlobalToggleLayout) {
case GlobalToggleLayout.TRI_STATE_TOGGLE:
TriStateSiteSettingsPreference triStateToggle =
getPreferenceScreen().findPreference(TRI_STATE_TOGGLE_KEY);
return (triStateToggle.getCheckedSetting() == ContentSettingValues.BLOCK);
case GlobalToggleLayout.TRI_STATE_COOKIE_TOGGLE:
TriStateCookieSettingsPreference triStateCookieToggle =
getPreferenceScreen().findPreference(TRI_STATE_COOKIE_TOGGLE);
return triStateCookieToggle.getState() != CookieControlsMode.OFF;
case GlobalToggleLayout.BINARY_TOGGLE:
ChromeSwitchPreference binaryToggle =
getPreferenceScreen().findPreference(BINARY_TOGGLE_KEY);
if (binaryToggle != null) {
return !binaryToggle.isChecked();
}
break;
}
return false;
}
private void configureGlobalToggles() {
int contentType = mCategory.getContentSettingsType();
PreferenceScreen screen = getPreferenceScreen();
// Find all preferences on the current preference screen. Some preferences are
// not needed for the current category and will be removed in the steps below.
ChromeSwitchPreference binaryToggle = screen.findPreference(BINARY_TOGGLE_KEY);
TriStateSiteSettingsPreference triStateToggle = screen.findPreference(TRI_STATE_TOGGLE_KEY);
TriStateCookieSettingsPreference triStateCookieToggle =
screen.findPreference(TRI_STATE_COOKIE_TOGGLE);
Preference notificationsVibrate = screen.findPreference(NOTIFICATIONS_VIBRATE_TOGGLE_KEY);
mNotificationsQuietUiPref = screen.findPreference(NOTIFICATIONS_QUIET_UI_TOGGLE_KEY);
mNotificationsTriStatePref = screen.findPreference(NOTIFICATIONS_TRI_STATE_PREF_KEY);
mLocationTriStatePref = screen.findPreference(LOCATION_TRI_STATE_PREF_KEY);
mDesktopSiteWindowPref = screen.findPreference(DESKTOP_SITE_WINDOW_TOGGLE_KEY);
Preference explainProtectedMediaKey = screen.findPreference(EXPLAIN_PROTECTED_MEDIA_KEY);
PreferenceGroup allowedGroup = screen.findPreference(ALLOWED_GROUP);
PreferenceGroup blockedGroup = screen.findPreference(BLOCKED_GROUP);
PreferenceGroup managedGroup = screen.findPreference(MANAGED_GROUP);
boolean permissionBlockedByOs = mCategory.showPermissionBlockedMessage(getContext());
if (mGlobalToggleLayout != GlobalToggleLayout.BINARY_TOGGLE) {
screen.removePreference(binaryToggle);
}
if (mGlobalToggleLayout != GlobalToggleLayout.TRI_STATE_TOGGLE) {
screen.removePreference(triStateToggle);
}
if (mGlobalToggleLayout != GlobalToggleLayout.TRI_STATE_COOKIE_TOGGLE) {
screen.removePreference(triStateCookieToggle);
}
switch (mGlobalToggleLayout) {
case GlobalToggleLayout.BINARY_TOGGLE:
configureBinaryToggle(binaryToggle, contentType);
break;
case GlobalToggleLayout.TRI_STATE_TOGGLE:
configureTriStateToggle(triStateToggle, contentType);
break;
case GlobalToggleLayout.TRI_STATE_COOKIE_TOGGLE:
configureTriStateCookieToggle(triStateCookieToggle);
break;
}
Preference infoText = screen.findPreference(INFO_TEXT_KEY);
if (mCategory.getType() == SiteSettingsCategory.Type.SITE_DATA) {
infoText.setSummary(R.string.website_settings_site_data_page_description);
} else if (mCategory.getType() == SiteSettingsCategory.Type.THIRD_PARTY_COOKIES) {
infoText.setSummary(R.string.website_settings_third_party_cookies_page_description);
} else if (mCategory.getType() == SiteSettingsCategory.Type.STORAGE_ACCESS) {
infoText.setSummary(getStorageAccessSummary());
} else {
screen.removePreference(infoText);
}
// Hide the anti-abuse text preferences, as needed.
if (mCategory.getType() != SiteSettingsCategory.Type.ANTI_ABUSE) {
Preference antiAbuseWhenOnHeader = screen.findPreference(ANTI_ABUSE_WHEN_ON_HEADER);
Preference antiAbuseWhenOnSectionOne =
screen.findPreference(ANTI_ABUSE_WHEN_ON_SECTION_ONE);
Preference antiAbuseWhenOnSectionTwo =
screen.findPreference(ANTI_ABUSE_WHEN_ON_SECTION_TWO);
Preference antiAbuseWhenOnSectionThree =
screen.findPreference(ANTI_ABUSE_WHEN_ON_SECTION_THREE);
Preference antiAbuseThingsToConsiderHeader =
screen.findPreference(ANTI_ABUSE_THINGS_TO_CONSIDER_HEADER);
Preference antiAbuseThingsToConsiderSectionOne =
screen.findPreference(ANTI_ABUSE_THINGS_TO_CONSIDER_SECTION_ONE);
screen.removePreference(antiAbuseWhenOnHeader);
screen.removePreference(antiAbuseWhenOnSectionOne);
screen.removePreference(antiAbuseWhenOnSectionTwo);
screen.removePreference(antiAbuseWhenOnSectionThree);
screen.removePreference(antiAbuseThingsToConsiderHeader);
screen.removePreference(antiAbuseThingsToConsiderSectionOne);
}
// Show either the old or new settings UI for geolocation permissions.
if (mCategory.getType() == SiteSettingsCategory.Type.DEVICE_LOCATION) {
if (getSiteSettingsDelegate().isPermissionDedicatedCpssSettingAndroidFeatureEnabled()) {
mLocationTriStatePref.initialize(
(UserPrefs.get(getSiteSettingsDelegate().getBrowserContextHandle())));
updateLocationSecondaryControls();
} else {
screen.removePreference(mLocationTriStatePref);
}
} else {
screen.removePreference(mLocationTriStatePref);
}
if (permissionBlockedByOs) {
maybeShowOsWarning(screen);
screen.removePreference(notificationsVibrate);
screen.removePreference(mNotificationsQuietUiPref);
screen.removePreference(mNotificationsTriStatePref);
screen.removePreference(mDesktopSiteWindowPref);
screen.removePreference(explainProtectedMediaKey);
screen.removePreference(allowedGroup);
screen.removePreference(blockedGroup);
screen.removePreference(managedGroup);
// Since all preferences are hidden, there's nothing to do further and we can
// simply return.
return;
}
// Configure/hide the notifications secondary controls, as needed.
if (mCategory.getType() == SiteSettingsCategory.Type.NOTIFICATIONS) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
notificationsVibrate.setOnPreferenceChangeListener(this);
} else {
screen.removePreference(notificationsVibrate);
}
if (getSiteSettingsDelegate().isQuietNotificationPromptsFeatureEnabled()) {
mNotificationsQuietUiPref.setOnPreferenceChangeListener(this);
} else {
screen.removePreference(mNotificationsQuietUiPref);
}
// Show either the old or new settings UI for notifications permissions.
if (getSiteSettingsDelegate().isPermissionDedicatedCpssSettingAndroidFeatureEnabled()) {
screen.removePreference(mNotificationsQuietUiPref);
mNotificationsTriStatePref.initialize(
UserPrefs.get(getSiteSettingsDelegate().getBrowserContextHandle()));
} else {
screen.removePreference(mNotificationsTriStatePref);
}
updateNotificationsSecondaryControls();
} else {
screen.removePreference(notificationsVibrate);
screen.removePreference(mNotificationsQuietUiPref);
screen.removePreference(mNotificationsTriStatePref);
}
// Configure/hide the desktop site window setting, as needed.
if (mCategory.getType() == SiteSettingsCategory.Type.REQUEST_DESKTOP_SITE
&& ContentFeatureMap.isEnabled(
ContentFeatureList.REQUEST_DESKTOP_SITE_WINDOW_SETTING)) {
mDesktopSiteWindowPref.setOnPreferenceChangeListener(this);
updateDesktopSiteWindowSetting();
} else {
screen.removePreference(mDesktopSiteWindowPref);
}
// Only show the link that explains protected content settings when needed.
if (mCategory.getType() == SiteSettingsCategory.Type.PROTECTED_MEDIA
&& getSiteSettingsDelegate().isHelpAndFeedbackEnabled()) {
explainProtectedMediaKey.setOnPreferenceClickListener(
preference -> {
getSiteSettingsDelegate()
.launchProtectedContentHelpAndFeedbackActivity(getActivity());
return true;
});
// On small screens with no touch input, nested focusable items inside a LinearLayout in
// ListView cause focus problems when using a keyboard (crbug.com/974413).
// TODO(chouinard): Verify on a small screen device whether this patch is still needed
// now that we've migrated this fragment to Support Library (mListView is a RecyclerView
// now).
mListView.setFocusable(false);
} else {
screen.removePreference(explainProtectedMediaKey);
mListView.setFocusable(true);
}
// When this menu opens, make sure the Blocked list is collapsed.
if (!mGroupByAllowBlock) {
mBlockListExpanded = false;
mAllowListExpanded = true;
mManagedListExpanded = false;
}
mGroupByAllowBlock = true;
allowedGroup.setOnPreferenceClickListener(this);
blockedGroup.setOnPreferenceClickListener(this);
managedGroup.setOnPreferenceClickListener(this);
}
private SpannableString getStorageAccessSummary() {
final String storageAccessRawString =
getContext().getString(R.string.website_settings_storage_access_page_description);
final NoUnderlineClickableSpan clickableSpan =
new NoUnderlineClickableSpan(
getContext(),
(widget) -> {
getSiteSettingsDelegate()
.launchStorageAccessHelpActivity(getActivity());
});
return SpanApplier.applySpans(
storageAccessRawString,
new SpanApplier.SpanInfo("<link>", "</link>", clickableSpan));
}
private void maybeShowOsWarning(PreferenceScreen screen) {
if (isBlocked()) {
return;
}
// Show the link to system settings since permission is disabled.
ChromeBasePreference osWarning = new ChromeBasePreference(getStyledContext(), null);
ChromeBasePreference osWarningExtra = new ChromeBasePreference(getStyledContext(), null);
mCategory.configurePermissionIsOffPreferences(
osWarning,
osWarningExtra,
getContext(),
true,
getSiteSettingsDelegate().getAppName());
if (osWarning.getTitle() != null) {
osWarning.setKey(SingleWebsiteSettings.PREF_OS_PERMISSIONS_WARNING);
screen.addPreference(osWarning);
}
if (osWarningExtra.getTitle() != null) {
osWarningExtra.setKey(SingleWebsiteSettings.PREF_OS_PERMISSIONS_WARNING_EXTRA);
screen.addPreference(osWarningExtra);
}
}
private void configureTriStateCookieToggle(
TriStateCookieSettingsPreference triStateCookieToggle) {
triStateCookieToggle.setOnPreferenceChangeListener(this);
triStateCookieToggle.setCookiesDetailsRequestedListener(this);
TriStateCookieSettingsPreference.Params params =
new TriStateCookieSettingsPreference.Params();
params.cookieControlsMode = getCookieControlsMode();
params.cookieControlsModeEnforced = mCategory.isManaged();
params.isIncognitoModeEnabled = getSiteSettingsDelegate().isIncognitoModeEnabled();
params.isPrivacySandboxFirstPartySetsUIEnabled =
getSiteSettingsDelegate().isPrivacySandboxFirstPartySetsUIFeatureEnabled();
params.isFirstPartySetsDataAccessEnabled =
getSiteSettingsDelegate().isFirstPartySetsDataAccessEnabled();
triStateCookieToggle.setState(params);
maybeShowOffboardingCard();
}
private int getCookieControlsMode() {
PrefService prefService =
UserPrefs.get(getSiteSettingsDelegate().getBrowserContextHandle());
return prefService.getInteger(COOKIE_CONTROLS_MODE);
}
private void configureTriStateToggle(
TriStateSiteSettingsPreference triStateToggle, int contentType) {
triStateToggle.setOnPreferenceChangeListener(this);
@ContentSettingValues
int setting =
WebsitePreferenceBridge.getDefaultContentSetting(
getSiteSettingsDelegate().getBrowserContextHandle(), contentType);
int[] descriptionIds =
ContentSettingsResources.getTriStateSettingDescriptionIDs(contentType);
triStateToggle.initialize(setting, descriptionIds);
}
private void configureBinaryToggle(ChromeSwitchPreference binaryToggle, int contentType) {
binaryToggle.setOnPreferenceChangeListener(this);
binaryToggle.setTitle(ContentSettingsResources.getTitle(contentType));
// Set summary on or off.
BrowserContextHandle browserContextHandle =
getSiteSettingsDelegate().getBrowserContextHandle();
if (mCategory.getType() == SiteSettingsCategory.Type.DEVICE_LOCATION
&& WebsitePreferenceBridge.isLocationAllowedByPolicy(browserContextHandle)) {
binaryToggle.setSummaryOn(ContentSettingsResources.getGeolocationAllowedSummary());
} else {
binaryToggle.setSummaryOn(ContentSettingsResources.getEnabledSummary(contentType));
}
binaryToggle.setSummaryOff(ContentSettingsResources.getDisabledSummary(contentType));
int summaryForAccessibility =
ContentSettingsResources.getSummaryOverrideForScreenReader(contentType);
if (summaryForAccessibility != 0) {
binaryToggle.setSummaryOverrideForScreenReader(
getContext().getString(summaryForAccessibility));
}
binaryToggle.setManagedPreferenceDelegate(
new SingleCategoryManagedPreferenceDelegate(
getSiteSettingsDelegate().getManagedPreferenceDelegate()));
// Set the checked value.
binaryToggle.setChecked(
WebsitePreferenceBridge.isCategoryEnabled(browserContextHandle, contentType));
}
private void updateNotificationsSecondaryControls() {
BrowserContextHandle browserContextHandle =
getSiteSettingsDelegate().getBrowserContextHandle();
Boolean categoryEnabled =
WebsitePreferenceBridge.isCategoryEnabled(
browserContextHandle, ContentSettingsType.NOTIFICATIONS);
// The notifications vibrate checkbox.
ChromeBaseCheckBoxPreference vibratePref =
getPreferenceScreen().findPreference(NOTIFICATIONS_VIBRATE_TOGGLE_KEY);
if (vibratePref != null) vibratePref.setEnabled(categoryEnabled);
if (!getSiteSettingsDelegate().isQuietNotificationPromptsFeatureEnabled()) return;
if (categoryEnabled) {
if (getSiteSettingsDelegate().isPermissionDedicatedCpssSettingAndroidFeatureEnabled()) {
getPreferenceScreen().addPreference(mNotificationsTriStatePref);
} else {
getPreferenceScreen().addPreference(mNotificationsQuietUiPref);
PrefService prefService = UserPrefs.get(browserContextHandle);
mNotificationsQuietUiPref.setChecked(
prefService.getBoolean(ENABLE_QUIET_NOTIFICATION_PERMISSION_UI));
}
} else {
if (getSiteSettingsDelegate().isPermissionDedicatedCpssSettingAndroidFeatureEnabled()) {
getPreferenceScreen().removePreference(mNotificationsTriStatePref);
} else {
getPreferenceScreen().removePreference(mNotificationsQuietUiPref);
}
}
}
private void updateLocationSecondaryControls() {
BrowserContextHandle browserContextHandle =
getSiteSettingsDelegate().getBrowserContextHandle();
Boolean categoryEnabled =
WebsitePreferenceBridge.isCategoryEnabled(
browserContextHandle, ContentSettingsType.GEOLOCATION);
if (getSiteSettingsDelegate().isPermissionDedicatedCpssSettingAndroidFeatureEnabled()) {
if (categoryEnabled) {
getPreferenceScreen().addPreference(mLocationTriStatePref);
} else {
getPreferenceScreen().removePreference(mLocationTriStatePref);
}
}
}
// TODO(crbug.com/40852484): Looking at a different class setup for SingleCategorySettings that
// allows category specific logic to live in separate files.
private void updateDesktopSiteWindowSetting() {
if (!ContentFeatureMap.isEnabled(ContentFeatureList.REQUEST_DESKTOP_SITE_WINDOW_SETTING)) {
return;
}
BrowserContextHandle browserContextHandle =
getSiteSettingsDelegate().getBrowserContextHandle();
Boolean categoryEnabled =
WebsitePreferenceBridge.isCategoryEnabled(
browserContextHandle, ContentSettingsType.REQUEST_DESKTOP_SITE);
if (categoryEnabled) {
// When the global setting for RDS is on, window setting should be displayed.
getPreferenceScreen().addPreference(mDesktopSiteWindowPref);
PrefService prefService = UserPrefs.get(browserContextHandle);
mDesktopSiteWindowPref.setChecked(
prefService.getBoolean(DESKTOP_SITE_WINDOW_SETTING_ENABLED));
} else {
// Otherwise, ensure window setting is hidden.
getPreferenceScreen().removePreference(mDesktopSiteWindowPref);
}
}
private void showManagedToast() {
if (mCategory.isManagedByCustodian()) {
ManagedPreferencesUtils.showManagedByParentToast(
getContext(),
new SingleCategoryManagedPreferenceDelegate(
getSiteSettingsDelegate().getManagedPreferenceDelegate()));
} else {
ManagedPreferencesUtils.showManagedByAdministratorToast(getContext());
}
}
/**
* Builds an alert dialog which can be used to change the preference value or remove
* for the exception for the current categories ContentSettingType on a Website.
*/
private AlertDialog buildPreferenceDialog(Website site) {
BrowserContextHandle browserContextHandle =
getSiteSettingsDelegate().getBrowserContextHandle();
@ContentSettingsType.EnumType int contentSettingsType = mCategory.getContentSettingsType();
@ContentSettingValues
Integer value = site.getContentSetting(browserContextHandle, contentSettingsType);
AlertDialog alertDialog =
new AlertDialog.Builder(getContext(), R.style.ThemeOverlay_BrowserUI_AlertDialog)
.setTitle(
getContext()
.getString(
R.string.website_settings_edit_site_dialog_title))
.setPositiveButton(R.string.cancel, null)
.setNegativeButton(
R.string.remove,
(dialog, which) -> {
site.setContentSetting(
browserContextHandle,
contentSettingsType,
ContentSettingValues.DEFAULT);
if (mCategory.getType()
== SiteSettingsCategory.Type.AUTO_DARK_WEB_CONTENT) {
AutoDarkMetrics.recordAutoDarkSettingsChangeSource(
AutoDarkSettingsChangeSource
.SITE_SETTINGS_EXCEPTION_LIST,
false);
}
getInfoForOrigins();
dialog.dismiss();
})
.create();
// Set a custom view with description text and a radio button group that uses
// RadioButtonWithDescriptionLayout.
var inflater =
(LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
var contentView = (LinearLayout) inflater.inflate(R.layout.edit_site_dialog_content, null);
TextView messageView = contentView.findViewById(R.id.message);
messageView.setText(
getContext()
.getString(
R.string.website_settings_edit_site_dialog_description,
site.getTitleForPreferenceRow()));
RadioButtonWithDescriptionLayout radioGroup =
contentView.findViewById(R.id.radio_button_group);
RadioButtonWithDescription allowButton = radioGroup.findViewById(R.id.allow);
allowButton.setPrimaryText(
getString(
ContentSettingsResources.getSiteSummary(
ContentSettingValues.ALLOW, contentSettingsType)));
RadioButtonWithDescription blockButton = radioGroup.findViewById(R.id.block);
blockButton.setPrimaryText(
getString(
ContentSettingsResources.getSiteSummary(
ContentSettingValues.BLOCK, contentSettingsType)));
if (value == ContentSettingValues.ALLOW) {
allowButton.setChecked(true);
} else {
blockButton.setChecked(true);
}
radioGroup.setOnCheckedChangeListener(
(radioButtonGroup, i) -> {
@ContentSettingValues
int permission =
allowButton.isChecked()
? ContentSettingValues.ALLOW
: ContentSettingValues.BLOCK;
site.setContentSetting(browserContextHandle, contentSettingsType, permission);
DesktopSiteMetrics.recordDesktopSiteSettingsChanged(
mCategory.getType(), permission, site);
getInfoForOrigins();
alertDialog.dismiss();
});
alertDialog.setView(contentView);
return alertDialog;
}
/**
* Always returns true unless a category uses custom logic to show/hide exceptions on the
* category settings page.
* @return Whether exceptions should be added for the category.
*/
private boolean shouldAddExceptionsForCategory() {
if (mCategory.getType() == SiteSettingsCategory.Type.ANTI_ABUSE) {
return false;
}
return true;
}
/**
* Performs a set of tasks when the user updates the desktop site content setting.
* 1. Records the desktop site content setting change.
* 2. Updates the Shared Preference USER_ENABLED_DESKTOP_SITE_GLOBAL_SETTING_PREFERENCE_KEY.
* @param enabled Whether the desktop site is enabled after the change.
*/
public static void recordSiteLayoutChanged(boolean enabled) {
@SiteLayout int layout = enabled ? SiteLayout.DESKTOP : SiteLayout.MOBILE;
RecordHistogram.recordEnumeratedHistogram(
"Android.RequestDesktopSite.Changed", layout, SiteLayout.NUM_ENTRIES);
// TODO(crbug.com/40126122): Use SharedPreferencesManager if it is componentized.
ContextUtils.getAppSharedPreferences()
.edit()
.putBoolean(
SingleCategorySettingsConstants
.USER_ENABLED_DESKTOP_SITE_GLOBAL_SETTING_PREFERENCE_KEY,
enabled)
.apply();
}
public ForwardingManagedPreferenceDelegate createWebsiteManagedPrefDelegate() {
return new ForwardingManagedPreferenceDelegate(
getSiteSettingsDelegate().getManagedPreferenceDelegate()) {
@Override
public boolean isPreferenceControlledByPolicy(Preference preference) {
WebsitePreference websitePref = (WebsitePreference) preference;
ContentSettingException exception =
websitePref
.site()
.getContentSettingException(mCategory.getContentSettingsType());
if (exception != null && exception.getSource() != null) {
return exception.getSource().equals(POLICY);
}
return false;
}
/*
* Click is always enabled as a toast will be shown if a managed preference is clicked.
*/
@Override
public boolean isPreferenceClickDisabled(Preference preference) {
return false;
}
};
}
private void maybeShowOffboardingCard() {
if (getSiteSettingsDelegate().shouldShowSettingsOffboardingNotice()) {
mCardPreference = findPreference(CARD_PREFERENCE_KEY);
mCardPreference.setVisible(true);
mCardPreference.setSummary(
SpanApplier.applySpans(
getResources()
.getString(
R.string.tracking_protection_settings_rollback_notice),
new SpanInfo(
"<link>",
"</link>",
new NoUnderlineClickableSpan(
getContext(),
(view) -> openUrlInCct(TP_LEARN_MORE_URL)))));
mCardPreference.setIconDrawable(
SettingsUtils.getTintedIcon(getContext(), R.drawable.infobar_warning));
mCardPreference.setCloseIconVisibility(View.VISIBLE);
mCardPreference.setOnCloseClickListener(this::onOffboardingCardCloseClick);
}
}
private void onOffboardingCardCloseClick(View button) {
mCardPreference.setVisible(false);
}
private void openUrlInCct(String url) {
var customTabHelper = getCustomTabIntentHelper();
assert (customTabHelper != null) : "CCT helpers must be set before opening a link";
CustomTabsIntent customTabIntent =
new CustomTabsIntent.Builder().setShowTitle(true).build();
customTabIntent.intent.setData(Uri.parse(url));
Intent intent =
customTabHelper.createCustomTabActivityIntent(getContext(), customTabIntent.intent);
intent.setPackage(getContext().getPackageName());
intent.putExtra(Browser.EXTRA_APPLICATION_ID, getContext().getPackageName());
IntentUtils.addTrustedIntentExtras(intent);
IntentUtils.safeStartActivity(getContext(), intent);
}
}