chromium/chrome/browser/password_manager/android/password_store_android_backend.cc

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/password_manager/android/password_store_android_backend.h"

#include <jni.h>

#include <cmath>
#include <list>
#include <memory>
#include <optional>
#include <vector>

#include "base/barrier_callback.h"
#include "base/containers/flat_set.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "base/ranges/algorithm.h"
#include "base/strings/strcat.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_runner.h"
#include "base/time/time.h"
#include "chrome/browser/password_manager/android/password_manager_eviction_util.h"
#include "chrome/browser/password_manager/android/password_manager_lifecycle_helper_impl.h"
#include "chrome/browser/password_manager/android/password_store_android_backend_api_error_codes.h"
#include "chrome/browser/password_manager/android/password_store_android_backend_bridge_helper.h"
#include "chrome/browser/password_manager/android/password_store_android_backend_dispatcher_bridge.h"
#include "components/affiliations/core/browser/affiliation_utils.h"
#include "components/autofill/core/common/autofill_regexes.h"
#include "components/password_manager/core/browser/features/password_features.h"
#include "components/password_manager/core/browser/password_form.h"
#include "components/password_manager/core/browser/password_manager_metrics_util.h"
#include "components/password_manager/core/browser/password_store/android_backend_error.h"
#include "components/password_manager/core/browser/password_store/password_store_backend_error.h"
#include "components/password_manager/core/browser/password_store/password_store_backend_metrics_recorder.h"
#include "components/password_manager/core/browser/password_store/password_store_util.h"
#include "components/password_manager/core/browser/password_store/psl_matching_helper.h"
#include "components/password_manager/core/browser/password_sync_util.h"
#include "components/password_manager/core/common/password_manager_pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/sync/model/proxy_data_type_controller_delegate.h"
#include "components/sync/service/sync_service.h"

