chromium/chrome/browser/safety_hub/android/java/src/org/chromium/chrome/browser/safety_hub/SafetyHubMagicStackMediator.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.recordExternalInteractions;

import android.content.Context;

import androidx.annotation.NonNull;

import org.chromium.base.Callback;
import org.chromium.base.supplier.Supplier;
import org.chromium.chrome.browser.magic_stack.ModuleDelegate;
import org.chromium.chrome.browser.magic_stack.ModuleDelegate.ModuleType;
import org.chromium.chrome.browser.preferences.Pref;
import org.chromium.chrome.browser.preferences.PrefChangeRegistrar;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.safe_browsing.settings.SafeBrowsingSettingsFragment;
import org.chromium.chrome.browser.safety_hub.SafetyHubMetricUtils.ExternalInteractions;
import org.chromium.chrome.browser.settings.SettingsLauncherFactory;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorObserver;
import org.chromium.components.browser_ui.settings.SettingsUtils;
import org.chromium.components.prefs.PrefService;
import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.ui.modelutil.PropertyModel;

/** Mediator for the Safety Hub Magic Stack module. */
class SafetyHubMagicStackMediator implements TabModelSelectorObserver, MagicStackBridge.Observer {
    private final Context mContext;
    private final Profile mProfile;
    private final PrefService mPrefService;
    private final PropertyModel mModel;
    private final MagicStackBridge mMagicStackBridge;
    private final TabModelSelector mTabModelSelector;
    private final ModuleDelegate mModuleDelegate;
    private final PrefChangeRegistrar mPrefChangeRegistrar;
    private final Supplier<ModalDialogManager> mModalDialogManagerSupplier;
    private final Callback<String> mShowSurveyCallback;

    private boolean mHasBeenDismissed;

    SafetyHubMagicStackMediator(
            @NonNull Context context,
            @NonNull Profile profile,
            @NonNull PrefService prefService,
            @NonNull PropertyModel model,
            @NonNull MagicStackBridge magicStackBridge,
            @NonNull TabModelSelector tabModelSelector,
            @NonNull ModuleDelegate moduleDelegate,
            @NonNull PrefChangeRegistrar prefChangeRegistrar,
            @NonNull Supplier<ModalDialogManager> modalDialogManagerSupplier,
            @NonNull Callback<String> showSurveyCallback) {
        mContext = context;
        mProfile = profile;
        mPrefService = prefService;
        mModel = model;
        mMagicStackBridge = magicStackBridge;
        mTabModelSelector = tabModelSelector;
        mModuleDelegate = moduleDelegate;
        mPrefChangeRegistrar = prefChangeRegistrar;
        mModalDialogManagerSupplier = modalDialogManagerSupplier;
        mShowSurveyCallback = showSurveyCallback;
    }

    void showModule() {
        if (mHasBeenDismissed) {
            return;
        }

        if (!mTabModelSelector.isTabStateInitialized()) {
            mTabModelSelector.addObserver(this);
            return;
        }

        MagicStackEntry magicStackEntry = mMagicStackBridge.getModuleToShow();

        if (magicStackEntry == null) {
            mModuleDelegate.onDataFetchFailed(ModuleType.SAFETY_HUB);
            return;
        }

        switch (magicStackEntry.getModuleType()) {
            case MagicStackEntry.ModuleType.NOTIFICATION_PERMISSIONS:
                bindNotificationReviewView(magicStackEntry.getDescription());
                break;
            case MagicStackEntry.ModuleType.REVOKED_PERMISSIONS:
                bindRevokedPermissionsView(magicStackEntry.getDescription());
                break;
            case MagicStackEntry.ModuleType.SAFE_BROWSING:
                bindSafeBrowsingView(magicStackEntry.getDescription());
                break;
            case MagicStackEntry.ModuleType.PASSWORDS:
                bindCompromisedPasswordsView(magicStackEntry.getDescription());
                break;
        }

        mModuleDelegate.onDataReady(ModuleType.SAFETY_HUB, mModel);

        // Add observers to dismiss the module if necessary.
        mMagicStackBridge.addObserver(this);
        if (magicStackEntry.getModuleType().equals(MagicStackEntry.ModuleType.SAFE_BROWSING)) {
            mPrefChangeRegistrar.addObserver(
                    Pref.SAFE_BROWSING_ENABLED, this::onSafeBrowsingChanged);
        } else if (magicStackEntry.getModuleType().equals(MagicStackEntry.ModuleType.PASSWORDS)) {
            mPrefChangeRegistrar.addObserver(
                    Pref.BREACHED_CREDENTIALS_COUNT, this::onCompromisedPasswordsCountChanged);
        }
    }

