chromium/chrome/browser/safety_hub/android/java/src/org/chromium/chrome/browser/safety_hub/SafetyHubFragment.java

// Copyright 2024 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.safety_hub;

import static org.chromium.chrome.browser.safety_hub.SafetyHubMetricUtils.getDashboardModuleTypeForModuleOption;
import static org.chromium.chrome.browser.safety_hub.SafetyHubMetricUtils.recordDashboardInteractions;
import static org.chromium.chrome.browser.safety_hub.SafetyHubMetricUtils.recordModuleState;
import static org.chromium.chrome.browser.safety_hub.SafetyHubMetricUtils.recordNotificationsInteraction;
import static org.chromium.chrome.browser.safety_hub.SafetyHubMetricUtils.recordRevokedPermissionsInteraction;
import static org.chromium.chrome.browser.safety_hub.SafetyHubModuleViewBinder.getModuleState;
import static org.chromium.chrome.browser.safety_hub.SafetyHubModuleViewBinder.isBrowserStateSafe;

import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Browser;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;

import androidx.annotation.VisibleForTesting;
import androidx.browser.customtabs.CustomTabsIntent;

import org.chromium.base.IntentUtils;
import org.chromium.base.supplier.ObservableSupplier;
import org.chromium.base.supplier.ObservableSupplierImpl;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.omaha.UpdateStatusProvider;
import org.chromium.chrome.browser.password_manager.PasswordStoreBridge;
import org.chromium.chrome.browser.password_manager.PasswordStoreCredential;
import org.chromium.chrome.browser.preferences.Pref;
import org.chromium.chrome.browser.safe_browsing.SafeBrowsingState;
import org.chromium.chrome.browser.safe_browsing.settings.SafeBrowsingSettingsFragment;
import org.chromium.chrome.browser.safety_hub.SafetyHubMetricUtils.DashboardInteractions;
import org.chromium.chrome.browser.safety_hub.SafetyHubMetricUtils.DashboardModuleType;
import org.chromium.chrome.browser.safety_hub.SafetyHubMetricUtils.LifecycleEvent;
import org.chromium.chrome.browser.safety_hub.SafetyHubMetricUtils.NotificationsModuleInteractions;
import org.chromium.chrome.browser.safety_hub.SafetyHubMetricUtils.PermissionsModuleInteractions;
import org.chromium.chrome.browser.safety_hub.SafetyHubModuleProperties.ModuleOption;
import org.chromium.chrome.browser.safety_hub.SafetyHubModuleProperties.ModuleState;
import org.chromium.chrome.browser.signin.services.IdentityServicesProvider;
import org.chromium.chrome.browser.signin.services.SigninManager;
import org.chromium.chrome.browser.ui.messages.snackbar.Snackbar;
import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
import org.chromium.components.browser_ui.settings.CardPreference;
import org.chromium.components.browser_ui.settings.ExpandablePreferenceGroup;
import org.chromium.components.browser_ui.settings.SettingsUtils;
import org.chromium.components.browser_ui.site_settings.SiteSettings;
import org.chromium.components.browser_ui.site_settings.SiteSettingsCategory;
import org.chromium.components.browser_ui.util.TraceEventVectorDrawableCompat;
import org.chromium.components.user_prefs.UserPrefs;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor;

import java.util.List;