namespace password_manager {

namespace {

// Tasks that are older than this timeout are cleaned up whenever Chrome starts
// a new foreground session since it's likely that Chrome missed the response.
constexpr base::TimeDelta kAsyncTaskTimeout = base::Seconds(30);
constexpr char kRetryHistogramBase[] =
    "PasswordManager.PasswordStoreAndroidBackend.Retry";
constexpr base::TimeDelta kTaskRetryTimeout = base::Seconds(16);
// Time in seconds by which calls to the password store happening on startup
// should be delayed.
constexpr base::TimeDelta kPasswordStoreCallDelaySeconds = base::Seconds(5);
constexpr int kMaxReportedRetryAttempts = 10;

using base::UTF8ToUTF16;
using password_manager::GetExpressionForFederatedMatching;
using password_manager::GetRegexForPSLFederatedMatching;
using password_manager::GetRegexForPSLMatching;

using JobId = PasswordStoreAndroidBackendReceiverBridge::JobId;
using SuccessStatus = PasswordStoreBackendMetricsRecorder::SuccessStatus;

std::string FormToSignonRealmQuery(const PasswordFormDigest& form,
                                   bool include_psl) {
  if (include_psl) {
    // Check PSL matches and matches for exact signon realm.
    return GetRegistryControlledDomain(GURL(form.signon_realm));
  }
  if (form.scheme == PasswordForm::Scheme::kHtml &&
      !affiliations::IsValidAndroidFacetURI(form.signon_realm)) {
    // Check federated matches and matches for exact signon realm.
    return form.url.host();
  }
  // Check matches for exact signon realm.
  return form.signon_realm;
}

bool MatchesRegexWithCache(std::u16string_view input,
                           std::u16string_view regex) {
  static base::NoDestructor<autofill::AutofillRegexCache> cache(
      autofill::ThreadSafe(true));
  const icu::RegexPattern* regex_pattern = cache->GetRegexPattern(regex);
  return autofill::MatchesRegex(input, *regex_pattern);
}

bool MatchesIncludedPSLAndFederation(const PasswordForm& retrieved_login,
                                     const PasswordFormDigest& form_to_match,
                                     bool include_psl) {
  if (retrieved_login.signon_realm == form_to_match.signon_realm) {
    return true;
  }

  if (form_to_match.scheme != retrieved_login.scheme) {
    return false;
  }

  std::u16string retrieved_login_signon_realm =
      UTF8ToUTF16(retrieved_login.signon_realm);
  const bool include_federated =
      form_to_match.scheme == PasswordForm::Scheme::kHtml;

  if (include_psl) {
    const std::u16string psl_regex =
        UTF8ToUTF16(GetRegexForPSLMatching(form_to_match.signon_realm));
    if (MatchesRegexWithCache(retrieved_login_signon_realm, psl_regex)) {
      // Ensure match qualifies as PSL Match.
      return IsPublicSuffixDomainMatch(retrieved_login.signon_realm,
                                       form_to_match.signon_realm);
    }
    if (include_federated) {
      const std::u16string psl_federated_regex = UTF8ToUTF16(
          GetRegexForPSLFederatedMatching(form_to_match.signon_realm));
      if (MatchesRegexWithCache(retrieved_login_signon_realm,
                                psl_federated_regex)) {
        return true;
      }
    }
  } else if (include_federated) {
    const std::u16string federated_regex =
        UTF8ToUTF16("^" + GetExpressionForFederatedMatching(form_to_match.url));
    return include_federated &&
           MatchesRegexWithCache(retrieved_login_signon_realm, federated_regex);
  }
  return false;
}

void ValidateSignonRealm(const PasswordFormDigest& form_digest_to_match,
                         bool include_psl,
                         LoginsOrErrorReply callback,
                         LoginsResultOrError logins_or_error) {
  if (absl::holds_alternative<PasswordStoreBackendError>(logins_or_error)) {
    std::move(callback).Run(std::move(logins_or_error));
    return;
  }
  std::erase_if(absl::get<LoginsResult>(logins_or_error),
                [&form_digest_to_match, include_psl](const auto& form) {
                  return !MatchesIncludedPSLAndFederation(
                      form, form_digest_to_match, include_psl);
                });
  std::move(callback).Run(std::move(logins_or_error));
}

void ProcessGroupedLoginsAndReply(const PasswordFormDigest& form_digest,
                                  LoginsOrErrorReply callback,
                                  LoginsResultOrError logins_or_error) {
  if (absl::holds_alternative<PasswordStoreBackendError>(logins_or_error)) {
    std::move(callback).Run(std::move(logins_or_error));
    return;
  }
  for (auto& form : absl::get<LoginsResult>(logins_or_error)) {
    switch (GetMatchResult(form, form_digest)) {
      case MatchResult::NO_MATCH:
        // If it's not PSL nor exact match it has to be affiliated or grouped.
        CHECK(form.match_type.has_value());
        break;
      case MatchResult::EXACT_MATCH:
      case MatchResult::FEDERATED_MATCH:
        // Rewrite match type completely for exact matches so it won't be
        // confused as other types.
        form.match_type = PasswordForm::MatchType::kExact;
        break;
      case MatchResult::PSL_MATCH:
      case MatchResult::FEDERATED_PSL_MATCH:
        // PSL match is only possible if form was marked as grouped match.
        CHECK(form.match_type.has_value());
        form.match_type |= PasswordForm::MatchType::kPSL;
        break;
    }
  }

  std::move(callback).Run(std::move(logins_or_error));
}

LoginsResultOrError JoinRetrievedLoginsOrError(
    std::vector<LoginsResultOrError> results) {
  LoginsResult joined_logins;
  for (auto& result : results) {
    // If one of retrievals ended with an error, pass on the error.
    if (absl::holds_alternative<PasswordStoreBackendError>(result)) {
      return std::move(absl::get<PasswordStoreBackendError>(result));
    }
    LoginsResult logins = std::move(absl::get<LoginsResult>(result));
    std::move(logins.begin(), logins.end(), std::back_inserter(joined_logins));
  }
  return joined_logins;
}

SuccessStatus GetSuccessStatusFromError(
    const std::optional<AndroidBackendError>& error) {
  if (!error.has_value()) {
    return SuccessStatus::kSuccess;
  }
  switch (error.value().type) {
    case AndroidBackendErrorType::kCleanedUpWithoutResponse:
      return SuccessStatus::kCancelledTimeout;
    case AndroidBackendErrorType::kCancelledPwdSyncStateChanged:
      return SuccessStatus::kCancelledPwdSyncStateChanged;
    case AndroidBackendErrorType::kUncategorized:
    case AndroidBackendErrorType::kNoContext:
    case AndroidBackendErrorType::kNoAccount:
    case AndroidBackendErrorType::kProfileNotInitialized:
    case AndroidBackendErrorType::kSyncServiceUnavailable:
    case AndroidBackendErrorType::kPassphraseNotSupported:
    case AndroidBackendErrorType::kGMSVersionNotSupported:
    case AndroidBackendErrorType::kExternalError:
    case AndroidBackendErrorType::kBackendNotAvailable:
    case AndroidBackendErrorType::kFailedToCreateFacetId:
      return SuccessStatus::kError;
  }
  NOTREACHED_IN_MIGRATION();
  return SuccessStatus::kError;
}

std::string GetOperationName(PasswordStoreOperation operation) {
  switch (operation) {
    case PasswordStoreOperation::kGetAllLoginsAsync:
      return "GetAllLoginsAsync";
    case PasswordStoreOperation::kGetAutofillableLoginsAsync:
      return "GetAutofillableLoginsAsync";
    case PasswordStoreOperation::kFillMatchingLoginsAsync:
      return "FillMatchingLoginsAsync";
    case PasswordStoreOperation::kAddLoginAsync:
      return "AddLoginAsync";
    case PasswordStoreOperation::kUpdateLoginAsync:
      return "UpdateLoginAsync";
    case PasswordStoreOperation::kRemoveLoginAsync:
      return "RemoveLoginAsync";
    case PasswordStoreOperation::kRemoveLoginsByURLAndTimeAsync:
      return "RemoveLoginsByURLAndTimeAsync";
    case PasswordStoreOperation::kRemoveLoginsCreatedBetweenAsync:
      return "RemoveLoginsCreatedBetweenAsync";
    case PasswordStoreOperation::kDisableAutoSignInForOriginsAsync:
      return "DisableAutoSignInForOriginsAsync";
    case PasswordStoreOperation::kGetGroupedMatchingLoginsAsync:
      return "GetGroupedMatchingLoginsAsync";
    case PasswordStoreOperation::kGetAllLoginsWithBrandingInfoAsync:
      return "GetAllLoginsWithBrandingInfoAsync";
  }
  NOTREACHED_IN_MIGRATION() << "Operation code not handled";
  return "";
}

int GetRetryAttemptFromDelay(base::TimeDelta delay) {
  // Delays are exponential (powers of 2). Original operation delay is 0.
  int attempt = 1;
  if (delay.InSeconds() >= 1) {
    attempt = log2(delay.InSeconds()) + 2;
  }
  return attempt;
}

void RecordRetryHistograms(PasswordStoreOperation operation,
                           AndroidBackendAPIErrorCode api_error_code,
                           base::TimeDelta delay) {
  int attempt = GetRetryAttemptFromDelay(delay);
  // Record per-operation metrics
  base::UmaHistogramSparse(
      base::StrCat(
          {kRetryHistogramBase, ".", GetOperationName(operation), ".APIError"}),
      static_cast<int>(api_error_code));
  base::UmaHistogramExactLinear(
      base::StrCat(
          {kRetryHistogramBase, ".", GetOperationName(operation), ".Attempt"}),
      attempt, kMaxReportedRetryAttempts);

  // Record aggregated metrics
  base::UmaHistogramSparse(base::StrCat({kRetryHistogramBase, ".APIError"}),
                           static_cast<int>(api_error_code));
  base::UmaHistogramExactLinear(base::StrCat({kRetryHistogramBase, ".Attempt"}),
                                attempt, kMaxReportedRetryAttempts);
}

void RecordCancelledRetryMetrics(PasswordStoreOperation operation,
                                 base::TimeDelta delay) {
  int attempt = GetRetryAttemptFromDelay(delay);

  // Record per-operation metrics
  base::UmaHistogramExactLinear(
      base::StrCat({kRetryHistogramBase, ".", GetOperationName(operation),
                    ".CancelledAtAttempt"}),
      attempt, kMaxReportedRetryAttempts);

  base::UmaHistogramExactLinear(
      base::StrCat({kRetryHistogramBase, ".CancelledAtAttempt"}), attempt,
      kMaxReportedRetryAttempts);
}
enum class ActionOnApiError {
  // See password_manager_upm_eviction::EvictCurrentUser().
  kEvict,
  // See prefs::kSavePasswordsSuspendedByError.
  kDisableSaving,
  // See PasswordStoreAndroidBackend::TryFixPassphraseErrorCb.
  kDisableSavingAndTryFixPassphraseError,
  kRetry,
};

bool ShouldRetryOperationOnError(PasswordStoreOperation operation,
                                 AndroidBackendAPIErrorCode api_error_code,
                                 base::TimeDelta delay) {
  const base::flat_set<PasswordStoreOperation> kRetriableOperations = {
      PasswordStoreOperation::kGetAllLoginsAsync,
      PasswordStoreOperation::kGetAutofillableLoginsAsync,
  };
  const base::flat_set<AndroidBackendAPIErrorCode> kRetriableErrors = {
      AndroidBackendAPIErrorCode::kNetworkError,
      AndroidBackendAPIErrorCode::kApiNotConnected,
      AndroidBackendAPIErrorCode::kConnectionSuspendedDuringCall,
      AndroidBackendAPIErrorCode::kReconnectionTimedOut,
      AndroidBackendAPIErrorCode::kBackendGeneric};
  return delay < kTaskRetryTimeout &&
         kRetriableOperations.contains(operation) &&
         kRetriableErrors.contains(
             static_cast<AndroidBackendAPIErrorCode>(api_error_code));
}

PasswordStoreBackendErrorType APIErrorCodeToErrorType(
    AndroidBackendAPIErrorCode api_error_code) {
  switch (api_error_code) {
    case AndroidBackendAPIErrorCode::kAuthErrorResolvable:
      return PasswordStoreBackendErrorType::kAuthErrorResolvable;
    case AndroidBackendAPIErrorCode::kAuthErrorUnresolvable:
      return PasswordStoreBackendErrorType::kAuthErrorUnresolvable;
    case AndroidBackendAPIErrorCode::kKeyRetrievalRequired:
      return PasswordStoreBackendErrorType::kKeyRetrievalRequired;
    case AndroidBackendAPIErrorCode::kNetworkError:
    case AndroidBackendAPIErrorCode::kInternalError:
    case AndroidBackendAPIErrorCode::kDeveloperError:
    case AndroidBackendAPIErrorCode::kApiNotConnected:
    case AndroidBackendAPIErrorCode::kConnectionSuspendedDuringCall:
    case AndroidBackendAPIErrorCode::kReconnectionTimedOut:
    case AndroidBackendAPIErrorCode::kPassphraseRequired:
    case AndroidBackendAPIErrorCode::kAccessDenied:
    case AndroidBackendAPIErrorCode::kBadRequest:
    case AndroidBackendAPIErrorCode::kBackendGeneric:
    case AndroidBackendAPIErrorCode::kBackendResourceExhausted:
    case AndroidBackendAPIErrorCode::kInvalidData:
    case AndroidBackendAPIErrorCode::kUnmappedErrorCode:
    case AndroidBackendAPIErrorCode::kUnexpectedError:
    case AndroidBackendAPIErrorCode::kChromeSyncAPICallError:
    case AndroidBackendAPIErrorCode::kErrorWhileDoingLeakServiceGRPC:
    case AndroidBackendAPIErrorCode::kRequiredSyncingAccountMissing:
    case AndroidBackendAPIErrorCode::kLeakCheckServiceAuthError:
    case AndroidBackendAPIErrorCode::kLeakCheckServiceResourceExhausted:
      return PasswordStoreBackendErrorType::kUncategorized;
  }
  // The api_error_code is determined by static casting an int. It is thus
  // possible for the value to not be among the explicit enum values, however
  // that case should still be handled. Not adding a default statement to the
  // switch, so that the compiler still warns when a new enum value is added and
  // not explicitly handled here.
  return PasswordStoreBackendErrorType::kUncategorized;
}

}  // namespace

PasswordStoreAndroidBackend::PasswordStoreAndroidBackend(
    std::unique_ptr<PasswordStoreAndroidBackendBridgeHelper> bridge_helper,
    std::unique_ptr<PasswordManagerLifecycleHelper> lifecycle_helper,
    PrefService* prefs)
    : lifecycle_helper_(std::move(lifecycle_helper)),
      bridge_helper_(std::move(bridge_helper)),
      prefs_(prefs) {
  DCHECK(bridge_helper_);
  DCHECK(prefs_);
  bridge_helper_->SetConsumer(weak_ptr_factory_.GetWeakPtr());
}

PasswordStoreAndroidBackend::~PasswordStoreAndroidBackend() = default;

void PasswordStoreAndroidBackend::Init(
    PasswordStoreBackend::RemoteChangesReceived remote_form_changes_received) {
  main_task_runner_ = base::SequencedTaskRunner::GetCurrentDefault();
  stored_passwords_changed_ = std::move(remote_form_changes_received);
  lifecycle_helper_->RegisterObserver(base::BindRepeating(
      &PasswordStoreAndroidBackend::OnForegroundSessionStart,
      base::Unretained(this)));
  // TODO(crbug.com/40778507): Create subscription before completion.
}

void PasswordStoreAndroidBackend::Shutdown(
    base::OnceClosure shutdown_completed) {
  lifecycle_helper_->UnregisterObserver();
  // TODO(crbug.com/40190023): Implement (e.g. unsubscribe from GMS).
  std::move(shutdown_completed).Run();
}

void PasswordStoreAndroidBackend::GetAutofillableLoginsInternal(
    std::string account,
    LoginsOrErrorReply callback,
    PasswordStoreOperation operation,
    base::TimeDelta delay) {
  JobId job_id = bridge_helper_->GetAutofillableLogins(std::move(account));
  QueueNewJob(job_id, std::move(callback),
              MethodName("GetAutofillableLoginsAsync"), operation, delay);
}

void PasswordStoreAndroidBackend::
    GetAllLoginsWithAffiliationAndBrandingInternal(
        std::string account,
        LoginsOrErrorReply callback) {
  JobId job_id =
      bridge_helper_->GetAllLoginsWithBrandingInfo(std::move(account));
  QueueNewJob(job_id, std::move(callback),
              MethodName("GetAllLoginsWithBrandingInfoAsync"),
              PasswordStoreOperation::kGetAllLoginsWithBrandingInfoAsync,
              /*delay=*/base::Seconds(0));
}

void PasswordStoreAndroidBackend::GetAllLoginsInternal(
    std::string account,
    LoginsOrErrorReply callback,
    PasswordStoreOperation operation,
    base::TimeDelta delay) {
  JobId job_id = bridge_helper_->GetAllLogins(std::move(account));
  QueueNewJob(job_id, std::move(callback), MethodName("GetAllLoginsAsync"),
              operation, delay);
}

void PasswordStoreAndroidBackend::GetLoginsInternal(
    std::string account,
    const PasswordFormDigest& form,
    bool include_psl,
    LoginsOrErrorReply callback) {
  JobId job_id = bridge_helper_->GetLoginsForSignonRealm(
      FormToSignonRealmQuery(form, include_psl), std::move(account));
  // TODO(crbug.com/40284943): Re-design metrics to be less reliant on exact
  // method name and separate external methods from internal ones.
  QueueNewJob(job_id,
              base::BindOnce(&ValidateSignonRealm, std::move(form), include_psl,
                             std::move(callback)),
              MethodName("GetLoginsAsync"),
              PasswordStoreOperation::kFillMatchingLoginsAsync,
              /*delay=*/base::Seconds(0));
}

void PasswordStoreAndroidBackend::AddLoginInternal(
    std::string account,
    const PasswordForm& form,
    PasswordChangesOrErrorReply callback) {
  PasswordForm sanitized_form = form;
  if (sanitized_form.blocked_by_user) {
    sanitized_form.username_value.clear();
    sanitized_form.password_value.clear();
  }
  JobId job_id = bridge_helper_->AddLogin(sanitized_form, std::move(account));
  QueueNewJob(job_id, std::move(callback), MethodName("AddLoginAsync"),
              PasswordStoreOperation::kAddLoginAsync,
              /*delay=*/base::Seconds(0));
}

void PasswordStoreAndroidBackend::UpdateLoginInternal(
    std::string account,
    const PasswordForm& form,
    PasswordChangesOrErrorReply callback) {
  PasswordForm sanitized_form = form;
  if (sanitized_form.blocked_by_user) {
    sanitized_form.username_value.clear();
    sanitized_form.password_value.clear();
  }
  JobId job_id =
      bridge_helper_->UpdateLogin(sanitized_form, std::move(account));
  QueueNewJob(job_id, std::move(callback), MethodName("UpdateLoginAsync"),
              PasswordStoreOperation::kUpdateLoginAsync,
              /*delay=*/base::Seconds(0));
}

void PasswordStoreAndroidBackend::RemoveLoginInternal(
    std::string account,
    const PasswordForm& form,
    PasswordChangesOrErrorReply callback) {
  JobId job_id = bridge_helper_->RemoveLogin(form, std::move(account));
  QueueNewJob(job_id, std::move(callback), MethodName("RemoveLoginAsync"),
              PasswordStoreOperation::kRemoveLoginAsync,
              /*delay=*/base::Seconds(0));
}

void PasswordStoreAndroidBackend::FillMatchingLoginsInternal(
    std::string account,
    LoginsOrErrorReply callback,
    bool include_psl,
    const std::vector<PasswordFormDigest>& forms) {
  if (forms.empty()) {
    std::move(callback).Run(LoginsResult());
    return;
  }

  // Record FillMatchingLoginsAsync metrics prior to invoking |callback|.
  LoginsOrErrorReply record_metrics_and_reply =
      ReportMetricsAndInvokeCallbackForLoginsRetrieval(
          MethodName("FillMatchingLoginsAsync"), std::move(callback),
          GetStorageType());

  // Create a barrier callback that aggregates results of a multiple
  // calls to GetLoginsInternal.
  auto barrier_callback = base::BarrierCallback<LoginsResultOrError>(
      forms.size(), base::BindOnce(&JoinRetrievedLoginsOrError)
                        .Then(std::move(record_metrics_and_reply)));

  // Create and run a callbacks chain that retrieves logins and invokes
  // |barrier_callback| afterwards.
  base::OnceClosure callbacks_chain = base::DoNothing();
  for (const PasswordFormDigest& form : forms) {
    callbacks_chain = base::BindOnce(
        &PasswordStoreAndroidBackend::GetLoginsInternal,
        weak_ptr_factory_.GetWeakPtr(), account, std::move(form), include_psl,
        base::BindOnce(barrier_callback).Then(std::move(callbacks_chain)));
  }
  std::move(callbacks_chain).Run();
}

void PasswordStoreAndroidBackend::GetGroupedMatchingLoginsInternal(
    std::string account,
    const PasswordFormDigest& form_digest,
    LoginsOrErrorReply callback) {
  JobId job_id = bridge_helper_->GetAffiliatedLoginsForSignonRealm(
      form_digest.signon_realm, std::move(account));
  QueueNewJob(job_id,
              base::BindOnce(&ProcessGroupedLoginsAndReply, form_digest,
                             std::move(callback)),
              MethodName("GetGroupedMatchingLoginsAsync"),
              PasswordStoreOperation::kGetGroupedMatchingLoginsAsync,
              /*delay=*/base::Seconds(0));
}

void PasswordStoreAndroidBackend::RemoveLoginsByURLAndTimeInternal(
    std::string account,
    const base::RepeatingCallback<bool(const GURL&)>& url_filter,
    base::Time delete_begin,
    base::Time delete_end,
    PasswordChangesOrErrorReply callback) {
  // Record metrics prior to invoking |callback|.
  PasswordChangesOrErrorReply record_metrics_and_reply =
      ReportMetricsAndInvokeCallbackForStoreModifications(
          MethodName("RemoveLoginsByURLAndTimeAsync"), std::move(callback),
          GetStorageType());

  GetAllLoginsInternal(
      account,
      base::BindOnce(&PasswordStoreAndroidBackend::FilterAndRemoveLogins,
                     weak_ptr_factory_.GetWeakPtr(), account,
                     std::move(url_filter), delete_begin, delete_end,
                     std::move(record_metrics_and_reply)),
      PasswordStoreOperation::kRemoveLoginsByURLAndTimeAsync);
}

void PasswordStoreAndroidBackend::RemoveLoginsCreatedBetweenInternal(
    std::string account,
    base::Time delete_begin,
    base::Time delete_end,
    PasswordChangesOrErrorReply callback) {
  // Record metrics prior to invoking |callback|.
  PasswordChangesOrErrorReply record_metrics_and_reply =
      ReportMetricsAndInvokeCallbackForStoreModifications(
          MethodName("RemoveLoginsCreatedBetweenAsync"), std::move(callback),
          GetStorageType());

  GetAllLoginsInternal(
      account,
      base::BindOnce(&PasswordStoreAndroidBackend::FilterAndRemoveLogins,
                     weak_ptr_factory_.GetWeakPtr(), account,
                     // Include all urls.
                     base::BindRepeating([](const GURL&) { return true; }),
                     delete_begin, delete_end,
                     std::move(record_metrics_and_reply)),
      PasswordStoreOperation::kRemoveLoginsCreatedBetweenAsync);
}

void PasswordStoreAndroidBackend::DisableAutoSignInForOriginsInternal(
    std::string account,
    const base::RepeatingCallback<bool(const GURL&)>& origin_filter,
    base::OnceClosure completion) {
  // TODO(crbug.com/40778511) Switch to using base::PassThrough to
  // handle this callback more gracefully when it's implemented.
  PasswordChangesOrErrorReply record_metrics_and_run_completion =
      base::BindOnce(
          [](PasswordStoreBackendMetricsRecorder metrics_recorder,
             base::OnceClosure completion, PasswordChangesOrError changes) {
            // Errors are not recorded at the moment.
            // TODO(crbug.com/40208332): Implement error handling,
            // when actual store changes will be received from the store.
            metrics_recorder.RecordMetrics(SuccessStatus::kSuccess,
                                           /*error=*/std::nullopt);
            std::move(completion).Run();
          },
          PasswordStoreBackendMetricsRecorder(
              BackendInfix("AndroidBackend"),
              MethodName("DisableAutoSignInForOriginsAsync"), GetStorageType()),
          std::move(completion));

  GetAllLoginsInternal(
      account,
      base::BindOnce(&PasswordStoreAndroidBackend::FilterAndDisableAutoSignIn,
                     weak_ptr_factory_.GetWeakPtr(), account, origin_filter,
                     std::move(record_metrics_and_run_completion)),
      PasswordStoreOperation::kDisableAutoSignInForOriginsAsync);
}

void PasswordStoreAndroidBackend::ClearAllTasksAndReplyWithReason(
    const AndroidBackendError& reason,
    const PasswordStoreBackendError& reply_error) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);

  // Cancel queued jobs that haven't yet received a reply.
  for (auto& [id, job_reply] : request_for_job_) {
    job_reply.RecordMetrics(reason);
    if (job_reply.Holds<LoginsOrErrorReply>()) {
      main_task_runner_->PostTask(
          FROM_HERE,
          base::BindOnce(std::move(job_reply).Get<LoginsOrErrorReply>(),
                         reply_error));

    } else if (job_reply.Holds<PasswordChangesOrErrorReply>()) {
      main_task_runner_->PostTask(
          FROM_HERE,
          base::BindOnce(
              std::move(job_reply).Get<PasswordChangesOrErrorReply>(),
              reply_error));
    }
  }
  request_for_job_.clear();

  // Cancel posted delayed retries
  for (const auto& [id, retry_wrapper] : scheduled_retries_) {
    LoginsOrErrorReply reply_callback =
        retry_wrapper->GetReplyCallbackAndCancel();
    main_task_runner_->PostTask(
        FROM_HERE, base::BindOnce(std::move(reply_callback), reply_error));
  }
  scheduled_retries_.clear();
}

