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

// Copyright 2022 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_manager_error_message_delegate.h"

#include "base/android/jni_android.h"
#include "base/metrics/histogram_functions.h"
#include "chrome/browser/android/android_theme_resources.h"
#include "chrome/browser/android/resource_mapper.h"
#include "chrome/grit/generated_resources.h"
#include "components/messages/android/message_dispatcher_bridge.h"
#include "components/password_manager/core/browser/password_manager_client.h"
#include "components/password_manager/core/common/password_manager_pref_names.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/web_contents.h"
#include "ui/android/window_android.h"
#include "ui/aura/window.h"
#include "ui/base/l10n/l10n_util.h"

namespace {

using PasswordStoreBackendErrorType =
    password_manager::PasswordStoreBackendErrorType;

std::string GetErrorMessageName(PasswordStoreBackendErrorType error_type) {
  switch (error_type) {
    case PasswordStoreBackendErrorType::kAuthErrorResolvable:
      return "AuthErrorResolvable";
    case PasswordStoreBackendErrorType::kAuthErrorUnresolvable:
      return "AuthErrorUnresolvable";
    case PasswordStoreBackendErrorType::kKeyRetrievalRequired:
      return "KeyRetrievalRequired";
    case PasswordStoreBackendErrorType::kGMSCoreOutdatedSavingPossible:
      return "GMSCoreOutdatedSavingPossible";
    case PasswordStoreBackendErrorType::kGMSCoreOutdatedSavingDisabled:
      return "GMSCoreOutdatedSavingDisabled";
    case PasswordStoreBackendErrorType::kUncategorized:
    case PasswordStoreBackendErrorType::kKeychainError:
      // Other error types aren't supported.
      NOTREACHED();
  }
}

void RecordDismissalReasonMetrics(PasswordStoreBackendErrorType error_type,
                                  messages::DismissReason dismiss_reason) {
  base::UmaHistogramEnumeration("PasswordManager.ErrorMessageDismissalReason." +
                                    GetErrorMessageName(error_type),
                                dismiss_reason, messages::DismissReason::COUNT);
}

void RecordErrorTypeMetrics(PasswordStoreBackendErrorType error_type) {
  base::UmaHistogramEnumeration("PasswordManager.ErrorMessageDisplayReason",
                                error_type);
}

void SetVerifyItIsYouMessageContent(
    messages::MessageWrapper* message,
    password_manager::ErrorMessageFlowType flow_type) {
  message->SetTitle(l10n_util::GetStringUTF16(IDS_VERIFY_IT_IS_YOU));

  std::u16string description = l10n_util::GetStringUTF16(
      flow_type == password_manager::ErrorMessageFlowType::kSaveFlow
          ? IDS_PASSWORD_ERROR_DESCRIPTION_SIGN_UP
          : IDS_PASSWORD_ERROR_DESCRIPTION_SIGN_IN);
  message->SetDescription(description);

  message->SetPrimaryButtonText(
      l10n_util::GetStringUTF16(IDS_PASSWORD_ERROR_VERIFY_BUTTON_TITLE));

  message->SetIconResourceId(ResourceMapper::MapToJavaDrawableId(
      IDR_ANDORID_MESSAGE_PASSWORD_MANAGER_ERROR));

  message->DisableIconTint();
}

void SetUpdateGmsCoreMessageContent(messages::MessageWrapper* message,
                                    PasswordStoreBackendErrorType error_type) {
  CHECK(error_type ==
            PasswordStoreBackendErrorType::kGMSCoreOutdatedSavingPossible ||
        error_type ==
            PasswordStoreBackendErrorType::kGMSCoreOutdatedSavingDisabled);

  message->SetPrimaryButtonText(
      l10n_util::GetStringUTF16(IDS_UPDATE_GMS_BUTTON_TITLE));

  if (error_type ==
      PasswordStoreBackendErrorType::kGMSCoreOutdatedSavingPossible) {
    message->SetTitle(l10n_util::GetStringUTF16(IDS_UPDATE_GMS));
    message->SetDescription(
        l10n_util::GetStringUTF16(IDS_UPDATE_GMS_TO_SAVE_PASSWORDS_TO_ACCOUNT));
    message->SetIconResourceId(ResourceMapper::MapToJavaDrawableId(
        IDR_ANDROID_PASSWORD_MANAGER_LOGO_24DP));
  } else {
    message->SetTitle(l10n_util::GetStringUTF16(IDS_UPDATE_TO_SAVE_PASSWORDS));
    message->SetDescription(
        l10n_util::GetStringUTF16(IDS_UPDATE_GMS_TO_SAVE_PASSWORDS));
    message->SetIconResourceId(
        ResourceMapper::MapToJavaDrawableId(IDR_ANDROID_IC_ERROR));
  }
  message->DisableIconTint();
}

}  // namespace

PasswordManagerErrorMessageDelegate::PasswordManagerErrorMessageDelegate(
    std::unique_ptr<PasswordManagerErrorMessageHelperBridge> bridge_)
    : helper_bridge_(std::move(bridge_)) {}

PasswordManagerErrorMessageDelegate::~PasswordManagerErrorMessageDelegate() =
    default;

void PasswordManagerErrorMessageDelegate::DismissPasswordManagerErrorMessage(
    messages::DismissReason dismiss_reason) {
  if (message_) {
    messages::MessageDispatcherBridge::Get()->DismissMessage(message_.get(),
                                                             dismiss_reason);
  }
}

