chromium/ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_card_mediator.mm

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

#import "ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_card_mediator.h"

#import <vector>

#import "base/i18n/message_formatter.h"
#import "base/metrics/user_metrics.h"
#import "base/strings/sys_string_conversions.h"
#import "base/strings/utf_string_conversions.h"
#import "components/autofill/core/browser/browser_autofill_manager.h"
#import "components/autofill/core/browser/data_model/credit_card.h"
#import "components/autofill/core/browser/personal_data_manager.h"
#import "components/autofill/core/common/autofill_payments_features.h"
#import "components/autofill/ios/browser/personal_data_manager_observer_bridge.h"
#import "ios/chrome/browser/autofill/ui_bundled/manual_fill/card_consumer.h"
#import "ios/chrome/browser/autofill/ui_bundled/manual_fill/card_list_delegate.h"
#import "ios/chrome/browser/autofill/ui_bundled/manual_fill/full_card_request_result_delegate_bridge.h"
#import "ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_action_cell.h"
#import "ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_card_cell.h"
#import "ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_content_injector.h"
#import "ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_credit_card+CreditCard.h"
#import "ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_credit_card.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/shared/ui/list_model/list_model.h"
#import "ios/chrome/browser/shared/ui/table_view/table_view_model.h"
#import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h"
#import "ios/chrome/browser/ui/menu/browser_action_factory.h"
#import "ios/chrome/common/ui/reauthentication/reauthentication_module.h"
#import "ios/chrome/grit/ios_strings.h"
#import "net/base/registry_controlled_domains/registry_controlled_domain.h"
#import "ui/base/l10n/l10n_util.h"
#import "ui/base/l10n/l10n_util_mac.h"
#import "ui/base/resource/resource_bundle.h"
#import "url/gurl.h"

using autofill::CreditCard;
using manual_fill::PaymentFieldType;

namespace manual_fill {

// Returns `true` if overflow menu actions should be made available in the
// manual fill cell of a card with the given `record_type`.
bool ShouldShowMenuActionsInManualFallback(CreditCard::RecordType record_type) {
  switch (record_type) {
    case autofill::CreditCard::RecordType::kLocalCard:
    case autofill::CreditCard::RecordType::kMaskedServerCard:
      return IsKeyboardAccessoryUpgradeEnabled();
    case autofill::CreditCard::RecordType::kVirtualCard:
      return NO;
    case autofill::CreditCard::RecordType::kFullServerCard:
      // Full server cards are a temporary cached state and should never be
      // being offered as a suggestion for manual fill.
      NOTREACHED();
  }
}

}  // namespace manual_fill

@interface ManualFillCardMediator () <PersonalDataManagerObserver>

// All available credit cards.
// TODO(crbug.com/361606673): Store cards by value.
@property(nonatomic, assign) std::vector<CreditCard*> cards;

@end

@implementation ManualFillCardMediator {
  // Personal data manager to be observed.
  raw_ptr<autofill::PersonalDataManager> _personalDataManager;

  // C++ to ObjC bridge for PersonalDataManagerObserver.
  std::unique_ptr<autofill::PersonalDataManagerObserverBridge>
      _personalDataManagerObserver;

  // Reauthentication Module used for re-authentication.
  ReauthenticationModule* _reauthenticationModule;

  // Indicates whether to show the autofill button for the items.
  BOOL _showAutofillFormButton;
}

- (instancetype)initWithPersonalDataManager:
                    (autofill::PersonalDataManager*)personalDataManager
                     reauthenticationModule:
                         (ReauthenticationModule*)reauthenticationModule
                     showAutofillFormButton:(BOOL)showAutofillFormButton {
  self = [super init];
  if (self) {
    _personalDataManager = personalDataManager;
    _personalDataManagerObserver.reset(
        new autofill::PersonalDataManagerObserverBridge(self));
    _personalDataManager->AddObserver(_personalDataManagerObserver.get());
    _cards =
        _personalDataManager->payments_data_manager().GetCreditCardsToSuggest();
    _reauthenticationModule = reauthenticationModule;
    _showAutofillFormButton = showAutofillFormButton;
  }
  return self;
}

- (void)setConsumer:(id<ManualFillCardConsumer>)consumer {
  if (consumer == _consumer) {
    return;
  }
  _consumer = consumer;
  [self postCardsToConsumer];
  [self postActionsToConsumer];
}

- (const CreditCard*)findCreditCardfromGUID:(NSString*)GUID {
  for (CreditCard* card : self.cards) {
    NSString* cppGUID =
        base::SysUTF16ToNSString(base::ASCIIToUTF16(card->guid()));
    if ([cppGUID isEqualToString:GUID])
      return card;
  }
  return nil;
}