PasswordStoreAndroidBackend::JobReturnHandler::JobReturnHandler(
    LoginsOrErrorReply callback,
    PasswordStoreBackendMetricsRecorder metrics_recorder,
    base::TimeDelta delay,
    PasswordStoreOperation operation)
    : success_callback_(std::move(callback)),
      metrics_recorder_(std::move(metrics_recorder)),
      delay_(delay),
      operation_(operation) {}

PasswordStoreAndroidBackend::JobReturnHandler::JobReturnHandler(
    PasswordChangesOrErrorReply callback,
    PasswordStoreBackendMetricsRecorder metrics_recorder,
    base::TimeDelta delay,
    PasswordStoreOperation operation)
    : success_callback_(std::move(callback)),
      metrics_recorder_(std::move(metrics_recorder)),
      delay_(delay),
      operation_(operation) {}

PasswordStoreAndroidBackend::JobReturnHandler::JobReturnHandler(
    JobReturnHandler&&) = default;
PasswordStoreAndroidBackend::JobReturnHandler::~JobReturnHandler() = default;

void PasswordStoreAndroidBackend::JobReturnHandler::RecordMetrics(
    std::optional<AndroidBackendError> error) const {
  SuccessStatus sucess_status = GetSuccessStatusFromError(error);
  metrics_recorder_.RecordMetrics(sucess_status, std::move(error));
}