    void destroy() {
        mTabModelSelector.removeObserver(this);
        mMagicStackBridge.removeObserver(this);

        mPrefChangeRegistrar.removeObserver(Pref.SAFE_BROWSING_ENABLED);
        mPrefChangeRegistrar.removeObserver(Pref.BREACHED_CREDENTIALS_COUNT);
        mPrefChangeRegistrar.destroy();
    }

    int getModuleType() {
        return ModuleType.SAFETY_HUB;
    }

    @Override
    public void activeModuleDismissed() {
        if (!mHasBeenDismissed) {
            dismissModule();
        }
    }

    @Override
    public void onTabStateInitialized() {
        mTabModelSelector.removeObserver(this);
        showModule();
    }

    private void onSafeBrowsingChanged() {
        boolean isSafeBrowsingEnabled = mPrefService.getBoolean(Pref.SAFE_BROWSING_ENABLED);
        if (isSafeBrowsingEnabled && !mHasBeenDismissed) {
            mMagicStackBridge.dismissSafeBrowsingModule();
            dismissModule();
        }
    }

    private void onCompromisedPasswordsCountChanged() {
        boolean hasCompromisedPasswords =
                mPrefService.getInteger(Pref.BREACHED_CREDENTIALS_COUNT) > 0;
        if (!hasCompromisedPasswords && !mHasBeenDismissed) {
            mMagicStackBridge.dismissCompromisedPasswordsModule();
            dismissModule();
        }
    }

    private void dismissModule() {
        mHasBeenDismissed = true;
        mModuleDelegate.removeModule(ModuleType.SAFETY_HUB);
    }

    private void bindRevokedPermissionsView(@NonNull String title) {
        mModel.set(
                SafetyHubMagicStackViewProperties.HEADER,
                mContext.getResources().getString(R.string.safety_hub_magic_stack_module_name));
        mModel.set(SafetyHubMagicStackViewProperties.TITLE, title);
        mModel.set(
                SafetyHubMagicStackViewProperties.BUTTON_TEXT,
                mContext.getResources()
                        .getString(R.string.safety_hub_magic_stack_safe_state_button_text));
        mModel.set(
                SafetyHubMagicStackViewProperties.ICON_DRAWABLE,
                SettingsUtils.getTintedIcon(
                        mContext,
                        R.drawable.ic_check_circle_filled_green_24dp,
                        R.color.default_green));
        mModel.set(
                SafetyHubMagicStackViewProperties.BUTTON_ON_CLICK_LISTENER,
                (view) -> {
                    mShowSurveyCallback.onResult(MagicStackEntry.ModuleType.REVOKED_PERMISSIONS);
                    SettingsLauncherFactory.createSettingsLauncher()
                            .launchSettingsActivity(mContext, SafetyHubFragment.class);
                    recordExternalInteractions(ExternalInteractions.OPEN_FROM_MAGIC_STACK);
                });
    }