void PasswordManagerErrorMessageDelegate::MaybeDisplayErrorMessage(
    content::WebContents* web_contents,
    PrefService* pref_service,
    password_manager::ErrorMessageFlowType flow_type,
    PasswordStoreBackendErrorType error_type,
    base::OnceCallback<void()> dismissal_callback) {
  DCHECK(web_contents);

  if (!ShouldShowErrorUI(web_contents, error_type)) {
    // Even if no message was technically shown, the owner of `this` should know
    // that it has served its purpose and can be safely destroyed.
    std::move(dismissal_callback).Run();
    return;
  }

  DCHECK(!message_);
  message_ =
      CreateMessage(web_contents, error_type, std::move(dismissal_callback));
  error_type_ = error_type;
  switch (error_type) {
    case PasswordStoreBackendErrorType::kAuthErrorResolvable:
    case PasswordStoreBackendErrorType::kAuthErrorUnresolvable:
    case PasswordStoreBackendErrorType::kKeyRetrievalRequired:
      SetVerifyItIsYouMessageContent(message_.get(), flow_type);
      break;
    case PasswordStoreBackendErrorType::kGMSCoreOutdatedSavingPossible:
    case PasswordStoreBackendErrorType::kGMSCoreOutdatedSavingDisabled:
      SetUpdateGmsCoreMessageContent(message_.get(), error_type);
      break;
    case PasswordStoreBackendErrorType::kUncategorized:
    case PasswordStoreBackendErrorType::kKeychainError:
      // Other error types aren't supported.
      NOTREACHED_IN_MIGRATION();
  }

  messages::MessageDispatcherBridge::Get()->EnqueueMessage(
      message_.get(), web_contents, messages::MessageScopeType::WEB_CONTENTS,
      messages::MessagePriority::kUrgent);
  helper_bridge_->SaveErrorUIShownTimestamp(web_contents);
}

bool PasswordManagerErrorMessageDelegate::ShouldShowErrorUI(
    content::WebContents* web_contents,
    password_manager::PasswordStoreBackendErrorType error_type) {
  switch (error_type) {
    case PasswordStoreBackendErrorType::kAuthErrorResolvable:
    case PasswordStoreBackendErrorType::kAuthErrorUnresolvable:
    case PasswordStoreBackendErrorType::kKeyRetrievalRequired:
      return helper_bridge_->ShouldShowSignInErrorUI(web_contents);
    case PasswordStoreBackendErrorType::kGMSCoreOutdatedSavingPossible:
    case PasswordStoreBackendErrorType::kGMSCoreOutdatedSavingDisabled:
      return helper_bridge_->ShouldShowUpdateGMSCoreErrorUI(web_contents);
    case PasswordStoreBackendErrorType::kUncategorized:
    case PasswordStoreBackendErrorType::kKeychainError:
      // Other error types aren't supported.
      NOTREACHED();
  }
}

std::unique_ptr<messages::MessageWrapper>
PasswordManagerErrorMessageDelegate::CreateMessage(
    content::WebContents* web_contents,
    PasswordStoreBackendErrorType error_type,
    base::OnceCallback<void()> dismissal_callback) {
  messages::MessageIdentifier message_id =
      messages::MessageIdentifier::PASSWORD_MANAGER_ERROR;

  base::OnceClosure action_callback = base::BindOnce(
      &PasswordManagerErrorMessageDelegate::HandleActionButtonClicked,
      weak_ptr_factory_.GetWeakPtr(), web_contents, error_type);

  messages::MessageWrapper::DismissCallback post_dismissal_callback =
      base::BindOnce(
          &PasswordManagerErrorMessageDelegate::HandleMessageDismissed,
          weak_ptr_factory_.GetWeakPtr())
          .Then(std::move(dismissal_callback));

  RecordErrorTypeMetrics(error_type);

  return std::make_unique<messages::MessageWrapper>(
      message_id, std::move(action_callback),
      std::move(post_dismissal_callback));
}

void PasswordManagerErrorMessageDelegate::HandleMessageDismissed(
    messages::DismissReason dismiss_reason) {
  RecordDismissalReasonMetrics(error_type_, dismiss_reason);
  message_.reset();
}

void PasswordManagerErrorMessageDelegate::HandleActionButtonClicked(
    content::WebContents* web_contents,
    PasswordStoreBackendErrorType error) {
  switch (error) {
    case PasswordStoreBackendErrorType::kAuthErrorResolvable:
    case PasswordStoreBackendErrorType::kAuthErrorUnresolvable:
      helper_bridge_->StartUpdateAccountCredentialsFlow(web_contents);
      break;
    case PasswordStoreBackendErrorType::kKeyRetrievalRequired:
      helper_bridge_->StartTrustedVaultKeyRetrievalFlow(web_contents);
      break;
    case PasswordStoreBackendErrorType::kGMSCoreOutdatedSavingPossible:
    case PasswordStoreBackendErrorType::kGMSCoreOutdatedSavingDisabled:
      helper_bridge_->LaunchGmsUpdate(web_contents);
      break;
    case PasswordStoreBackendErrorType::kUncategorized:
    case PasswordStoreBackendErrorType::kKeychainError:
      // Other error types aren't supported.
      NOTREACHED_IN_MIGRATION();
  }
}