blob: f30a3271a5fdeaa54a93ca4ab3b28f2d3fbec044 [file] [log] [blame]
// Copyright 2020 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.signin.account_picker;
import android.accounts.Account;
import android.content.Context;
import android.text.TextUtils;
import androidx.annotation.Nullable;
import org.chromium.base.task.AsyncTask;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.signin.ProfileDataCache;
import org.chromium.chrome.browser.signin.account_picker.AccountPickerBottomSheetProperties.ViewState;
import org.chromium.components.signin.AccountManagerFacade;
import org.chromium.components.signin.AccountManagerFacadeProvider;
import org.chromium.components.signin.AccountUtils;
import org.chromium.components.signin.AccountsChangeObserver;
import org.chromium.components.signin.base.CoreAccountId;
import org.chromium.components.signin.base.CoreAccountInfo;
import org.chromium.components.signin.base.GoogleServiceAuthError;
import org.chromium.components.signin.base.GoogleServiceAuthError.State;
import org.chromium.ui.modelutil.PropertyModel;
import java.util.Collections;
import java.util.List;
/**
* Mediator of the account picker bottom sheet in web sign-in flow.
*/
class AccountPickerBottomSheetMediator implements AccountPickerCoordinator.Listener,
AccountPickerBottomSheetView.BackPressListener {
private final AccountPickerDelegate mAccountPickerDelegate;
private final ProfileDataCache mProfileDataCache;
private final PropertyModel mModel;
private final ProfileDataCache.Observer mProfileDataSourceObserver =
this::updateSelectedAccountData;
private final AccountManagerFacade mAccountManagerFacade;
private final AccountsChangeObserver mAccountsChangeObserver = this::onAccountListUpdated;
private @Nullable String mSelectedAccountName;
AccountPickerBottomSheetMediator(Context context, AccountPickerDelegate accountPickerDelegate) {
mAccountPickerDelegate = accountPickerDelegate;
mProfileDataCache = new ProfileDataCache(
context, context.getResources().getDimensionPixelSize(R.dimen.user_picture_size));
mModel = AccountPickerBottomSheetProperties.createModel(
this::onSelectedAccountClicked, this::onContinueAsClicked);
mProfileDataCache.addObserver(mProfileDataSourceObserver);
mAccountManagerFacade = AccountManagerFacadeProvider.getInstance();
mAccountManagerFacade.addObserver(mAccountsChangeObserver);
onAccountListUpdated();
}
/**
* Notifies that the user has selected an account.
*
* @param accountName The email of the selected account.
* @param isDefaultAccount Whether the selected account is the first in the account list.
*
* TODO(https://crbug.com/1115965): Use CoreAccountInfo instead of account's email
* as the first argument of the method.
*/
@Override
public void onAccountSelected(String accountName, boolean isDefaultAccount) {
// Clicking on one account in the account list when the account list is expanded
// will collapse it to the selected account
mModel.set(AccountPickerBottomSheetProperties.VIEW_STATE, ViewState.COLLAPSED_ACCOUNT_LIST);
setSelectedAccountName(accountName);
}
/**
* Notifies when the user clicked the "add account" button.
*/
@Override
public void addAccount() {
mAccountPickerDelegate.addAccount(accountName -> onAccountSelected(accountName, false));
}
/**
* Notifies when the user clicked the "Go Incognito mode" button.
*/
@Override
public void goIncognitoMode() {
mModel.set(AccountPickerBottomSheetProperties.VIEW_STATE, ViewState.INCOGNITO_INTERSTITIAL);
}
/**
* Notifies when user clicks the back-press button.
*
* @return true if the listener handles the back press, false if not.
*/
@Override
public boolean onBackPressed() {
@ViewState
int viewState = mModel.get(AccountPickerBottomSheetProperties.VIEW_STATE);
if (viewState == ViewState.EXPANDED_ACCOUNT_LIST) {
mModel.set(AccountPickerBottomSheetProperties.VIEW_STATE,
ViewState.COLLAPSED_ACCOUNT_LIST);
return true;
} else if (viewState == ViewState.INCOGNITO_INTERSTITIAL) {
mModel.set(
AccountPickerBottomSheetProperties.VIEW_STATE, ViewState.EXPANDED_ACCOUNT_LIST);
return true;
} else {
return false;
}
}
PropertyModel getModel() {
return mModel;
}
void destroy() {
mAccountPickerDelegate.onDismiss();
mProfileDataCache.removeObserver(mProfileDataSourceObserver);
mAccountManagerFacade.removeObserver(mAccountsChangeObserver);
}
/**
* Updates the collapsed account list when account list changes.
*
* Implements {@link AccountsChangeObserver}.
*/
private void onAccountListUpdated() {
List<Account> accounts = mAccountManagerFacade.tryGetGoogleAccounts();
if (accounts.isEmpty()) {
// If all accounts disappeared, no matter if the account list is collapsed or expanded,
// we will go to the zero account screen.
mModel.set(AccountPickerBottomSheetProperties.VIEW_STATE, ViewState.NO_ACCOUNTS);
mSelectedAccountName = null;
mModel.set(AccountPickerBottomSheetProperties.SELECTED_ACCOUNT_DATA, null);
return;
}
@ViewState
int viewState = mModel.get(AccountPickerBottomSheetProperties.VIEW_STATE);
if (viewState == ViewState.NO_ACCOUNTS) {
// When a non-empty account list appears while it is currently zero-account screen,
// we should change the screen to collapsed account list and set the selected account
// to the first account of the account list
setSelectedAccountName(accounts.get(0).name);
mModel.set(AccountPickerBottomSheetProperties.VIEW_STATE,
ViewState.COLLAPSED_ACCOUNT_LIST);
} else if (viewState == ViewState.COLLAPSED_ACCOUNT_LIST
&& AccountUtils.findAccountByName(accounts, mSelectedAccountName) == null) {
// When it is already collapsed account list, we update the selected account only
// when the current selected account name is no longer in the new account list
setSelectedAccountName(accounts.get(0).name);
}
}
private void setSelectedAccountName(String accountName) {
mSelectedAccountName = accountName;
mProfileDataCache.update(Collections.singletonList(mSelectedAccountName));
updateSelectedAccountData(mSelectedAccountName);
}
/**
* Implements {@link ProfileDataCache.Observer}.
*/
private void updateSelectedAccountData(String accountName) {
if (TextUtils.equals(mSelectedAccountName, accountName)) {
mModel.set(AccountPickerBottomSheetProperties.SELECTED_ACCOUNT_DATA,
mProfileDataCache.getProfileDataOrDefault(accountName));
}
}
/**
* Callback for the PropertyKey
* {@link AccountPickerBottomSheetProperties#ON_SELECTED_ACCOUNT_CLICKED}.
*/
private void onSelectedAccountClicked() {
// Clicking on the selected account when the account list is collapsed will expand the
// account list and make the account list visible
mModel.set(AccountPickerBottomSheetProperties.VIEW_STATE, ViewState.EXPANDED_ACCOUNT_LIST);
}
/**
* Callback for the PropertyKey
* {@link AccountPickerBottomSheetProperties#ON_CONTINUE_AS_CLICKED}.
*/
private void onContinueAsClicked() {
if (mSelectedAccountName == null) {
addAccount();
} else {
mModel.set(AccountPickerBottomSheetProperties.VIEW_STATE, ViewState.SIGNIN_IN_PROGRESS);
new AsyncTask<String>() {
@Override
protected String doInBackground() {
return mAccountManagerFacade.getAccountGaiaId(mSelectedAccountName);
}
@Override
protected void onPostExecute(String accountGaiaId) {
CoreAccountInfo coreAccountInfo = new CoreAccountInfo(
new CoreAccountId(accountGaiaId), mSelectedAccountName, accountGaiaId);
mAccountPickerDelegate.signIn(
coreAccountInfo, AccountPickerBottomSheetMediator.this::onSignInError);
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
private void onSignInError(GoogleServiceAuthError error) {
if (error.getState() == State.INVALID_GAIA_CREDENTIALS) {
mModel.set(AccountPickerBottomSheetProperties.VIEW_STATE, ViewState.SIGNIN_AUTH_ERROR);
} else {
mModel.set(
AccountPickerBottomSheetProperties.VIEW_STATE, ViewState.SIGNIN_GENERAL_ERROR);
}
}
}