base::TimeDelta
PasswordStoreAndroidBackend::JobReturnHandler::GetElapsedTimeSinceStart()
    const {
  // The recorder is always created right before the task starts.
  return metrics_recorder_.GetElapsedTimeSinceCreation();
}

base::TimeDelta PasswordStoreAndroidBackend::JobReturnHandler::GetDelay() {
  return delay_;
}

PasswordStoreOperation
PasswordStoreAndroidBackend::JobReturnHandler::GetOperation() {
  return operation_;
}

PasswordStoreAndroidBackend::CancellableRetryCallback::CancellableRetryCallback(
    base::OnceCallback<void(LoginsOrErrorReply,
                            PasswordStoreOperation,
                            base::TimeDelta)> callback,
    PasswordStoreOperation operation,
    LoginsOrErrorReply reply_callback,
    base::TimeDelta current_delay)
    : callback_(std::move(callback)),
      operation_(operation),
      reply_callback_(std::move(reply_callback)),
      current_delay_(current_delay) {}
PasswordStoreAndroidBackend::CancellableRetryCallback::
    ~CancellableRetryCallback() = default;

base::WeakPtr<PasswordStoreAndroidBackend::CancellableRetryCallback>
PasswordStoreAndroidBackend::CancellableRetryCallback::AsWeakPtr() {
  return weak_ptr_factory_.GetWeakPtr();
}