    private void bindNotificationReviewView(@NonNull String summary) {
        mModel.set(
                SafetyHubMagicStackViewProperties.HEADER,
                mContext.getResources().getString(R.string.safety_hub_magic_stack_module_name));
        mModel.set(
                SafetyHubMagicStackViewProperties.TITLE,
                mContext.getResources()
                        .getString(R.string.safety_hub_magic_stack_notifications_title));
        mModel.set(SafetyHubMagicStackViewProperties.SUMMARY, summary);
        mModel.set(
                SafetyHubMagicStackViewProperties.BUTTON_TEXT,
                mContext.getResources()
                        .getString(R.string.safety_hub_magic_stack_safe_state_button_text));
        mModel.set(
                SafetyHubMagicStackViewProperties.ICON_DRAWABLE,
                SettingsUtils.getTintedIcon(
                        mContext,
                        R.drawable.safety_hub_notifications_icon,
                        R.color.default_icon_color_accent1_tint_list));
        mModel.set(
                SafetyHubMagicStackViewProperties.BUTTON_ON_CLICK_LISTENER,
                (view) -> {
                    mShowSurveyCallback.onResult(
                            MagicStackEntry.ModuleType.NOTIFICATION_PERMISSIONS);
                    SettingsLauncherFactory.createSettingsLauncher()
                            .launchSettingsActivity(mContext, SafetyHubFragment.class);
                    recordExternalInteractions(ExternalInteractions.OPEN_FROM_MAGIC_STACK);
                });
    }

    private void bindSafeBrowsingView(@NonNull String summary) {
        mModel.set(
                SafetyHubMagicStackViewProperties.HEADER,
                mContext.getResources().getString(R.string.safety_hub_magic_stack_module_name));
        mModel.set(
                SafetyHubMagicStackViewProperties.TITLE,
                mContext.getResources()
                        .getString(R.string.safety_hub_magic_stack_safe_browsing_title));
        mModel.set(SafetyHubMagicStackViewProperties.SUMMARY, summary);
        mModel.set(
                SafetyHubMagicStackViewProperties.BUTTON_TEXT,
                mContext.getResources()
                        .getString(R.string.safety_hub_magic_stack_safe_browsing_button_text));
        mModel.set(
                SafetyHubMagicStackViewProperties.ICON_DRAWABLE,
                SettingsUtils.getTintedIcon(
                        mContext,
                        R.drawable.ic_gshield_24,
                        R.color.default_icon_color_accent1_tint_list));
        mModel.set(
                SafetyHubMagicStackViewProperties.BUTTON_ON_CLICK_LISTENER,
                (view) -> {
                    mShowSurveyCallback.onResult(MagicStackEntry.ModuleType.SAFE_BROWSING);
                    SettingsLauncherFactory.createSettingsLauncher()
                            .launchSettingsActivity(mContext, SafeBrowsingSettingsFragment.class);
                    recordExternalInteractions(
                            ExternalInteractions.OPEN_SAFE_BROWSING_FROM_MAGIC_STACK);
                    mMagicStackBridge.dismissSafeBrowsingModule();
                    dismissModule();
                });
    }

    private void bindCompromisedPasswordsView(String summary) {
        mModel.set(
                SafetyHubMagicStackViewProperties.HEADER,
                mContext.getResources().getString(R.string.safety_hub_magic_stack_module_name));
        mModel.set(
                SafetyHubMagicStackViewProperties.TITLE,
                mContext.getResources()
                        .getString(R.string.safety_hub_magic_stack_compromised_passwords_title));
        mModel.set(SafetyHubMagicStackViewProperties.SUMMARY, summary);
        mModel.set(
                SafetyHubMagicStackViewProperties.BUTTON_TEXT,
                mContext.getResources()
                        .getString(R.string.safety_hub_magic_stack_compromised_passwords_title));
        mModel.set(
                SafetyHubMagicStackViewProperties.ICON_DRAWABLE,
                SettingsUtils.getTintedIcon(
                        mContext,
                        R.drawable.ic_password_manager_key,
                        R.color.default_icon_color_accent1_tint_list));
        mModel.set(
                SafetyHubMagicStackViewProperties.BUTTON_ON_CLICK_LISTENER,
                (view) -> {
                    mShowSurveyCallback.onResult(MagicStackEntry.ModuleType.PASSWORDS);
                    SafetyHubUtils.showPasswordCheckUI(
                            mContext, mProfile, mModalDialogManagerSupplier);
                    recordExternalInteractions(ExternalInteractions.OPEN_GPM_FROM_MAGIC_STACK);
                    mMagicStackBridge.dismissCompromisedPasswordsModule();
                    dismissModule();
                });
    }
}