chromium/components/account_manager_core/account_manager_facade_impl.h

// 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.

#ifndef COMPONENTS_ACCOUNT_MANAGER_CORE_ACCOUNT_MANAGER_FACADE_IMPL_H_
#define COMPONENTS_ACCOUNT_MANAGER_CORE_ACCOUNT_MANAGER_FACADE_IMPL_H_

#include <memory>
#include <string>

#include "base/component_export.h"
#include "base/functional/callback_forward.h"
#include "base/functional/callback_helpers.h"
#include "base/gtest_prod_util.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "chromeos/crosapi/mojom/account_manager.mojom.h"
#include "components/account_manager_core/account_manager_facade.h"
#include "components/account_manager_core/account_upsertion_result.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"

class OAuth2AccessTokenFetcher;
class OAuth2AccessTokenConsumer;

namespace account_manager {

class AccountManager;

// ChromeOS-specific implementation of |AccountManagerFacade| that talks to
// |account_manager::AccountManager| over Mojo. Used by both Lacros and Ash.
class COMPONENT_EXPORT(ACCOUNT_MANAGER_CORE) AccountManagerFacadeImpl
    : public AccountManagerFacade,
      public crosapi::mojom::AccountManagerObserver {
 public:
  // Constructs `AccountManagerFacadeImpl`.
  // `account_manager_remote` is a Mojo `Remote` to Account Manager in Ash -
  // either in-process or out-of-process.
  // `remote_version` is the Mojo API version of the remote.
  // `init_finished` is called after `this` has been fully initialized.
  AccountManagerFacadeImpl(
      mojo::Remote<crosapi::mojom::AccountManager> account_manager_remote,
      uint32_t remote_version,
      base::WeakPtr<AccountManager> account_manager_for_tests,
      base::OnceClosure init_finished = base::DoNothing());
  AccountManagerFacadeImpl(const AccountManagerFacadeImpl&) = delete;
  AccountManagerFacadeImpl& operator=(const AccountManagerFacadeImpl&) = delete;
  ~AccountManagerFacadeImpl() override;

  // AccountManagerFacade overrides:
  void AddObserver(Observer* observer) override;
  void RemoveObserver(Observer* observer) override;
  void GetAccounts(
      base::OnceCallback<void(const std::vector<Account>&)> callback) override;
  void GetPersistentErrorForAccount(
      const AccountKey& account,
      base::OnceCallback<void(const GoogleServiceAuthError&)> callback)
      override;
  void ShowAddAccountDialog(AccountAdditionSource source) override;
  void ShowAddAccountDialog(
      AccountAdditionSource source,
      base::OnceCallback<void(const account_manager::AccountUpsertionResult&
                                  result)> callback) override;
  void ShowReauthAccountDialog(
      AccountAdditionSource source,
      const std::string& email,
      base::OnceCallback<void(const account_manager::AccountUpsertionResult&
                                  result)> callback) override;
  void ShowManageAccountsSettings() override;
  std::unique_ptr<OAuth2AccessTokenFetcher> CreateAccessTokenFetcher(
      const AccountKey& account,
      OAuth2AccessTokenConsumer* consumer) override;
  void ReportAuthError(const account_manager::AccountKey& account,
                       const GoogleServiceAuthError& error) override;
  void UpsertAccountForTesting(const Account& account,
                               const std::string& token_value) override;
  void RemoveAccountForTesting(const AccountKey& account) override;

  // crosapi::mojom::AccountManagerObserver overrides:
  void OnTokenUpserted(crosapi::mojom::AccountPtr account) override;
  void OnAccountRemoved(crosapi::mojom::AccountPtr account) override;
  void OnAuthErrorChanged(
      crosapi::mojom::AccountKeyPtr account,
      crosapi::mojom::GoogleServiceAuthErrorPtr error) override;
  void OnSigninDialogClosed() override;

 private:
  FRIEND_TEST_ALL_PREFIXES(AccountManagerFacadeImplTest,
                           ShowAddAccountDialogCallsMojo);
  FRIEND_TEST_ALL_PREFIXES(AccountManagerFacadeImplTest,
                           GetAccountsHangsWhenRemoteIsNull);
  FRIEND_TEST_ALL_PREFIXES(AccountManagerFacadeImplTest,
                           ShowAddAccountDialogUMA);
  FRIEND_TEST_ALL_PREFIXES(AccountManagerFacadeImplTest,
                           ShowReauthAccountDialogCallsMojo);
  FRIEND_TEST_ALL_PREFIXES(
      AccountManagerFacadeImplTest,
      ShowAddAccountDialogSetsCorrectOptionsForAdditionFromAsh);
  FRIEND_TEST_ALL_PREFIXES(
      AccountManagerFacadeImplTest,
      ShowAddAccountDialogSetsCorrectOptionsForAdditionFromLacros);
  FRIEND_TEST_ALL_PREFIXES(
      AccountManagerFacadeImplTest,
      ShowAddAccountDialogSetsCorrectOptionsForAdditionFromArc);
  FRIEND_TEST_ALL_PREFIXES(AccountManagerFacadeImplTest,
                           ShowReauthAccountDialogUMA);
  FRIEND_TEST_ALL_PREFIXES(AccountManagerFacadeImplTest,
                           ShowManageAccountsSettingsCallsMojo);
  FRIEND_TEST_ALL_PREFIXES(AccountManagerFacadeImplTest,
                           InitializationStatusIsCorrectlySet);
  FRIEND_TEST_ALL_PREFIXES(
      AccountManagerFacadeImplTest,
      AccessTokenFetcherCanBeCreatedBeforeAccountManagerFacadeInitialization);
  FRIEND_TEST_ALL_PREFIXES(AccountManagerFacadeImplTest,
                           HistogramsForZeroAccountManagerRemoteDisconnections);
  FRIEND_TEST_ALL_PREFIXES(AccountManagerFacadeImplTest,
                           HistogramsForAccountManagerRemoteDisconnection);
  FRIEND_TEST_ALL_PREFIXES(
      AccountManagerFacadeImplTest,
      HistogramsForZeroAccountManagerObserverReceiverDisconnections);
  FRIEND_TEST_ALL_PREFIXES(
      AccountManagerFacadeImplTest,
      HistogramsForAccountManagerObserverReceiverDisconnections);

  // Status of the mojo connection.
  // These values are persisted to logs. Entries should not be renumbered and
  // numeric values should never be reused.
  enum class FacadeMojoStatus {
    kOk = 0,
    kUninitialized = 1,
    kNoRemote = 2,
    kVersionMismatch = 3,

    kMaxValue = kVersionMismatch
  };

  static std::string GetAccountUpsertionResultStatusHistogramNameForTesting();
  static std::string GetAccountsMojoStatusHistogramNameForTesting();

  // A utility class to fetch access tokens over Mojo.
  class AccessTokenFetcher;

  void OnReceiverReceived(
      mojo::PendingReceiver<AccountManagerObserver> receiver);
  // Callback for `crosapi::mojom::AccountManager::ShowAddAccountDialog`.
  void OnSigninDialogActionFinished(
      base::OnceCallback<
          void(const account_manager::AccountUpsertionResult& result)> callback,
      crosapi::mojom::AccountUpsertionResultPtr mojo_result);
  void FinishUpsertAccount(
      base::OnceCallback<
          void(const account_manager::AccountUpsertionResult& result)> callback,
      const account_manager::AccountUpsertionResult& result);

  void GetAccountsInternal(
      base::OnceCallback<void(const std::vector<Account>&)> callback);

  void GetPersistentErrorInternal(
      const AccountKey& account,
      base::OnceCallback<void(const GoogleServiceAuthError&)> callback);

  // Proxy method to call `CreateAccessTokenFetcher` on
  // `account_manager_remote_`. Returns `true` if `account_manager_remote_` is
  // bound and the call was queued successfully.
  bool CreateAccessTokenFetcher(
      crosapi::mojom::AccountKeyPtr account_key,
      const std::string& oauth_consumer_name,
      crosapi::mojom::AccountManager::CreateAccessTokenFetcherCallback
          callback);

  // The initialization sequence for `AccountManagerFacadeImpl` consists of
  // adding an observer to the remote.
  //
  // Remote-querying methods like `GetAccounts` won't actually produce a remote
  // call until the initialization sequence is finished (instead, they will be
  // queued in `initialization_callbacks_`).
  //
  // `FinishInitSequenceIfNotAlreadyFinished` invokes callbacks from
  // `initialization_callbacks_` and marks the initialization as finished.
  void FinishInitSequenceIfNotAlreadyFinished();
  // `closure` will be invoked after the initialization sequence is finished.
  // See `FinishInitSequenceIfNotAlreadyFinished` for details.
  void RunAfterInitializationSequence(base::OnceClosure closure);

  // Runs `closure` if/when `account_manager_remote_` gets disconnected.
  void RunOnAccountManagerRemoteDisconnection(base::OnceClosure closure);

  // Mojo disconnect handler for `account_manager_remote_`.
  void OnAccountManagerRemoteDisconnected();

  // Mojo disconnect handler for `receiver_`.
  void OnAccountManagerObserverReceiverDisconnected();

  bool IsInitialized();

  void FlushMojoForTesting();

  // Mojo API version on the remote (Ash) side.
  const uint32_t remote_version_;

  // Number of Mojo pipe disconnections seen by `account_manager_remote_`.
  int num_remote_disconnections_ = 0;

  // Number of Mojo pipe disconnections seen by `receiver_`.
  int num_receiver_disconnections_ = 0;

  bool is_initialized_ = false;
  std::vector<base::OnceClosure> initialization_callbacks_;
  std::vector<base::OnceClosure> account_manager_remote_disconnection_handlers_;

  mojo::Remote<crosapi::mojom::AccountManager> account_manager_remote_;
  std::unique_ptr<mojo::Receiver<crosapi::mojom::AccountManagerObserver>>
      receiver_;

  base::ObserverList<Observer> observer_list_;

  const base::WeakPtr<AccountManager> account_manager_for_tests_ = nullptr;

  base::WeakPtrFactory<AccountManagerFacadeImpl> weak_factory_{this};
};

}  // namespace account_manager

#endif  // COMPONENTS_ACCOUNT_MANAGER_CORE_ACCOUNT_MANAGER_FACADE_IMPL_H_