void PasswordStoreAndroidBackend::CancellableRetryCallback::Run() {
  CHECK(callback_);
  std::move(callback_).Run(std::move(reply_callback_), operation_,
                           current_delay_);
}

LoginsOrErrorReply PasswordStoreAndroidBackend::CancellableRetryCallback::
    GetReplyCallbackAndCancel() {
  RecordCancelledRetryMetrics(operation_, current_delay_);
  weak_ptr_factory_.InvalidateWeakPtrs();

  return std::move(reply_callback_);
}

base::OnceCallback<
    void(LoginsOrErrorReply, PasswordStoreOperation, base::TimeDelta)>
PasswordStoreAndroidBackend::GetRetryCallbackForOperation(
    PasswordStoreOperation operation) {
  switch (operation) {
    case PasswordStoreOperation::kGetAllLoginsAsync:
      return base::BindOnce(&PasswordStoreAndroidBackend::GetAllLoginsInternal,
                            weak_ptr_factory_.GetWeakPtr(),
                            GetAccountToRetryOperation());
    case PasswordStoreOperation::kGetAutofillableLoginsAsync:
      return base::BindOnce(
          &PasswordStoreAndroidBackend::GetAutofillableLoginsInternal,
          weak_ptr_factory_.GetWeakPtr(), GetAccountToRetryOperation());
    case PasswordStoreOperation::kFillMatchingLoginsAsync:
    case PasswordStoreOperation::kAddLoginAsync:
    case PasswordStoreOperation::kUpdateLoginAsync:
    case PasswordStoreOperation::kRemoveLoginAsync:
    case PasswordStoreOperation::kRemoveLoginsByURLAndTimeAsync:
    case PasswordStoreOperation::kRemoveLoginsCreatedBetweenAsync:
    case PasswordStoreOperation::kDisableAutoSignInForOriginsAsync:
    case PasswordStoreOperation::kGetGroupedMatchingLoginsAsync:
    case PasswordStoreOperation::kGetAllLoginsWithBrandingInfoAsync:
      NOTREACHED();
  }
}

