// Copyright 2022 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.privacy_sandbox;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.base.supplier.ObservableSupplier;
import org.chromium.base.supplier.ObservableSupplierImpl;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.preferences.Pref;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.settings.ChromeManagedPreferenceDelegate;
import org.chromium.chrome.browser.ui.messages.snackbar.Snackbar;
import org.chromium.components.browser_ui.settings.ChromeBasePreference;
import org.chromium.components.browser_ui.settings.ChromeSwitchPreference;
import org.chromium.components.browser_ui.settings.ClickableSpansTextMessagePreference;
import org.chromium.components.browser_ui.settings.SettingsUtils;
import org.chromium.components.browser_ui.settings.TextMessagePreference;
import org.chromium.components.favicon.LargeIconBridge;
import org.chromium.components.prefs.PrefService;
import org.chromium.components.user_prefs.UserPrefs;
import org.chromium.ui.text.NoUnderlineClickableSpan;
import org.chromium.ui.text.SpanApplier;
import java.util.List;
/** Fragment for the Privacy Sandbox -> Fledge preferences. */
public class FledgeFragment extends PrivacySandboxSettingsBaseFragment
implements Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener {
@VisibleForTesting static final int MAX_DISPLAYED_SITES = 15;
private static final String FLEDGE_TOGGLE_PREFERENCE = "fledge_toggle";
private static final String FLEDGE_DESCRIPTION_PREFERENCE = "fledge_description";
private static final String HEADING_PREFERENCE = "fledge_heading";
private static final String CURRENT_SITES_PREFERENCE = "current_fledge_sites";
private static final String EMPTY_FLEDGE_PREFERENCE = "fledge_empty";
private static final String DISABLED_FLEDGE_PREFERENCE = "fledge_disabled";
private static final String ALL_SITES_PREFERENCE = "fledge_all_sites";
private static final String FOOTER_PREFERENCE = "fledge_page_footer";
private ChromeSwitchPreference mFledgeTogglePreference;
private TextMessagePreference mFledgeDescriptionPreference;
private PreferenceCategoryWithClickableSummary mHeadingPreference;
private PreferenceCategory mCurrentSitesCategory;
private TextMessagePreference mEmptyFledgePreference;
private TextMessagePreference mDisabledFledgePreference;
private ChromeBasePreference mAllSitesPreference;
private LargeIconBridge mLargeIconBridge;
private ClickableSpansTextMessagePreference mFooterPreference;
private boolean mMoreThanMaxSitesToDisplay;
private final ObservableSupplierImpl<String> mPageTitle = new ObservableSupplierImpl<>();
static boolean isFledgePrefEnabled(Profile profile) {
PrefService prefService = UserPrefs.get(profile);
return prefService.getBoolean(Pref.PRIVACY_SANDBOX_M1_FLEDGE_ENABLED);
}
static void setFledgePrefEnabled(Profile profile, boolean isEnabled) {
PrefService prefService = UserPrefs.get(profile);
prefService.setBoolean(Pref.PRIVACY_SANDBOX_M1_FLEDGE_ENABLED, isEnabled);
}
static boolean isFledgePrefManaged(Profile profile) {
PrefService prefService = UserPrefs.get(profile);
return prefService.isManagedPreference(Pref.PRIVACY_SANDBOX_M1_FLEDGE_ENABLED);
}
@Override
public void onCreatePreferences(@Nullable Bundle bundle, @Nullable String s) {
super.onCreatePreferences(bundle, s);
mPageTitle.set(getString(R.string.settings_fledge_page_title));
SettingsUtils.addPreferencesFromResource(this, R.xml.fledge_preference);
mFledgeTogglePreference = findPreference(FLEDGE_TOGGLE_PREFERENCE);
mFledgeDescriptionPreference = findPreference(FLEDGE_DESCRIPTION_PREFERENCE);
mHeadingPreference = findPreference(HEADING_PREFERENCE);
mCurrentSitesCategory = findPreference(CURRENT_SITES_PREFERENCE);
mEmptyFledgePreference = findPreference(EMPTY_FLEDGE_PREFERENCE);
mDisabledFledgePreference = findPreference(DISABLED_FLEDGE_PREFERENCE);
mAllSitesPreference = findPreference(ALL_SITES_PREFERENCE);
mFooterPreference = (ClickableSpansTextMessagePreference) findPreference(FOOTER_PREFERENCE);
mFledgeTogglePreference.setChecked(isFledgePrefEnabled(getProfile()));
mFledgeTogglePreference.setOnPreferenceChangeListener(this);
mFledgeTogglePreference.setManagedPreferenceDelegate(createManagedPreferenceDelegate());
mMoreThanMaxSitesToDisplay = false;
mHeadingPreference.setSummary(
SpanApplier.applySpans(
getResources()
.getString(R.string.settings_fledge_page_current_sites_description),
new SpanApplier.SpanInfo(
"<link>",
"</link>",
new NoUnderlineClickableSpan(
getContext(), this::onLearnMoreClicked))));
if (!ChromeFeatureList.isEnabled(
ChromeFeatureList.PRIVACY_SANDBOX_PROACTIVE_TOPICS_BLOCKING)) {
mFooterPreference.setSummary(
SpanApplier.applySpans(
getResources().getString(R.string.settings_fledge_page_footer),
new SpanApplier.SpanInfo(
"<link1>",
"</link1>",
new NoUnderlineClickableSpan(
getContext(), this::onFledgeSettingsLinkClicked)),
new SpanApplier.SpanInfo(
"<link2>",
"</link2>",
new NoUnderlineClickableSpan(
getContext(), this::onCookieSettingsLink))));
mFledgeDescriptionPreference.setVisible(false);
return;
}
mFooterPreference.setSummary(
SpanApplier.applySpans(
getResources().getString(R.string.settings_fledge_page_footer_new),
new SpanApplier.SpanInfo(
"<link1>",
"</link1>",
new NoUnderlineClickableSpan(
getContext(), this::onFledgeSettingsLinkClicked)),
new SpanApplier.SpanInfo(
"<link2>",
"</link2>",
new NoUnderlineClickableSpan(
getContext(), this::onCookieSettingsLink)),
new SpanApplier.SpanInfo(
"<link3>",
"</link3>",
new NoUnderlineClickableSpan(
getContext(), this::onManagingAdPrivacyClicked))));
}
@Override
public ObservableSupplier<String> getPageTitle() {
return mPageTitle;
}
private void onLearnMoreClicked(View view) {
RecordUserAction.record("Settings.PrivacySandbox.Fledge.LearnMoreClicked");
launchSettingsActivity(FledgeLearnMoreFragment.class);
}
private void onManagingAdPrivacyClicked(View view) {
openUrlInCct(PrivacySandboxSettingsFragment.HELP_CENTER_URL);
}
private void onFledgeSettingsLinkClicked(View view) {
launchSettingsActivity(TopicsFragment.class);
}
private void onCookieSettingsLink(View view) {
launchCookieSettings();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Disable animations on preference changes.
getListView().setItemAnimator(null);
}
@Override
public void onResume() {
super.onResume();
getPrivacySandboxBridge().getFledgeJoiningEtldPlusOneForDisplay(this::populateCurrentSites);
updatePreferenceVisibility();
}
@Override
public void onDestroyView() {
super.onDestroyView();
if (mLargeIconBridge != null) {
mLargeIconBridge.destroy();
}
mLargeIconBridge = null;
}
@Override
public boolean onPreferenceChange(@NonNull Preference preference, Object value) {
if (preference.getKey().equals(FLEDGE_TOGGLE_PREFERENCE)) {
boolean enabled = (boolean) value;
RecordUserAction.record(
enabled
? "Settings.PrivacySandbox.Fledge.Enabled"
: "Settings.PrivacySandbox.Fledge.Disabled");
setFledgePrefEnabled(getProfile(), enabled);
updatePreferenceVisibility();
return true;
}
return false;
}
@Override
public boolean onPreferenceClick(@NonNull Preference preference) {
if (preference instanceof FledgePreference) {
getPrivacySandboxBridge()
.setFledgeJoiningAllowed(((FledgePreference) preference).getSite(), false);
mCurrentSitesCategory.removePreference(preference);
updatePreferenceVisibility();
showSnackbar(
R.string.settings_fledge_page_block_site_snackbar,
null,
Snackbar.TYPE_ACTION,
Snackbar.UMA_PRIVACY_SANDBOX_REMOVE_SITE,
/* actionStringResId= */ 0,
/* multiLine= */ true);
RecordUserAction.record("Settings.PrivacySandbox.Fledge.SiteRemoved");
return true;
}
return false;
}
private void populateCurrentSites(List<String> currentSites) {
if (mLargeIconBridge == null) {
mLargeIconBridge = new LargeIconBridge(getProfile());
}
mCurrentSitesCategory.removeAll();
mMoreThanMaxSitesToDisplay = currentSites.size() > MAX_DISPLAYED_SITES;
int nrDisplayedSites = Math.min(currentSites.size(), MAX_DISPLAYED_SITES);
for (int i = 0; i < nrDisplayedSites; i++) {
String site = currentSites.get(i);
FledgePreference preference =
new FledgePreference(getContext(), site, mLargeIconBridge);
preference.setImage(
R.drawable.btn_close,
getResources()
.getString(R.string.settings_fledge_page_block_site_a11y_label, site));
preference.setDividerAllowedAbove(false);
preference.setOnPreferenceClickListener(this);
mCurrentSitesCategory.addPreference(preference);
}
updatePreferenceVisibility();
}
private void updatePreferenceVisibility() {
boolean fledgeEnabled = isFledgePrefEnabled(getProfile());
boolean sitesEmpty = mCurrentSitesCategory.getPreferenceCount() == 0;
// Visible when Fledge is disabled.
mDisabledFledgePreference.setVisible(!fledgeEnabled);
// Visible when Fledge is enabled, but the current sites list is empty.
mEmptyFledgePreference.setVisible(fledgeEnabled && sitesEmpty);
// Visible when Fledge is enabled and the current sites list is not empty.
mCurrentSitesCategory.setVisible(fledgeEnabled && !sitesEmpty);
// Visible when Fledge is enabled and has more than MAX_DISPLAYED_SITES allowed sites.
mAllSitesPreference.setVisible(fledgeEnabled && mMoreThanMaxSitesToDisplay);
}
private ChromeManagedPreferenceDelegate createManagedPreferenceDelegate() {
return new ChromeManagedPreferenceDelegate(getProfile()) {
@Override
public boolean isPreferenceControlledByPolicy(Preference preference) {
if (FLEDGE_TOGGLE_PREFERENCE.equals(preference.getKey())) {
return isFledgePrefManaged(getProfile());
}
return false;
}
};
}
}