- (void)disconnect {
  if (_personalDataManager && _personalDataManagerObserver.get()) {
    _personalDataManager->RemoveObserver(_personalDataManagerObserver.get());
    _personalDataManagerObserver.reset();
  }
}

#pragma mark - PersonalDataManagerObserver

- (void)onPersonalDataChanged {
  self.cards =
      _personalDataManager->payments_data_manager().GetCreditCardsToSuggest();
  if (self.consumer) {
    [self postCardsToConsumer];
    [self postActionsToConsumer];
  }
}

#pragma mark - Private

// Posts the cards to the consumer.
- (void)postCardsToConsumer {
  if (!self.consumer) {
    return;
  }

  // Holds the cards that will be presented in the UI. Can differ from
  // `self.cards` as a virtual card will be created for the cards that are
  // enrolled to have one (if any).
  std::vector<CreditCard*> cardsToPresent = {};

  // Holds the created virtual cards. Needed so that the created virtual cards
  // won't go out of scope before the end of the method.
  std::vector<std::unique_ptr<CreditCard>> virtualCards;

  // Go through `self.cards` and create a virtual card for every card that is
  // enrolled to have one.
  for (CreditCard* card : self.cards) {
    // Virtual cards are ordered directly before their original card.
    if (base::FeatureList::IsEnabled(
            autofill::features::kAutofillEnableVirtualCards) &&
        card->virtual_card_enrollment_state() ==
            CreditCard::VirtualCardEnrollmentState::kEnrolled) {
      std::unique_ptr<CreditCard> virtualCard =
          std::make_unique<CreditCard>(CreditCard::CreateVirtualCard(*card));
      cardsToPresent.push_back(virtualCard.get());
      virtualCards.push_back(std::move(virtualCard));
    }
    cardsToPresent.push_back(card);
  }

  int cardsToPresentCount = cardsToPresent.size();
  NSMutableArray* cardItems =
      [[NSMutableArray alloc] initWithCapacity:cardsToPresentCount];
  for (int i = 0; i < cardsToPresentCount; i++) {
    CreditCard* card = cardsToPresent[i];
    NSString* cellIndexAccessibilityLabel = base::SysUTF16ToNSString(
        base::i18n::MessageFormatter::FormatWithNamedArgs(
            l10n_util::GetStringUTF16(
                IDS_IOS_MANUAL_FALLBACK_PAYMENT_CELL_INDEX),
            "count", cardsToPresentCount, "position", i + 1));

    [cardItems addObject:[self createManualFillCardItemForCard:card
                                                     cellIndex:i
                                   cellIndexAccessibilityLabel:
                                       cellIndexAccessibilityLabel]];
  }

  [self.consumer presentCards:cardItems];
}

// Creates a ManualFillCardItem for the given `card`. When `card` is of a type
// other than "virtual", it must be retained indefinetely as it will be passed
// to PrceduralBlocks upon the creation of menu actions (see
// `-createMenuActionsForCard`). When `card` is virtual, it is sufficient to
// retain it for the duration of the this method as no menu actions will be
// created for this type of card.
// TODO(crbug.com/361606673): Pass `card` by value instead.
- (ManualFillCardItem*)createManualFillCardItemForCard:(CreditCard*)card
                                             cellIndex:(NSInteger)cellIndex
                           cellIndexAccessibilityLabel:
                               (NSString*)cellIndexAccessibilityLabel {
  ManualFillCreditCard* manualFillCreditCard = [[ManualFillCreditCard alloc]
      initWithCreditCard:*card
                    icon:[self iconForCreditCard:card]];
  NSArray<UIAction*>* menuActions =
      manual_fill::ShouldShowMenuActionsInManualFallback(card->record_type())
          ? [self createMenuActionsForCard:card]
          : @[];

  return
      [[ManualFillCardItem alloc] initWithCreditCard:manualFillCreditCard
                                     contentInjector:self.contentInjector
                                  navigationDelegate:self.navigationDelegate
                                         menuActions:menuActions
                                           cellIndex:cellIndex
                         cellIndexAccessibilityLabel:cellIndexAccessibilityLabel
                              showAutofillFormButton:_showAutofillFormButton];
}