void PasswordStoreAndroidBackend::RetryOperation(
    PasswordStoreOperation operation,
    AndroidBackendAPIErrorCode api_error_code,
    base::TimeDelta delay,
    LoginsOrErrorReply reply) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
  RecordRetryHistograms(operation, api_error_code, delay);

  base::TimeDelta new_delay =
      delay.InSeconds() == 0 ? base::Seconds(1) : delay * 2;

  auto retry =
      std::make_unique<PasswordStoreAndroidBackend::CancellableRetryCallback>(
          GetRetryCallbackForOperation(operation), operation, std::move(reply),
          new_delay);

  DelayedRetryId id = delayed_retry_id_generator_.GenerateNextId();
  base::OnceClosure cleanup =
      base::BindOnce(&PasswordStoreAndroidBackend::CleanupRetryAfterRun,
                     weak_ptr_factory_.GetWeakPtr(), id);

  main_task_runner_->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(&CancellableRetryCallback::Run, retry->AsWeakPtr())
          .Then(std::move(cleanup)),
      new_delay);
  scheduled_retries_.emplace(id, std::move(retry));
}

void PasswordStoreAndroidBackend::CleanupRetryAfterRun(
    DelayedRetryId retry_id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
  scheduled_retries_.erase(retry_id);
}

void PasswordStoreAndroidBackend::OnCompleteWithLogins(
    JobId job_id,
    std::vector<PasswordForm> passwords) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
  std::optional<JobReturnHandler> reply = GetAndEraseJob(job_id);
  if (!reply.has_value()) {
    return;  // Task cleaned up after returning from background.
  }

  OnCallToGMSCoreSucceeded();
  reply->RecordMetrics(/*error=*/std::nullopt);
  DCHECK(reply->Holds<LoginsOrErrorReply>());
  main_task_runner_->PostTask(
      FROM_HERE, base::BindOnce(std::move(*reply).Get<LoginsOrErrorReply>(),
                                std::move(passwords)));
}

