chromium/chrome/android/java/src/org/chromium/chrome/browser/sync/settings/SyncErrorCardPreference.java

// Copyright 2020 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.sync.settings;

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;

import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;

import org.chromium.base.metrics.RecordHistogram;
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.ProfileDataCache;
import org.chromium.chrome.browser.sync.SyncServiceFactory;
import org.chromium.chrome.browser.sync.settings.SyncSettingsUtils.ErrorUiAction;
import org.chromium.chrome.browser.sync.settings.SyncSettingsUtils.SyncError;
import org.chromium.chrome.browser.ui.signin.PersonalizedSigninPromoView;
import org.chromium.components.signin.base.CoreAccountInfo;
import org.chromium.components.signin.identitymanager.ConsentLevel;
import org.chromium.components.signin.identitymanager.IdentityManager;
import org.chromium.components.sync.SyncService;

public class SyncErrorCardPreference extends Preference
        implements SyncService.SyncStateChangedListener, ProfileDataCache.Observer {
    /** Listener for the buttons in the error card. */
    public interface SyncErrorCardPreferenceListener {
        /**
         * Called to check if the preference should be hidden in case its created from signin
         * screen.
         */
        boolean shouldSuppressSyncSetupIncomplete();

        /** Called when the user clicks the primary button. */
        void onSyncErrorCardPrimaryButtonClicked();

        /**
         * Called when the user clicks the secondary button. This button is only shown for
         * {@link SyncError.SYNC_SETUP_INCOMPLETE} error.
         */
        void onSyncErrorCardSecondaryButtonClicked();
    }

    private ProfileDataCache mProfileDataCache;
    private Profile mProfile;
    private SyncService mSyncService;
    private IdentityManager mIdentityManager;
    private SyncErrorCardPreferenceListener mListener;
    private @SyncError int mSyncError;

    public SyncErrorCardPreference(Context context, AttributeSet attrs) {
        super(context, attrs);

        setLayoutResource(R.layout.sync_promo_view_settings);
        mSyncError = SyncError.NO_ERROR;
        setVisible(false);
    }

    /**
     * Initialize the dependencies for the SyncErrorCardPreference.
     *
     * <p>Must be called before the preference is attached, which is called from the containing
     * settings screen's onViewCreated method.
     */
    public void initialize(
            ProfileDataCache profileDataCache,
            Profile profile,
            SyncErrorCardPreferenceListener listener) {
        mProfileDataCache = profileDataCache;
        mProfile = profile;
        mSyncService = SyncServiceFactory.getForProfile(mProfile);
        mIdentityManager = IdentityServicesProvider.get().getIdentityManager(mProfile);
        mListener = listener;
    }

    @Override
    public void onAttached() {
        super.onAttached();
        mProfileDataCache.addObserver(this);
        if (mSyncService != null) {
            mSyncService.addSyncStateChangedListener(this);
        }
        update();
    }

    @Override
    public void onDetached() {
        super.onDetached();
        mProfileDataCache.removeObserver(this);
        if (mSyncService != null) {
            mSyncService.removeSyncStateChangedListener(this);
        }
    }

    @Override
    public void onBindViewHolder(PreferenceViewHolder holder) {
        super.onBindViewHolder(holder);

        if (mSyncError == SyncError.NO_ERROR) {
            return;
        }

        PersonalizedSigninPromoView errorCardView =
                (PersonalizedSigninPromoView) holder.findViewById(R.id.signin_promo_view_container);
        setupSyncErrorCardView(errorCardView);
    }

    private void update() {
        if (mSyncError == SyncSettingsUtils.getSyncError(mProfile)) {
            return;
        }

        mSyncError = SyncSettingsUtils.getSyncError(mProfile);
        boolean suppressSyncSetupIncompleteFromSigninPage =
                (mSyncError == SyncError.SYNC_SETUP_INCOMPLETE)
                        && mListener.shouldSuppressSyncSetupIncomplete();
        if (mSyncError == SyncError.NO_ERROR || suppressSyncSetupIncompleteFromSigninPage) {
            setVisible(false);
        } else {
            setVisible(true);
            notifyChanged();
            RecordHistogram.recordEnumeratedHistogram(
                    "Sync.SyncErrorCard" + SyncSettingsUtils.getHistogramSuffixForError(mSyncError),
                    ErrorUiAction.SHOWN,
                    ErrorUiAction.NUM_ENTRIES);
        }
    }

    private void setupSyncErrorCardView(PersonalizedSigninPromoView errorCardView) {
        String signedInAccount =
                CoreAccountInfo.getEmailFrom(
                        mIdentityManager.getPrimaryAccountInfo(ConsentLevel.SYNC));
        // May happen if account is removed from the device while this screen is shown.
        // ManageSyncSettings will take care of finishing the activity in such case.
        if (signedInAccount == null) {
            return;
        }
        Drawable accountImage =
                mProfileDataCache.getProfileDataOrDefault(signedInAccount).getImage();
        errorCardView.getImage().setImageDrawable(accountImage);

        errorCardView.getDismissButton().setVisibility(View.GONE);
        if (mSyncError == SyncError.SYNC_SETUP_INCOMPLETE) {
            errorCardView.getTitle().setVisibility(View.GONE);
        } else {
            errorCardView.getTitle().setVisibility(View.VISIBLE);
        }
        errorCardView
                .getTitle()
                .setText(SyncSettingsUtils.getSyncErrorCardTitle(getContext(), mSyncError));

        errorCardView
                .getDescription()
                .setText(SyncSettingsUtils.getSyncErrorHint(getContext(), mSyncError));

        errorCardView
                .getPrimaryButton()
                .setText(SyncSettingsUtils.getSyncErrorCardButtonLabel(getContext(), mSyncError));
        errorCardView
                .getPrimaryButton()
                .setOnClickListener(
                        v -> {
                            RecordHistogram.recordEnumeratedHistogram(
                                    "Sync.SyncErrorCard"
                                            + SyncSettingsUtils.getHistogramSuffixForError(
                                                    mSyncError),
                                    ErrorUiAction.BUTTON_CLICKED,
                                    ErrorUiAction.NUM_ENTRIES);
                            mListener.onSyncErrorCardPrimaryButtonClicked();
                        });
        if (mSyncError == SyncError.SYNC_SETUP_INCOMPLETE) {
            errorCardView
                    .getSecondaryButton()
                    .setOnClickListener(v -> mListener.onSyncErrorCardSecondaryButtonClicked());
            errorCardView.getSecondaryButton().setText(R.string.cancel);
        } else {
            errorCardView.getSecondaryButton().setVisibility(View.GONE);
        }
    }

    public @SyncError int getSyncError() {
        return mSyncError;
    }

    /** {@link SyncService.SyncStateChangedListener} implementation. */
    @Override
    public void syncStateChanged() {
        update();
    }

    /** {@link ProfileDataCache.Observer} implementation. */
    @Override
    public void onProfileDataUpdated(String accountEmail) {
        update();
    }
}