[go: up one dir, main page]

blob: d5246da5a2216698754943336f3bd57d02014234 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// 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.bookmarks;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.signin.SyncConsentActivityLauncherImpl;
import org.chromium.chrome.browser.signin.SyncPromoView;
import org.chromium.chrome.browser.signin.services.IdentityServicesProvider;
import org.chromium.chrome.browser.signin.services.ProfileDataCache;
import org.chromium.chrome.browser.signin.services.SigninManager;
import org.chromium.chrome.browser.signin.services.SigninManager.SignInStateObserver;
import org.chromium.chrome.browser.sync.SyncService;
import org.chromium.chrome.browser.ui.signin.PersonalizedSigninPromoView;
import org.chromium.chrome.browser.ui.signin.SigninPromoController;
import org.chromium.chrome.browser.ui.signin.SigninPromoController.SyncPromoState;
import org.chromium.components.signin.AccountManagerFacade;
import org.chromium.components.signin.AccountManagerFacadeProvider;
import org.chromium.components.signin.AccountsChangeObserver;
import org.chromium.components.signin.identitymanager.ConsentLevel;
import org.chromium.components.signin.metrics.SigninAccessPoint;
/**
* Class that manages all the logic and UI behind the signin promo header in the bookmark
* content UI. The header is shown only on certain situations, (e.g., not signed in).
*/
class BookmarkPromoHeader implements SyncService.SyncStateChangedListener, SignInStateObserver,
ProfileDataCache.Observer, AccountsChangeObserver {
// TODO(kkimlabs): Figure out the optimal number based on UMA data.
private static final int MAX_SIGNIN_AND_SYNC_PROMO_SHOW_COUNT = 10;
private static @Nullable @SyncPromoState Integer sPromoStateForTests;
private final Context mContext;
private final SigninManager mSignInManager;
private final AccountManagerFacade mAccountManagerFacade;
private final Runnable mPromoHeaderChangeAction;
private @Nullable ProfileDataCache mProfileDataCache;
private final @Nullable SigninPromoController mSigninPromoController;
private @SyncPromoState int mPromoState = SyncPromoState.NO_PROMO;
private final @Nullable SyncService mSyncService;
/**
* Initializes the class. Note that this will start listening to signin related events and
* update itself if needed.
*/
BookmarkPromoHeader(Context context, Runnable promoHeaderChangeAction) {
mContext = context;
mPromoHeaderChangeAction = promoHeaderChangeAction;
mSyncService = SyncService.get();
if (mSyncService != null) mSyncService.addSyncStateChangedListener(this);
mSignInManager = IdentityServicesProvider.get().getSigninManager(
Profile.getLastUsedRegularProfile());
mSignInManager.addSignInStateObserver(this);
mAccountManagerFacade = AccountManagerFacadeProvider.getInstance();
if (SigninPromoController.canShowSyncPromo(SigninAccessPoint.BOOKMARK_MANAGER)) {
mProfileDataCache = ProfileDataCache.createWithDefaultImageSizeAndNoBadge(mContext);
mProfileDataCache.addObserver(this);
mSigninPromoController = new SigninPromoController(
SigninAccessPoint.BOOKMARK_MANAGER, SyncConsentActivityLauncherImpl.get());
mAccountManagerFacade.addObserver(this);
} else {
mProfileDataCache = null;
mSigninPromoController = null;
}
updatePromoState();
}
/**
* Clean ups the class. Must be called once done using this class.
*/
void destroy() {
if (mSyncService != null) mSyncService.removeSyncStateChangedListener(this);
if (mSigninPromoController != null) {
mAccountManagerFacade.removeObserver(this);
mProfileDataCache.removeObserver(this);
}
mSignInManager.removeSignInStateObserver(this);
}
/**
* @return The current state of the promo.
*/
@SyncPromoState
int getPromoState() {
return mPromoState;
}
/**
* @return Personalized signin promo header {@link ViewHolder} instance that can be used with
* {@link RecyclerView}.
*/
ViewHolder createPersonalizedSigninAndSyncPromoHolder(ViewGroup parent) {
View view = LayoutInflater.from(mContext).inflate(
R.layout.personalized_signin_promo_view_bookmarks, parent, false);
// ViewHolder is abstract and it cannot be instantiated directly.
return new ViewHolder(view) {};
}
/**
* @return Sync promo header {@link ViewHolder} instance that can be used with
* {@link RecyclerView}.
*/
ViewHolder createSyncPromoHolder(ViewGroup parent) {
SyncPromoView view = SyncPromoView.create(parent, SigninAccessPoint.BOOKMARK_MANAGER);
// ViewHolder is abstract and it cannot be instantiated directly.
return new ViewHolder(view) {};
}
/**
* Sets up the sync promo view.
*/
void setUpSyncPromoView(PersonalizedSigninPromoView view) {
mSigninPromoController.setUpSyncPromoView(
mProfileDataCache, view, this::setPersonalizedSigninPromoDeclined);
}
/**
* Detaches the previously configured {@link PersonalizedSigninPromoView}.
*/
void detachPersonalizePromoView() {
if (mSigninPromoController != null) mSigninPromoController.detach();
}
/**
* Saves that the personalized signin promo was declined and updates the UI.
*/
private void setPersonalizedSigninPromoDeclined() {
mPromoState = calculatePromoState();
triggerPromoUpdate();
}
/**
* @return Whether the personalized signin promo should be shown to user.
*/
private boolean shouldShowBookmarkSigninPromo() {
return mSignInManager.isSyncOptInAllowed()
&& SigninPromoController.canShowSyncPromo(SigninAccessPoint.BOOKMARK_MANAGER);
}
private @SyncPromoState int calculatePromoState() {
if (sPromoStateForTests != null) {
return sPromoStateForTests;
}
if (mSyncService == null) {
// |mSyncService| will remain null until the next browser startup, so no sense in
// offering any promo.
return SyncPromoState.NO_PROMO;
}
if (!mSignInManager.getIdentityManager().hasPrimaryAccount(ConsentLevel.SYNC)) {
if (!shouldShowBookmarkSigninPromo()) {
return SyncPromoState.NO_PROMO;
}
return mSignInManager.getIdentityManager().hasPrimaryAccount(ConsentLevel.SIGNIN)
? SyncPromoState.PROMO_FOR_SIGNED_IN_STATE
: SyncPromoState.PROMO_FOR_SIGNED_OUT_STATE;
}
boolean impressionLimitNotReached =
SharedPreferencesManager.getInstance().readInt(
ChromePreferenceKeys.SIGNIN_AND_SYNC_PROMO_SHOW_COUNT)
< MAX_SIGNIN_AND_SYNC_PROMO_SHOW_COUNT;
if ((!mSyncService.isSyncRequested() || mSyncService.getChosenDataTypes().isEmpty())
&& impressionLimitNotReached) {
return SyncPromoState.PROMO_FOR_SYNC_TURNED_OFF_STATE;
}
return SyncPromoState.NO_PROMO;
}
private void updatePromoState() {
final @SyncPromoState int newState = calculatePromoState();
if (newState == mPromoState) return;
// PROMO_SYNC state and it's impression counts is not tracked by SigninPromoController.
final boolean hasSyncPromoStateChangedtoShown =
(mPromoState == SyncPromoState.NO_PROMO
|| mPromoState == SyncPromoState.PROMO_FOR_SYNC_TURNED_OFF_STATE)
&& (newState == SyncPromoState.PROMO_FOR_SIGNED_OUT_STATE
|| newState == SyncPromoState.PROMO_FOR_SIGNED_IN_STATE);
if (mSigninPromoController != null && hasSyncPromoStateChangedtoShown) {
mSigninPromoController.increasePromoShowCount();
}
if (newState == SyncPromoState.PROMO_FOR_SYNC_TURNED_OFF_STATE) {
SharedPreferencesManager.getInstance().incrementInt(
ChromePreferenceKeys.SIGNIN_AND_SYNC_PROMO_SHOW_COUNT);
}
mPromoState = newState;
}
// SyncService.SyncStateChangedListener implementation.
@Override
public void syncStateChanged() {
updatePromoState();
triggerPromoUpdate();
}
// SignInStateObserver implementation.
@Override
public void onSignedIn() {
updatePromoState();
triggerPromoUpdate();
}
@Override
public void onSignedOut() {
updatePromoState();
triggerPromoUpdate();
}
// ProfileDataCache.Observer implementation.
@Override
public void onProfileDataUpdated(String accountEmail) {
triggerPromoUpdate();
}
// AccountsChangeObserver implementation.
@Override
public void onAccountsChanged() {
triggerPromoUpdate();
}
private void triggerPromoUpdate() {
detachPersonalizePromoView();
mPromoHeaderChangeAction.run();
}
/**
* Forces the promo state to a particular value for testing purposes.
* @param promoState The promo state to which the header will be set to.
*/
@VisibleForTesting
static void forcePromoStateForTests(@Nullable @SyncPromoState Integer promoState) {
sPromoStateForTests = promoState;
}
}