void PasswordStoreAndroidBackend::OnLoginsChanged(JobId job_id,
                                                  PasswordChanges changes) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
  std::optional<JobReturnHandler> reply = GetAndEraseJob(job_id);
  if (!reply.has_value()) {
    return;  // Task cleaned up after returning from background.
  }
  reply->RecordMetrics(/*error=*/std::nullopt);
  DCHECK(reply->Holds<PasswordChangesOrErrorReply>());

  OnCallToGMSCoreSucceeded();
  main_task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(std::move(*reply).Get<PasswordChangesOrErrorReply>(),
                     changes));
}

void PasswordStoreAndroidBackend::OnError(JobId job_id,
                                          AndroidBackendError error) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
  std::optional<JobReturnHandler> reply = GetAndEraseJob(job_id);
  if (!reply.has_value()) {
    return;  // Task cleaned up after returning from background.
  }

  PasswordStoreOperation operation = reply->GetOperation();

  // The error to report is computed before potential eviction. This is because
  // eviction resets state which might be used to infer the recovery type of
  // the error.
  base::TimeDelta delay = reply->GetDelay();
  PasswordStoreBackendError reported_error(
      PasswordStoreBackendErrorType::kUncategorized);

  if (error.api_error_code.has_value()) {
    // TODO(crbug.com/40839365): DCHECK_EQ(api_error_code,
    // AndroidBackendAPIErrorCode::kDeveloperError) to catch dev errors.
    DCHECK_EQ(AndroidBackendErrorType::kExternalError, error.type);
    int api_error = error.api_error_code.value();
    reported_error.android_backend_api_error = api_error;
    auto api_error_code = static_cast<AndroidBackendAPIErrorCode>(api_error);

    // Retry the call if the performed operation in combination with the error
    // was retriable and the time limit was not reached.
    if (ShouldRetryOperationOnError(operation, api_error_code, delay)) {
      RetryOperation(operation, api_error_code, delay,
                     std::move(*reply).Get<LoginsOrErrorReply>());
      return;
    }

    if (delay < kTaskRetryTimeout) {
      // Either the operation or error is not retriable.
      RecoverOnError(api_error_code);
      reported_error.type = APIErrorCodeToErrorType(api_error_code);
    }
  }

  reply->RecordMetrics(std::move(error));
  // The decision whether to show an error UI depends on the re-enrollment pref
  // and as such the consumers should be called last.
  if (reply->Holds<LoginsOrErrorReply>()) {
    main_task_runner_->PostTask(
        FROM_HERE, base::BindOnce(std::move(*reply).Get<LoginsOrErrorReply>(),
                                  reported_error));
    return;
  }
  if (reply->Holds<PasswordChangesOrErrorReply>()) {
    // Run callback with empty resulting changelist.
    main_task_runner_->PostTask(
        FROM_HERE,
        base::BindOnce(std::move(*reply).Get<PasswordChangesOrErrorReply>(),
                       reported_error));
  }
}

template <typename Callback>
void PasswordStoreAndroidBackend::QueueNewJob(JobId job_id,
                                              Callback callback,
                                              MethodName method_name,
                                              PasswordStoreOperation operation,
                                              base::TimeDelta delay) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
  request_for_job_.emplace(
      job_id, JobReturnHandler(std::move(callback),
                               PasswordStoreBackendMetricsRecorder(
                                   BackendInfix("AndroidBackend"),
                                   std::move(method_name), GetStorageType()),
                               delay, operation));
}

std::optional<PasswordStoreAndroidBackend::JobReturnHandler>
PasswordStoreAndroidBackend::GetAndEraseJob(JobId job_id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
  auto iter = request_for_job_.find(job_id);
  if (iter == request_for_job_.end()) {
    return std::nullopt;
  }
  JobReturnHandler reply = std::move(iter->second);
  request_for_job_.erase(iter);
  return reply;
}

void PasswordStoreAndroidBackend::FilterAndRemoveLogins(
    std::string account,
    const base::RepeatingCallback<bool(const GURL&)>& url_filter,
    base::Time delete_begin,
    base::Time delete_end,
    PasswordChangesOrErrorReply reply,
    LoginsResultOrError result) {
  if (absl::holds_alternative<PasswordStoreBackendError>(result)) {
    std::move(reply).Run(
        std::move(absl::get<PasswordStoreBackendError>(result)));
    return;
  }

  LoginsResult logins = std::move(absl::get<LoginsResult>(result));
  std::vector<PasswordForm> logins_to_remove;
  for (auto& login : logins) {
    if (login.date_created >= delete_begin && login.date_created < delete_end &&
        url_filter.Run(login.url)) {
      logins_to_remove.push_back(std::move(login));
    }
  }

  // Create a barrier callback that aggregates results of a multiple
  // calls to RemoveLoginAsync.
  auto barrier_callback = base::BarrierCallback<PasswordChangesOrError>(
      logins_to_remove.size(),
      base::BindOnce(&JoinPasswordStoreChanges).Then(std::move(reply)));

  // Create and run the callback chain that removes the logins.
  base::OnceClosure callbacks_chain = base::DoNothing();
  for (const auto& login : logins_to_remove) {
    callbacks_chain = base::BindOnce(
        &PasswordStoreAndroidBackend::RemoveLoginInternal,
        weak_ptr_factory_.GetWeakPtr(), account, std::move(login),
        base::BindOnce(barrier_callback).Then(std::move(callbacks_chain)));
  }
  std::move(callbacks_chain).Run();
}

