chromium/chrome/android/java/src/org/chromium/chrome/browser/firstrun/SyncConsentFirstRunFragment.java

// Copyright 2018 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.firstrun;

import android.content.Context;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import org.chromium.chrome.R;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.signin.services.IdentityServicesProvider;
import org.chromium.chrome.browser.signin.services.SigninManager;
import org.chromium.chrome.browser.signin.services.SigninPreferencesManager;
import org.chromium.chrome.browser.ui.signin.SyncConsentDelegate;
import org.chromium.chrome.browser.ui.signin.SyncConsentFragmentBase;
import org.chromium.components.signin.AccountManagerFacadeProvider;
import org.chromium.components.signin.AccountUtils;
import org.chromium.components.signin.base.CoreAccountInfo;
import org.chromium.components.signin.identitymanager.ConsentLevel;
import org.chromium.components.signin.metrics.SigninAccessPoint;
import org.chromium.ui.base.WindowAndroid;

import java.util.List;

/** Implementation of {@link SyncConsentFragmentBase} for the first run experience. */
public class SyncConsentFirstRunFragment extends SyncConsentFragmentBase
        implements FirstRunFragment {
    // Per-page parameters:
    // TODO(crbug.com/40165044): Remove IS_CHILD_ACCOUNT
    public static final String IS_CHILD_ACCOUNT = "IsChildAccount";

    private final SyncConsentDelegate mSyncConsentDelegate;

    // Do not remove. Empty fragment constructor is required for re-creating the fragment from a
    // saved state bundle. See crbug.com/1225102
    public SyncConsentFirstRunFragment() {
        mSyncConsentDelegate =
                new SyncConsentDelegate() {
                    @Nullable
                    @Override
                    public WindowAndroid getWindowAndroid() {
                        FirstRunPageDelegate delegate = getPageDelegate();
                        if (delegate == null) return null;
                        return delegate.getWindowAndroid();
                    }

                    @Nullable
                    @Override
                    public Profile getProfile() {
                        FirstRunPageDelegate delegate = getPageDelegate();
                        if (delegate == null) return null;
                        return delegate.getProfileProviderSupplier().get().getOriginalProfile();
                    }
                };
    }

    @NonNull
    @Override
    protected SyncConsentDelegate getDelegate() {
        return mSyncConsentDelegate;
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        final @Nullable CoreAccountInfo defaultAccount =
                AccountUtils.getDefaultCoreAccountInfoIfFulfilled(
                        AccountManagerFacadeProvider.getInstance().getCoreAccountInfos());
        final @Nullable String accountEmail =
                defaultAccount == null ? null : defaultAccount.getEmail();
        boolean isChild = getPageDelegate().getProperties().getBoolean(IS_CHILD_ACCOUNT, false);
        // TODO(crbug.com/40285057): Avoid sending `accountEmail` to create arguments. This class
        // uses the primary account from IdentityManager.
        setArguments(createArguments(SigninAccessPoint.START_PAGE, accountEmail, isChild));
    }

    @Override
    protected void onSyncRefused() {
        SigninPreferencesManager.getInstance().temporarilySuppressNewTabPagePromos();
        getPageDelegate().recordFreProgressHistogram(MobileFreProgress.SYNC_CONSENT_DISMISSED);
        getPageDelegate().advanceToNextPage();
    }

    @Override
    protected void onSyncAccepted(
            String accountName, boolean settingsClicked, SigninManager.SignInCallback callback) {
        // TODO(crbug.com/40217047): Once ENABLE_SYNC_IMMEDIATELY_IN_FRE launches, move these
        // metrics
        // elsewhere, so onSyncAccepted() is replaced with signinAndEnableSync() (common code).
        getPageDelegate().recordFreProgressHistogram(MobileFreProgress.SYNC_CONSENT_ACCEPTED);
        if (settingsClicked) {
            getPageDelegate()
                    .recordFreProgressHistogram(MobileFreProgress.SYNC_CONSENT_SETTINGS_LINK_CLICK);
        }

        // Enable sync now. Only call FirstRunSignInProcessor.scheduleOpeningSettings() later in
        // closeAndMaybeOpenSyncSettings(), because settings shouldn't open if
        // signinAndEnableSync() fails.
        if (!getPageDelegate().getProperties().getBoolean(IS_CHILD_ACCOUNT, false)) {
            signinAndEnableSync(accountName, settingsClicked, callback);
            return;
        }

        // Special case for child accounts. In rare cases, e.g. if Terms & Conditions is clicked,
        // SigninChecker might have been triggered before the FRE ends and started sign-in (the
        // ConsentLevel depends on AllowSyncOffForChildAccounts). In doubt, wait.
        Profile profile = getPageDelegate().getProfileProviderSupplier().get().getOriginalProfile();
        IdentityServicesProvider.get()
                .getSigninManager(profile)
                .runAfterOperationInProgress(
                        () -> {
                            CoreAccountInfo syncingAccount =
                                    IdentityServicesProvider.get()
                                            .getIdentityManager(profile)
                                            .getPrimaryAccountInfo(ConsentLevel.SYNC);
                            if (syncingAccount == null) {
                                signinAndEnableSync(accountName, settingsClicked, callback);
                                return;
                            }

                            if (!accountName.equals(syncingAccount.getEmail())) {
                                throw new IllegalStateException(
                                        "Child accounts should only be allowed to sync with a"
                                                + " single account");
                            }

                            // SigninChecker enabled sync already. Just open settings if needed.
                            closeAndMaybeOpenSyncSettings(settingsClicked);
                            callback.onSignInComplete();
                        });
    }

    @Override
    protected void closeAndMaybeOpenSyncSettings(boolean settingsClicked) {
        // Now that signinAndEnableSync() succeeded, signal whether FirstRunSignInProcessor should
        // open settings.
        if (settingsClicked) {
            FirstRunSignInProcessor.scheduleOpeningSettings();
        }
        getPageDelegate().advanceToNextPage();
    }

    @Override
    public void setInitialA11yFocus() {
        // Ignore calls before view is created.
        if (getView() == null) return;

        View title = getView().findViewById(R.id.signin_title);
        title.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
    }

    @Override
    protected void updateAccounts(List<CoreAccountInfo> coreAccountInfos) {
        final boolean selectedAccountDoesNotExist =
                (mSelectedAccountEmail != null
                        && AccountUtils.findCoreAccountInfoByEmail(
                                        coreAccountInfos, mSelectedAccountEmail)
                                == null);
        if (selectedAccountDoesNotExist) {
            // With MICe, there's no account picker and the sync consent is fixed for the signed
            // in account on welcome screen. If the signed-in account is removed, this page
            // no longer makes sense, so we abort the FRE here to allow users to restart FRE.
            getPageDelegate().abortFirstRunExperience();
            return;
        }
        super.updateAccounts(coreAccountInfos);
    }
}