- (void)postActionsToConsumer {
  if (!self.consumer) {
    return;
  }
  NSString* manageCreditCardsTitle =
      l10n_util::GetNSString(IDS_IOS_MANUAL_FALLBACK_MANAGE_CREDIT_CARDS);
  __weak __typeof(self) weakSelf = self;
  ManualFillActionItem* manageCreditCardsItem = [[ManualFillActionItem alloc]
      initWithTitle:manageCreditCardsTitle
             action:^{
               base::RecordAction(base::UserMetricsAction(
                   "ManualFallback_CreditCard_OpenManageCreditCard"));
               [weakSelf.navigationDelegate openCardSettings];
             }];
  manageCreditCardsItem.accessibilityIdentifier =
      manual_fill::kManagePaymentMethodsAccessibilityIdentifier;

  NSString* addCreditCardsTitle =
      l10n_util::GetNSString(IDS_IOS_MANUAL_FALLBACK_ADD_PAYMENT_METHOD);

  ManualFillActionItem* addCreditCardsItem = [[ManualFillActionItem alloc]
      initWithTitle:addCreditCardsTitle
             action:^{
               base::RecordAction(base::UserMetricsAction(
                   "ManualFallback_CreditCard_OpenAddCreditCard"));
               [weakSelf.navigationDelegate openAddCreditCard];
             }];
  addCreditCardsItem.accessibilityIdentifier =
      manual_fill::kAddPaymentMethodAccessibilityIdentifier;
  [self.consumer presentActions:@[ addCreditCardsItem, manageCreditCardsItem ]];
}

// Creates an "Edit" and a "Show Details" UIAction to be used with a UIMenu.
- (NSArray<UIAction*>*)createMenuActionsForCard:(const CreditCard*)card {
  ActionFactory* actionFactory = [[ActionFactory alloc]
      initWithScenario:
          kMenuScenarioHistogramAutofillManualFallbackPaymentEntry];

  __weak __typeof(self) weakSelf = self;
  UIAction* editAction = [actionFactory actionToEditWithBlock:^{
    [weakSelf openAddressDetails:card inEditMode:YES];
  }];

  UIAction* showDetailsAction = [actionFactory actionToShowDetailsWithBlock:^{
    [weakSelf openAddressDetails:card inEditMode:NO];
  }];

  return @[ editAction, showDetailsAction ];
}

// Requests the `navigationDelegate` to open the details of the given `card`.
// `editMode` indicates whether the details should be opened in edit mode.
- (void)openAddressDetails:(const CreditCard*)card inEditMode:(BOOL)editMode {
  base::RecordAction(base::UserMetricsAction(
      editMode ? "ManualFallback_CreditCard_OverflowMenu_Edit"
               : "ManualFallback_CreditCard_OverflowMenu_ShowDetails"));

  [self.navigationDelegate openCardDetails:card inEditMode:editMode];
}

// Returns the icon associated with the provided credit card.
- (UIImage*)iconForCreditCard:(const autofill::CreditCard*)creditCard {
  // Check if custom card art is available.
  GURL cardArtURL =
      _personalDataManager->payments_data_manager().GetCardArtURL(*creditCard);
  if (IsKeyboardAccessoryUpgradeEnabled() && !cardArtURL.is_empty() &&
      cardArtURL.is_valid()) {
    gfx::Image* image = _personalDataManager->payments_data_manager()
                            .GetCreditCardArtImageForUrl(cardArtURL);
    if (image) {
      return image->ToUIImage();
    }
  }

  // Otherwise, try to get the default card icon
  autofill::Suggestion::Icon icon = creditCard->CardIconForAutofillSuggestion();
  return icon == autofill::Suggestion::Icon::kNoIcon
             ? nil
             : ui::ResourceBundle::GetSharedInstance()
                   .GetNativeImageNamed(
                       autofill::CreditCard::IconResourceId(icon))
                   .ToUIImage();
}

#pragma mark - FullCardRequestResultDelegateObserving

- (void)onFullCardRequestSucceeded:(const CreditCard&)card
                         fieldType:(manual_fill::PaymentFieldType)fieldType {
  // Credit card are not shown as 'Secure'.
  ManualFillCreditCard* manualFillCreditCard = [[ManualFillCreditCard alloc]
      initWithCreditCard:card
                    icon:[self iconForCreditCard:&card]];
  NSString* fillValue;
  if (base::FeatureList::IsEnabled(
          autofill::features::kAutofillEnableVirtualCards)) {
    switch (fieldType) {
      case PaymentFieldType::kCardNumber:
        fillValue = manualFillCreditCard.number;
        break;
      case PaymentFieldType::kExpirationMonth:
        fillValue = manualFillCreditCard.expirationMonth;
        break;
      case PaymentFieldType::kExpirationYear:
        fillValue = manualFillCreditCard.expirationYear;
        break;
      case PaymentFieldType::kCVC:
        fillValue = manualFillCreditCard.CVC;
        break;
    }
  } else {
    fillValue = manualFillCreditCard.number;
  }

  // Don't replace the locked card with the unlocked one, so the user will
  // have to unlock it again, if needed.
  [self.contentInjector userDidPickContent:fillValue
                             passwordField:NO
                             requiresHTTPS:YES];
}

- (void)onFullCardRequestFailed {
  // This is called on user cancelling, so there's nothing to do here.
}

@end