void PasswordStoreAndroidBackend::FilterAndDisableAutoSignIn(
    std::string account,
    const base::RepeatingCallback<bool(const GURL&)>& origin_filter,
    PasswordChangesOrErrorReply completion,
    LoginsResultOrError result) {
  if (absl::holds_alternative<PasswordStoreBackendError>(result)) {
    std::move(completion)
        .Run(std::move(absl::get<PasswordStoreBackendError>(result)));
    return;
  }

  LoginsResult logins = std::move(absl::get<LoginsResult>(result));
  std::vector<PasswordForm> logins_to_update;
  for (auto& login : logins) {
    // Update login if it matches |origin_filer| and has autosignin enabled.
    if (origin_filter.Run(login.url) && !login.skip_zero_click) {
      logins_to_update.push_back(std::move(login));
      logins_to_update.back().skip_zero_click = true;
    }
  }

  auto barrier_callback = base::BarrierCallback<PasswordChangesOrError>(
      logins_to_update.size(),
      base::BindOnce(&JoinPasswordStoreChanges).Then(std::move(completion)));

  // Create and run a callbacks chain that updates the logins.
  base::OnceClosure callbacks_chain = base::DoNothing();
  for (PasswordForm& login : logins_to_update) {
    callbacks_chain = base::BindOnce(
        &PasswordStoreAndroidBackend::UpdateLoginInternal,
        weak_ptr_factory_.GetWeakPtr(), account, std::move(login),
        base::BindOnce(barrier_callback).Then(std::move(callbacks_chain)));
  }
  std::move(callbacks_chain).Run();
}

// static
LoginsOrErrorReply
PasswordStoreAndroidBackend::ReportMetricsAndInvokeCallbackForLoginsRetrieval(
    const MethodName& method_name,
    LoginsOrErrorReply callback,
    PasswordStoreBackendMetricsRecorder::PasswordStoreAndroidBackendType
        store_type) {
  // TODO(crbug.com/40778511) Switch to using base::PassThrough to handle
  // this callback more gracefully when it's implemented.
  return base::BindOnce(
      [](PasswordStoreBackendMetricsRecorder metrics_recorder,
         LoginsOrErrorReply callback, LoginsResultOrError results) {
        metrics_recorder.RecordMetrics(
            absl::holds_alternative<PasswordStoreBackendError>(results)
                ? SuccessStatus::kError
                : SuccessStatus::kSuccess,
            /*error=*/std::nullopt);
        std::move(callback).Run(std::move(results));
      },
      PasswordStoreBackendMetricsRecorder(BackendInfix("AndroidBackend"),
                                          method_name, store_type),
      std::move(callback));
}

// static
PasswordChangesOrErrorReply PasswordStoreAndroidBackend::
    ReportMetricsAndInvokeCallbackForStoreModifications(
        const MethodName& method_name,
        PasswordChangesOrErrorReply callback,
        PasswordStoreBackendMetricsRecorder::PasswordStoreAndroidBackendType
            store_type) {
  // TODO(crbug.com/40778511) Switch to using base::PassThrough to handle
  // this callback more gracefully when it's implemented.
  return base::BindOnce(
      [](PasswordStoreBackendMetricsRecorder metrics_recorder,
         PasswordChangesOrErrorReply callback, PasswordChangesOrError results) {
        // Errors are not recorded at the moment.
        // TODO(crbug.com/40208332): Implement error handling, when
        // actual store changes will be received from the store.
        metrics_recorder.RecordMetrics(SuccessStatus::kSuccess,
                                       /*error=*/std::nullopt);
        std::move(callback).Run(std::move(results));
      },
      PasswordStoreBackendMetricsRecorder(BackendInfix("AndroidBackend"),
                                          method_name, store_type),
      std::move(callback));
}

void PasswordStoreAndroidBackend::OnForegroundSessionStart() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
  DCHECK(stored_passwords_changed_);

  // Clear outdated pending tasks before the store queues a new request.
  ClearZombieTasks();

  // If this is the first foregrounding signal, it corresponds to Chrome
  // starting up. In that case, calls to Google Play Services should be delayed
  // as they tend to be resource-intensive.
  if (should_delay_refresh_on_foregrounding_) {
    should_delay_refresh_on_foregrounding_ = false;
    base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
        FROM_HERE, base::BindOnce(stored_passwords_changed_, std::nullopt),
        kPasswordStoreCallDelaySeconds);
    return;
  }

  // Calling the remote form changes with a nullopt means that changes are not
  // available and the store should request all logins asynchronously to
  // invoke `PasswordStoreInterface::Observer::OnLoginsRetained`.
  stored_passwords_changed_.Run(std::nullopt);
}

// TODO(b/322163027): Merge this with `ClearAllTasksAndReplyWithReason(...)`.
void PasswordStoreAndroidBackend::ClearZombieTasks() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
  // Collect expired jobs. Deleting them immediately would invalidate iterators.
  std::list<JobId> timed_out_job_ids;
  for (const auto& [id, job] : request_for_job_) {
    if (job.GetElapsedTimeSinceStart() >= kAsyncTaskTimeout) {
      timed_out_job_ids.push_back(id);
    }
  }
  // Erase each timed out job and record that it was cleaned up.
  base::ranges::for_each(timed_out_job_ids, [&](const JobId& job_id) {
    GetAndEraseJob(job_id)->RecordMetrics(AndroidBackendError{
        .type = AndroidBackendErrorType::kCleanedUpWithoutResponse});
  });
}

}  // namespace password_manager