/** Fragment containing Safety hub. */
public class SafetyHubFragment extends SafetyHubBaseFragment
        implements UnusedSitePermissionsBridge.Observer,
                NotificationPermissionReviewBridge.Observer,
                SafetyHubFetchService.Observer,
                PasswordStoreBridge.PasswordStoreObserver,
                SigninManager.SignInStateObserver {
    /**
     * Functional interface to start a Chrome Custom Tab for the given intent, e.g. by using {@link
     * org.chromium.chrome.browser.LaunchIntentDispatcher#createCustomTabActivityIntent}.
     * TODO(crbug.com/40751023): Update when LaunchIntentDispatcher is (partially-)modularized.
     */
    public interface CustomTabIntentHelper {
        /**
         * @see org.chromium.chrome.browser.LaunchIntentDispatcher#createCustomTabActivityIntent
         */
        Intent createCustomTabActivityIntent(Context context, Intent intent);
    }

    private static final String PREF_PASSWORDS = "passwords_account";
    private static final String PREF_UPDATE = "update_check";
    private static final String PREF_UNUSED_PERMISSIONS = "permissions";
    private static final String PREF_NOTIFICATIONS_REVIEW = "notifications_review";
    private static final String PREF_SAFE_BROWSING = "safe_browsing";
    private static final String PREF_BROWSER_STATE_INDICATOR = "browser_state_indicator";
    private static final String PREF_SAFETY_TIPS_SAFETY_TOOLS = "safety_tips_safety_tools";
    private static final String PREF_SAFETY_TIPS_INCOGNITO = "safety_tips_incognito";
    private static final String PREF_SAFETY_TIPS_SAFE_BROWSING = "safety_tips_safe_browsing";
    private static final String PREF_SAFETY_TIPS = "safety_tips";

    @VisibleForTesting
    static final String SAFETY_TOOLS_LEARN_MORE_URL = "https://www.google.com/chrome/#safe";

    @VisibleForTesting
    static final String INCOGNITO_LEARN_MORE_URL = "https://support.google.com/chrome/?p=incognito";

    @VisibleForTesting
    static final String SAFE_BROWSING_LEARN_MORE_URL =
            "https://support.google.com/chrome?p=safe_browsing_preferences";

    @VisibleForTesting
    static final String HELP_CENTER_URL = "https://support.google.com/chrome?p=safety_check";

    private SafetyHubModuleDelegate mDelegate;
    private UnusedSitePermissionsBridge mUnusedSitePermissionsBridge;
    private NotificationPermissionReviewBridge mNotificationPermissionReviewBridge;
    private SafetyHubFetchService mSafetyHubFetchService;
    private PropertyModel mUpdateCheckPropertyModel;
    private PropertyModel mPasswordCheckPropertyModel;
    private PropertyModel mSafeBrowsingPropertyModel;
    private PropertyModel mPermissionsModel;
    private PropertyModel mNotificationsModel;
    private PropertyModel mBrowserStateModule;
    private CustomTabIntentHelper mCustomTabIntentHelper;
    private PasswordStoreBridge mPasswordStoreBridge;
    private SigninManager mSigninManager;
    private final ObservableSupplierImpl<String> mPageTitle = new ObservableSupplierImpl<>();

    @Override
    public void onCreatePreferences(Bundle bundle, String s) {
        SettingsUtils.addPreferencesFromResource(this, R.xml.safety_hub_preferences);
        mPageTitle.set(getString(R.string.prefs_safety_check));

        mUnusedSitePermissionsBridge = UnusedSitePermissionsBridge.getForProfile(getProfile());
        mNotificationPermissionReviewBridge =
                NotificationPermissionReviewBridge.getForProfile(getProfile());
        mSafetyHubFetchService = SafetyHubFetchServiceFactory.getForProfile(getProfile());
        mSigninManager = IdentityServicesProvider.get().getSigninManager(getProfile());

        setUpAccountPasswordCheckModule();
        setUpUpdateCheckModule();
        setUpPermissionsRevocationModule();
        setUpNotificationsReviewModule();
        setUpSafeBrowsingModule();
        setUpSafetyTipsModule();
        setUpBrowserStateModule();

        updateAllModules();
        recordAllModulesState(LifecycleEvent.ON_IMPRESSION);
        setHasOptionsMenu(true);

        // Notify the magic stack to dismiss the active module.
        if (ChromeFeatureList.sSafetyHubMagicStack.isEnabled()) {
            MagicStackBridge.getForProfile(getProfile()).dismissActiveModule();
        }
    }

    private PropertyModel getModulePropertyModel(@ModuleOption int option) {
        switch (option) {
            case ModuleOption.UPDATE_CHECK:
                return mUpdateCheckPropertyModel;
            case ModuleOption.ACCOUNT_PASSWORDS:
                return mPasswordCheckPropertyModel;
            case ModuleOption.SAFE_BROWSING:
                return mSafeBrowsingPropertyModel;
            case ModuleOption.UNUSED_PERMISSIONS:
                return mPermissionsModel;
            case ModuleOption.NOTIFICATION_REVIEW:
                return mNotificationsModel;
            default:
                throw new IllegalArgumentException();
        }
    }

    @Override
    public ObservableSupplier<String> getPageTitle() {
        return mPageTitle;
    }

    private void setUpBrowserStateModule() {
        CardPreference browserStatePreference = findPreference(PREF_BROWSER_STATE_INDICATOR);
        int compromisedPasswordsCount =
                UserPrefs.get(getProfile()).getInteger(Pref.BREACHED_CREDENTIALS_COUNT);
        int totalPasswordsCount = mDelegate.getAccountPasswordsCount(mPasswordStoreBridge);
        int sitesWithUnusedPermissionsCount =
                mUnusedSitePermissionsBridge.getRevokedPermissions().length;
        int notificationPermissionsForReviewCount =
                mNotificationPermissionReviewBridge.getNotificationPermissions().size();

        mBrowserStateModule =
                new PropertyModel.Builder(SafetyHubModuleProperties.BROWSER_STATE_MODULE_KEYS)
                        .with(SafetyHubModuleProperties.UPDATE_STATUS, mDelegate.getUpdateStatus())
                        .with(
                                SafetyHubModuleProperties.COMPROMISED_PASSWORDS_COUNT,
                                compromisedPasswordsCount)
                        .with(SafetyHubModuleProperties.TOTAL_PASSWORDS_COUNT, totalPasswordsCount)
                        .with(
                                SafetyHubModuleProperties.NOTIFICATION_PERMISSIONS_FOR_REVIEW_COUNT,
                                notificationPermissionsForReviewCount)
                        .with(
                                SafetyHubModuleProperties.SITES_WITH_UNUSED_PERMISSIONS_COUNT,
                                sitesWithUnusedPermissionsCount)
                        .with(
                                SafetyHubModuleProperties.SAFE_BROWSING_STATE,
                                SafetyHubUtils.getSafeBrowsingState(getProfile()))
                        .build();

        PropertyModelChangeProcessor.create(
                mBrowserStateModule,
                browserStatePreference,
                SafetyHubModuleViewBinder::bindBrowserStateProperties);
    }

    private void setUpAccountPasswordCheckModule() {
        SafetyHubExpandablePreference passwordCheckPreference = findPreference(PREF_PASSWORDS);

        mPasswordCheckPropertyModel =
                new PropertyModel.Builder(
                                SafetyHubModuleProperties.PASSWORD_CHECK_SAFETY_HUB_MODULE_KEYS)
                        .with(SafetyHubModuleProperties.IS_VISIBLE, true)
                        .build();

        PropertyModelChangeProcessor.create(
                mPasswordCheckPropertyModel,
                passwordCheckPreference,
                SafetyHubModuleViewBinder::bindPasswordCheckProperties);
        mSafetyHubFetchService.addObserver(this);
        mSigninManager.addSignInStateObserver(this);
        if (SafetyHubUtils.isSignedIn(getProfile())) {
            mPasswordStoreBridge = new PasswordStoreBridge(getProfile());
            mPasswordStoreBridge.addObserver(this, true);
        }
    }

    private void setUpUpdateCheckModule() {
        SafetyHubExpandablePreference updateCheckPreference = findPreference(PREF_UPDATE);

        mUpdateCheckPropertyModel =
                new PropertyModel.Builder(
                                SafetyHubModuleProperties.UPDATE_CHECK_SAFETY_HUB_MODULE_KEYS)
                        .with(SafetyHubModuleProperties.IS_VISIBLE, true)
                        .with(
                                SafetyHubModuleProperties.PRIMARY_BUTTON_LISTENER,
                                v -> {
                                    mDelegate.openGooglePlayStore(getContext());
                                    recordDashboardInteractions(
                                            DashboardInteractions.OPEN_PLAY_STORE);
                                })
                        .with(
                                SafetyHubModuleProperties.SAFE_STATE_BUTTON_LISTENER,
                                v -> {
                                    mDelegate.openGooglePlayStore(getContext());
                                    recordDashboardInteractions(
                                            DashboardInteractions.OPEN_PLAY_STORE);
                                })
                        .build();

        PropertyModelChangeProcessor.create(
                mUpdateCheckPropertyModel,
                updateCheckPreference,
                SafetyHubModuleViewBinder::bindUpdateCheckProperties);
    }

    private void setUpPermissionsRevocationModule() {
        SafetyHubExpandablePreference permissionsPreference =
                findPreference(PREF_UNUSED_PERMISSIONS);

        mPermissionsModel =
                new PropertyModel.Builder(SafetyHubModuleProperties.PERMISSIONS_MODULE_KEYS)
                        .with(SafetyHubModuleProperties.IS_VISIBLE, true)
                        .with(
                                SafetyHubModuleProperties.PRIMARY_BUTTON_LISTENER,
                                v -> {
                                    PermissionsData[] permissionsDataList =
                                            mUnusedSitePermissionsBridge.getRevokedPermissions();
                                    mUnusedSitePermissionsBridge
                                            .clearRevokedPermissionsReviewList();
                                    showSnackbar(
                                            getResources()
                                                    .getQuantityString(
                                                            R.plurals
                                                                    .safety_hub_multiple_permissions_snackbar,
                                                            permissionsDataList.length,
                                                            permissionsDataList.length),
                                            Snackbar.UMA_SAFETY_HUB_REGRANT_MULTIPLE_PERMISSIONS,
                                            new SnackbarManager.SnackbarController() {
                                                @Override
                                                public void onAction(Object actionData) {
                                                    mUnusedSitePermissionsBridge
                                                            .restoreRevokedPermissionsReviewList(
                                                                    (PermissionsData[]) actionData);
                                                    recordRevokedPermissionsInteraction(
                                                            PermissionsModuleInteractions
                                                                    .UNDO_ACKNOWLEDGE_ALL);
                                                }
                                            },
                                            permissionsDataList);
                                    recordRevokedPermissionsInteraction(
                                            PermissionsModuleInteractions.ACKNOWLEDGE_ALL);
                                })
                        .with(
                                SafetyHubModuleProperties.SECONDARY_BUTTON_LISTENER,
                                v -> {
                                    launchSettingsActivity(SafetyHubPermissionsFragment.class);
                                    recordRevokedPermissionsInteraction(
                                            PermissionsModuleInteractions.OPEN_REVIEW_UI);
                                })
                        .with(
                                SafetyHubModuleProperties.SAFE_STATE_BUTTON_LISTENER,
                                v -> {
                                    launchSettingsActivity(SiteSettings.class);
                                    recordRevokedPermissionsInteraction(
                                            PermissionsModuleInteractions.GO_TO_SETTINGS);
                                })
                        .build();

        PropertyModelChangeProcessor.create(
                mPermissionsModel,
                permissionsPreference,
                SafetyHubModuleViewBinder::bindPermissionsProperties);

        mUnusedSitePermissionsBridge.addObserver(this);
    }

    private void setUpNotificationsReviewModule() {
        SafetyHubExpandablePreference notificationsPreference =
                findPreference(PREF_NOTIFICATIONS_REVIEW);

        mNotificationsModel =
                new PropertyModel.Builder(
                                SafetyHubModuleProperties.NOTIFICATIONS_REVIEW_MODULE_KEYS)
                        .with(SafetyHubModuleProperties.IS_VISIBLE, true)
                        .with(
                                SafetyHubModuleProperties.PRIMARY_BUTTON_LISTENER,
                                v -> {
                                    List<NotificationPermissions> notificationPermissionsList =
                                            mNotificationPermissionReviewBridge
                                                    .getNotificationPermissions();
                                    mNotificationPermissionReviewBridge
                                            .bulkResetNotificationPermissions();
                                    showSnackbar(
                                            getResources()
                                                    .getQuantityString(
                                                            R.plurals
                                                                    .safety_hub_notifications_bulk_reset_snackbar,
                                                            notificationPermissionsList.size(),
                                                            notificationPermissionsList.size()),
                                            Snackbar.UMA_SAFETY_HUB_MULTIPLE_SITE_NOTIFICATIONS,
                                            new SnackbarManager.SnackbarController() {
                                                @Override
                                                public void onAction(Object actionData) {
                                                    mNotificationPermissionReviewBridge
                                                            .bulkAllowNotificationPermissions(
                                                                    (List<NotificationPermissions>)
                                                                            actionData);
                                                    recordNotificationsInteraction(
                                                            NotificationsModuleInteractions
                                                                    .UNDO_BLOCK_ALL);
                                                }
                                            },
                                            notificationPermissionsList);
                                    recordNotificationsInteraction(
                                            NotificationsModuleInteractions.BLOCK_ALL);
                                })
                        .with(
                                SafetyHubModuleProperties.SECONDARY_BUTTON_LISTENER,
                                v -> {
                                    launchSettingsActivity(SafetyHubNotificationsFragment.class);
                                    recordNotificationsInteraction(
                                            NotificationsModuleInteractions.OPEN_UI_REVIEW);
                                })
                        .with(
                                SafetyHubModuleProperties.SAFE_STATE_BUTTON_LISTENER,
                                v -> {
                                    launchSiteSettingsActivity(
                                            SiteSettingsCategory.Type.NOTIFICATIONS);
                                    recordNotificationsInteraction(
                                            NotificationsModuleInteractions.GO_TO_SETTINGS);
                                })
                        .build();

        PropertyModelChangeProcessor.create(
                mNotificationsModel,
                notificationsPreference,
                SafetyHubModuleViewBinder::bindNotificationsReviewProperties);

        mNotificationPermissionReviewBridge.addObserver(this);
    }

    private void setUpSafeBrowsingModule() {
        SafetyHubExpandablePreference safeBrowsingPreference = findPreference(PREF_SAFE_BROWSING);

        mSafeBrowsingPropertyModel =
                new PropertyModel.Builder(SafetyHubModuleProperties.SAFE_BROWSING_MODULE_KEYS)
                        .with(SafetyHubModuleProperties.IS_VISIBLE, true)
                        .with(
                                SafetyHubModuleProperties.PRIMARY_BUTTON_LISTENER,
                                v -> {
                                    launchSettingsActivity(SafeBrowsingSettingsFragment.class);
                                    recordDashboardInteractions(
                                            DashboardInteractions.GO_TO_SAFE_BROWSING_SETTINGS);
                                })
                        .with(
                                SafetyHubModuleProperties.SAFE_STATE_BUTTON_LISTENER,
                                v -> {
                                    launchSettingsActivity(SafeBrowsingSettingsFragment.class);
                                    recordDashboardInteractions(
                                            DashboardInteractions.GO_TO_SAFE_BROWSING_SETTINGS);
                                })
                        .build();

        PropertyModelChangeProcessor.create(
                mSafeBrowsingPropertyModel,
                safeBrowsingPreference,
                SafetyHubModuleViewBinder::bindSafeBrowsingProperties);
    }

    private void setUpSafetyTipsModule() {
        ExpandablePreferenceGroup safetyTipsPreference = findPreference(PREF_SAFETY_TIPS);
        safetyTipsPreference.setExpanded(false);

        findPreference(PREF_SAFETY_TIPS_SAFETY_TOOLS)
                .setOnPreferenceClickListener(
                        (preference) -> {
                            openUrlInCct(SAFETY_TOOLS_LEARN_MORE_URL);
                            recordDashboardInteractions(
                                    DashboardInteractions.OPEN_SAFETY_TOOLS_INFO);
                            return true;
                        });

        findPreference(PREF_SAFETY_TIPS_INCOGNITO)
                .setOnPreferenceClickListener(
                        (preference) -> {
                            openUrlInCct(INCOGNITO_LEARN_MORE_URL);
                            recordDashboardInteractions(DashboardInteractions.OPEN_INCOGNITO_INFO);
                            return true;
                        });

        findPreference(PREF_SAFETY_TIPS_SAFE_BROWSING)
                .setOnPreferenceClickListener(
                        (preference) -> {
                            openUrlInCct(SAFE_BROWSING_LEARN_MORE_URL);
                            recordDashboardInteractions(
                                    DashboardInteractions.OPEN_SAFE_BROWSING_INFO);
                            return true;
                        });
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        menu.clear();
        MenuItem help =
                menu.add(Menu.NONE, R.id.menu_id_targeted_help, Menu.NONE, R.string.menu_help);
        help.setIcon(
                TraceEventVectorDrawableCompat.create(
                        getResources(), R.drawable.ic_help_and_feedback, getActivity().getTheme()));
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == R.id.menu_id_targeted_help) {
            openUrlInCct(HELP_CENTER_URL);
            recordDashboardInteractions(DashboardInteractions.OPEN_HELP_CENTER);
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    /** Sets the {@link CustomTabIntentHelper} to open urls in CCT. */
    public void setCustomTabIntentHelper(CustomTabIntentHelper helper) {
        mCustomTabIntentHelper = helper;
    }

    private void openUrlInCct(String url) {
        assert (mCustomTabIntentHelper != null)
                : "CCT helpers must be set on SafetyHubFragment before opening a link";
        CustomTabsIntent customTabIntent =
                new CustomTabsIntent.Builder().setShowTitle(true).build();
        customTabIntent.intent.setData(Uri.parse(url));
        Intent intent =
                mCustomTabIntentHelper.createCustomTabActivityIntent(
                        getContext(), customTabIntent.intent);
        intent.setPackage(getContext().getPackageName());
        intent.putExtra(Browser.EXTRA_APPLICATION_ID, getContext().getPackageName());
        IntentUtils.addTrustedIntentExtras(intent);
        IntentUtils.safeStartActivity(getContext(), intent);
    }

    @Override
    public void onResume() {
        super.onResume();
        updateAllModules();

        // Fetch the passwords again to get the latest result.
        mSafetyHubFetchService.fetchBreachedCredentialsCount(success -> {});
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        recordAllModulesState(LifecycleEvent.ON_EXIT);

        mNotificationPermissionReviewBridge.removeObserver(this);
        mUnusedSitePermissionsBridge.removeObserver(this);
        mSafetyHubFetchService.removeObserver(this);
        mSigninManager.removeSignInStateObserver(this);
        if (mPasswordStoreBridge != null) {
            mPasswordStoreBridge.removeObserver(this);
        }
    }

    @Override
    public void revokedPermissionsChanged() {
        updatePermissionsPreference();
    }

    @Override
    public void notificationPermissionsChanged() {
        updateNotificationsReviewPreference();
    }

    @Override
    public void compromisedPasswordCountChanged() {
        updatePasswordCheckPreference();
    }

    @Override
    public void updateStatusChanged() {
        updateUpdateCheckPreference();
    }

    @Override
    public void onSavedPasswordsChanged(int count) {
        updatePasswordCheckPreference();
    }

    @Override
    public void onEdit(PasswordStoreCredential credential) {}

    @Override
    public void onSignedIn() {
        if (mPasswordStoreBridge == null) {
            mPasswordStoreBridge = new PasswordStoreBridge(getProfile());
            mPasswordStoreBridge.addObserver(this, true);
        }
        updatePasswordCheckPreference();
    }

    @Override
    public void onSignedOut() {
        if (mPasswordStoreBridge != null) {
            mPasswordStoreBridge.removeObserver(this);
            mPasswordStoreBridge = null;
        }
        updatePasswordCheckPreference();
    }

    private void updateAllModules() {
        updateUpdateCheckPreference();
        updatePasswordCheckPreference();
        updateSafeBrowsingPreference();
        updatePermissionsPreference();
        updateNotificationsReviewPreference();

        updateAllModulesExpandState();
    }

    private void updateAllModulesExpandState() {
        boolean hasNonManagedWarningState = hasNonManagedWarningState();

        for (@ModuleOption int i = ModuleOption.OPTION_FIRST; i < ModuleOption.NUM_ENTRIES; i++) {
            updateModuleExpandState(i, hasNonManagedWarningState);
        }
    }

    private boolean hasNonManagedWarningState() {
        for (@ModuleOption int i = ModuleOption.OPTION_FIRST; i < ModuleOption.NUM_ENTRIES; i++) {
            PropertyModel propertyModel = getModulePropertyModel(i);
            @ModuleState int moduleState = getModuleState(propertyModel, i);
            boolean managed = propertyModel.get(SafetyHubModuleProperties.IS_CONTROLLED_BY_POLICY);

            if (moduleState == ModuleState.WARNING && !managed) {
                return true;
            }
        }
        return false;
    }

    private void updateModuleExpandState(
            @ModuleOption int option, boolean hasNonManagedWarningState) {
        PropertyModel propertyModel = getModulePropertyModel(option);
        @ModuleState int moduleState = getModuleState(propertyModel, option);
        boolean managed = propertyModel.get(SafetyHubModuleProperties.IS_CONTROLLED_BY_POLICY);

        switch (moduleState) {
            case ModuleState.WARNING:
                propertyModel.set(
                        SafetyHubModuleProperties.IS_EXPANDED,
                        !managed || !hasNonManagedWarningState);
                break;
            case ModuleState.UNAVAILABLE:
            case ModuleState.INFO:
                propertyModel.set(
                        SafetyHubModuleProperties.IS_EXPANDED, !hasNonManagedWarningState);
                break;
            case ModuleState.SAFE:
                propertyModel.set(SafetyHubModuleProperties.IS_EXPANDED, false);
                break;
            default:
                throw new IllegalArgumentException();
        }
    }

    public void setDelegate(SafetyHubModuleDelegate safetyHubModuleDelegate) {
        mDelegate = safetyHubModuleDelegate;
    }

    private void updatePermissionsPreference() {
        int sitesWithUnusedPermissionsCount =
                mUnusedSitePermissionsBridge.getRevokedPermissions().length;
        mPermissionsModel.set(
                SafetyHubModuleProperties.SITES_WITH_UNUSED_PERMISSIONS_COUNT,
                sitesWithUnusedPermissionsCount);
        mBrowserStateModule.set(
                SafetyHubModuleProperties.SITES_WITH_UNUSED_PERMISSIONS_COUNT,
                sitesWithUnusedPermissionsCount);

        updateAllModulesExpandState();
    }

    private void updateNotificationsReviewPreference() {
        int notificationPermissionsForReviewCount =
                mNotificationPermissionReviewBridge.getNotificationPermissions().size();
        mNotificationsModel.set(
                SafetyHubModuleProperties.NOTIFICATION_PERMISSIONS_FOR_REVIEW_COUNT,
                notificationPermissionsForReviewCount);
        mBrowserStateModule.set(
                SafetyHubModuleProperties.NOTIFICATION_PERMISSIONS_FOR_REVIEW_COUNT,
                notificationPermissionsForReviewCount);

        updateAllModulesExpandState();
    }

    private void updateSafeBrowsingPreference() {
        @SafeBrowsingState int state = SafetyHubUtils.getSafeBrowsingState(getProfile());
        mSafeBrowsingPropertyModel.set(
                SafetyHubModuleProperties.IS_CONTROLLED_BY_POLICY,
                SafetyHubUtils.isSafeBrowsingManaged(getProfile()));
        mSafeBrowsingPropertyModel.set(SafetyHubModuleProperties.SAFE_BROWSING_STATE, state);
        mBrowserStateModule.set(SafetyHubModuleProperties.SAFE_BROWSING_STATE, state);

        updateAllModulesExpandState();
    }

    private void updatePasswordCheckPreference() {
        int compromisedPasswordsCount =
                UserPrefs.get(getProfile()).getInteger(Pref.BREACHED_CREDENTIALS_COUNT);
        int totalPasswordsCount = mDelegate.getAccountPasswordsCount(mPasswordStoreBridge);
        boolean isPasswordSavingEnabled =
                UserPrefs.get(getProfile()).getBoolean(Pref.CREDENTIALS_ENABLE_SERVICE);
        boolean disabledByPolicy =
                UserPrefs.get(getProfile()).isManagedPreference(Pref.CREDENTIALS_ENABLE_SERVICE)
                        && !isPasswordSavingEnabled;

        mPasswordCheckPropertyModel.set(
                SafetyHubModuleProperties.COMPROMISED_PASSWORDS_COUNT, compromisedPasswordsCount);
        mPasswordCheckPropertyModel.set(
                SafetyHubModuleProperties.TOTAL_PASSWORDS_COUNT, totalPasswordsCount);
        mPasswordCheckPropertyModel.set(
                SafetyHubModuleProperties.IS_CONTROLLED_BY_POLICY, disabledByPolicy);
        mPasswordCheckPropertyModel.set(
                SafetyHubModuleProperties.IS_SIGNED_IN, SafetyHubUtils.isSignedIn(getProfile()));
        mPasswordCheckPropertyModel.set(
                SafetyHubModuleProperties.ACCOUNT_EMAIL,
                SafetyHubUtils.getAccountEmail(getProfile()));
        if (SafetyHubUtils.isSignedIn(getProfile())) {
            mPasswordCheckPropertyModel.set(
                    SafetyHubModuleProperties.PRIMARY_BUTTON_LISTENER,
                    v -> {
                        mDelegate.showPasswordCheckUI(getContext());
                        recordDashboardInteractions(DashboardInteractions.OPEN_PASSWORD_MANAGER);
                    });
            mPasswordCheckPropertyModel.set(
                    SafetyHubModuleProperties.SAFE_STATE_BUTTON_LISTENER,
                    v -> {
                        mDelegate.showPasswordCheckUI(getContext());
                        recordDashboardInteractions(DashboardInteractions.OPEN_PASSWORD_MANAGER);
                    });
        } else {
            mPasswordCheckPropertyModel.set(
                    SafetyHubModuleProperties.SAFE_STATE_BUTTON_LISTENER,
                    v -> {
                        mDelegate.launchSyncOrSigninPromo(getContext());
                        recordDashboardInteractions(DashboardInteractions.SHOW_SIGN_IN_PROMO);
                    });
        }

        mBrowserStateModule.set(
                SafetyHubModuleProperties.COMPROMISED_PASSWORDS_COUNT, compromisedPasswordsCount);
        mBrowserStateModule.set(
                SafetyHubModuleProperties.TOTAL_PASSWORDS_COUNT, totalPasswordsCount);

        updateAllModulesExpandState();
    }

    private void updateUpdateCheckPreference() {
        UpdateStatusProvider.UpdateStatus updateStatus = mDelegate.getUpdateStatus();
        mUpdateCheckPropertyModel.set(SafetyHubModuleProperties.UPDATE_STATUS, updateStatus);
        mBrowserStateModule.set(SafetyHubModuleProperties.UPDATE_STATUS, updateStatus);

        updateAllModulesExpandState();
    }

    private void recordAllModulesState(@LifecycleEvent String event) {
        for (@ModuleOption int i = ModuleOption.OPTION_FIRST; i < ModuleOption.NUM_ENTRIES; i++) {
            @ModuleState int moduleState = getModuleState(getModulePropertyModel(i), i);
            recordModuleState(moduleState, getDashboardModuleTypeForModuleOption(i), event);
        }

        @ModuleState
        int browserState =
                isBrowserStateSafe(mBrowserStateModule) ? ModuleState.SAFE : ModuleState.WARNING;
        recordModuleState(browserState, DashboardModuleType.BROWSER_STATE, event);
    }
}