| // Copyright 2015 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. |
| |
| #include "components/autofill/core/browser/webdata/autofill_wallet_syncable_service.h" |
| |
| #include <stddef.h> |
| |
| #include <algorithm> |
| #include <map> |
| #include <utility> |
| |
| #include "base/logging.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/strings/string_piece.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "components/autofill/core/browser/webdata/autofill_table.h" |
| #include "components/autofill/core/browser/webdata/autofill_webdata_backend.h" |
| #include "components/autofill/core/browser/webdata/autofill_webdata_service.h" |
| #include "components/autofill/core/common/autofill_util.h" |
| #include "components/sync/model/sync_change_processor.h" |
| #include "components/sync/model/sync_error_factory.h" |
| #include "components/sync/protocol/sync.pb.h" |
| |
| namespace autofill { |
| |
| namespace { |
| |
| void* AutofillWalletSyncableServiceUserDataKey() { |
| // Use the address of a static so that COMDAT folding won't ever fold |
| // with something else. |
| static int user_data_key = 0; |
| return reinterpret_cast<void*>(&user_data_key); |
| } |
| |
| const char* CardNetworkFromAutofillWalletCardType( |
| sync_pb::WalletMaskedCreditCard::WalletCardType type) { |
| switch (type) { |
| case sync_pb::WalletMaskedCreditCard::AMEX: |
| return kAmericanExpressCard; |
| case sync_pb::WalletMaskedCreditCard::DISCOVER: |
| return kDiscoverCard; |
| case sync_pb::WalletMaskedCreditCard::JCB: |
| return kJCBCard; |
| case sync_pb::WalletMaskedCreditCard::MASTER_CARD: |
| return kMasterCard; |
| case sync_pb::WalletMaskedCreditCard::UNIONPAY: |
| return kUnionPay; |
| case sync_pb::WalletMaskedCreditCard::VISA: |
| return kVisaCard; |
| |
| // These aren't supported by the client, so just declare a generic card. |
| case sync_pb::WalletMaskedCreditCard::MAESTRO: |
| case sync_pb::WalletMaskedCreditCard::SOLO: |
| case sync_pb::WalletMaskedCreditCard::SWITCH: |
| default: |
| return kGenericCard; |
| } |
| } |
| |
| CreditCard::CardType CardTypeFromAutofillWalletCardClass( |
| sync_pb::WalletMaskedCreditCard::WalletCardClass card_class) { |
| switch (card_class) { |
| case sync_pb::WalletMaskedCreditCard::CREDIT: |
| return CreditCard::CARD_TYPE_CREDIT; |
| case sync_pb::WalletMaskedCreditCard::DEBIT: |
| return CreditCard::CARD_TYPE_DEBIT; |
| case sync_pb::WalletMaskedCreditCard::PREPAID: |
| return CreditCard::CARD_TYPE_PREPAID; |
| default: |
| return CreditCard::CARD_TYPE_UNKNOWN; |
| } |
| } |
| |
| CreditCard::ServerStatus ServerToLocalWalletCardStatus( |
| sync_pb::WalletMaskedCreditCard::WalletCardStatus status) { |
| switch (status) { |
| case sync_pb::WalletMaskedCreditCard::VALID: |
| return CreditCard::OK; |
| case sync_pb::WalletMaskedCreditCard::EXPIRED: |
| default: |
| DCHECK_EQ(sync_pb::WalletMaskedCreditCard::EXPIRED, status); |
| return CreditCard::EXPIRED; |
| } |
| } |
| |
| CreditCard CardFromWalletCardSpecifics( |
| const sync_pb::WalletMaskedCreditCard& card) { |
| CreditCard result(CreditCard::MASKED_SERVER_CARD, card.id()); |
| result.SetNumber(base::UTF8ToUTF16(card.last_four())); |
| result.SetServerStatus(ServerToLocalWalletCardStatus(card.status())); |
| result.SetNetworkForMaskedCard( |
| CardNetworkFromAutofillWalletCardType(card.type())); |
| result.set_card_type(CardTypeFromAutofillWalletCardClass(card.card_class())); |
| result.SetRawInfo(CREDIT_CARD_NAME_FULL, |
| base::UTF8ToUTF16(card.name_on_card())); |
| result.SetExpirationMonth(card.exp_month()); |
| result.SetExpirationYear(card.exp_year()); |
| result.set_billing_address_id(card.billing_address_id()); |
| result.set_bank_name(card.bank_name()); |
| return result; |
| } |
| |
| AutofillProfile ProfileFromWalletCardSpecifics( |
| const sync_pb::WalletPostalAddress& address) { |
| AutofillProfile profile(AutofillProfile::SERVER_PROFILE, std::string()); |
| |
| // AutofillProfile stores multi-line addresses with newline separators. |
| std::vector<base::StringPiece> street_address( |
| address.street_address().begin(), address.street_address().end()); |
| profile.SetRawInfo(ADDRESS_HOME_STREET_ADDRESS, |
| base::UTF8ToUTF16(base::JoinString(street_address, "\n"))); |
| |
| profile.SetRawInfo(COMPANY_NAME, base::UTF8ToUTF16(address.company_name())); |
| profile.SetRawInfo(ADDRESS_HOME_STATE, |
| base::UTF8ToUTF16(address.address_1())); |
| profile.SetRawInfo(ADDRESS_HOME_CITY, base::UTF8ToUTF16(address.address_2())); |
| profile.SetRawInfo(ADDRESS_HOME_DEPENDENT_LOCALITY, |
| base::UTF8ToUTF16(address.address_3())); |
| // AutofillProfile doesn't support address_4 ("sub dependent locality"). |
| profile.SetRawInfo(ADDRESS_HOME_ZIP, |
| base::UTF8ToUTF16(address.postal_code())); |
| profile.SetRawInfo(ADDRESS_HOME_SORTING_CODE, |
| base::UTF8ToUTF16(address.sorting_code())); |
| profile.SetRawInfo(ADDRESS_HOME_COUNTRY, |
| base::UTF8ToUTF16(address.country_code())); |
| profile.set_language_code(address.language_code()); |
| |
| // SetInfo instead of SetRawInfo so the constituent pieces will be parsed |
| // for these data types. |
| profile.SetInfo(NAME_FULL, base::UTF8ToUTF16(address.recipient_name()), |
| profile.language_code()); |
| profile.SetInfo(PHONE_HOME_WHOLE_NUMBER, |
| base::UTF8ToUTF16(address.phone_number()), |
| profile.language_code()); |
| |
| profile.GenerateServerProfileIdentifier(); |
| |
| return profile; |
| } |
| |
| PaymentsCustomerData CustomerDataFromSyncSpecifics( |
| const sync_pb::PaymentsCustomerData& customer_data) { |
| return PaymentsCustomerData{/*customer_id=*/customer_data.id()}; |
| } |
| |
| } // namespace |
| |
| // static |
| template <class Item> |
| AutofillWalletSyncableService::Diff AutofillWalletSyncableService::ComputeDiff( |
| const std::vector<std::unique_ptr<Item>>& old_data, |
| const std::vector<Item>& new_data) { |
| // Build vectors of pointers, so that we can mutate (sort) them. |
| std::vector<const Item*> old_ptrs; |
| old_ptrs.reserve(old_data.size()); |
| for (const std::unique_ptr<Item>& old_item : old_data) |
| old_ptrs.push_back(old_item.get()); |
| std::vector<const Item*> new_ptrs; |
| new_ptrs.reserve(new_data.size()); |
| for (const Item& new_item : new_data) |
| new_ptrs.push_back(&new_item); |
| |
| // Sort our vectors. |
| auto compare = [](const Item* lhs, const Item* rhs) { |
| return lhs->Compare(*rhs) < 0; |
| }; |
| std::sort(old_ptrs.begin(), old_ptrs.end(), compare); |
| std::sort(new_ptrs.begin(), new_ptrs.end(), compare); |
| |
| // Walk over both of them and count added/removed elements. |
| Diff result; |
| auto old_it = old_ptrs.begin(); |
| auto new_it = new_ptrs.begin(); |
| while (old_it != old_ptrs.end()) { |
| if (new_it == new_ptrs.end()) { |
| result.items_removed += std::distance(old_it, old_ptrs.end()); |
| break; |
| } |
| int cmp = (*old_it)->Compare(**new_it); |
| if (cmp < 0) { |
| ++result.items_removed; |
| ++old_it; |
| } else if (cmp == 0) { |
| ++old_it; |
| ++new_it; |
| } else { |
| ++result.items_added; |
| ++new_it; |
| } |
| } |
| result.items_added += std::distance(new_it, new_ptrs.end()); |
| |
| DCHECK_EQ(old_data.size() + result.items_added - result.items_removed, |
| new_data.size()); |
| |
| return result; |
| } |
| |
| AutofillWalletSyncableService::AutofillWalletSyncableService( |
| AutofillWebDataBackend* webdata_backend, |
| const std::string& app_locale) |
| : webdata_backend_(webdata_backend) {} |
| |
| AutofillWalletSyncableService::~AutofillWalletSyncableService() {} |
| |
| syncer::SyncMergeResult AutofillWalletSyncableService::MergeDataAndStartSyncing( |
| syncer::ModelType type, |
| const syncer::SyncDataList& initial_sync_data, |
| std::unique_ptr<syncer::SyncChangeProcessor> sync_processor, |
| std::unique_ptr<syncer::SyncErrorFactory> sync_error_factory) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| sync_processor_ = std::move(sync_processor); |
| syncer::SyncMergeResult result = |
| SetSyncData(initial_sync_data, /*is_initial_merge=*/true); |
| if (webdata_backend_) |
| webdata_backend_->NotifyThatSyncHasStarted(type); |
| return result; |
| } |
| |
| void AutofillWalletSyncableService::StopSyncing(syncer::ModelType type) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK_EQ(type, syncer::AUTOFILL_WALLET_DATA); |
| sync_processor_.reset(); |
| } |
| |
| syncer::SyncDataList AutofillWalletSyncableService::GetAllSyncData( |
| syncer::ModelType type) const { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| // This data type is never synced "up" so we don't need to implement this. |
| syncer::SyncDataList current_data; |
| return current_data; |
| } |
| |
| syncer::SyncError AutofillWalletSyncableService::ProcessSyncChanges( |
| const base::Location& from_here, |
| const syncer::SyncChangeList& change_list) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| // Don't bother handling incremental updates. Wallet data changes very rarely |
| // and has few items. Instead, just get all the current data and save it. |
| SetSyncData(sync_processor_->GetAllSyncData(syncer::AUTOFILL_WALLET_DATA), |
| /*is_initial_merge=*/false); |
| return syncer::SyncError(); |
| } |
| |
| // static |
| void AutofillWalletSyncableService::CreateForWebDataServiceAndBackend( |
| AutofillWebDataService* web_data_service, |
| AutofillWebDataBackend* webdata_backend, |
| const std::string& app_locale) { |
| web_data_service->GetDBUserData()->SetUserData( |
| AutofillWalletSyncableServiceUserDataKey(), |
| base::WrapUnique( |
| new AutofillWalletSyncableService(webdata_backend, app_locale))); |
| } |
| |
| // static |
| AutofillWalletSyncableService* |
| AutofillWalletSyncableService::FromWebDataService( |
| AutofillWebDataService* web_data_service) { |
| return static_cast<AutofillWalletSyncableService*>( |
| web_data_service->GetDBUserData()->GetUserData( |
| AutofillWalletSyncableServiceUserDataKey())); |
| } |
| |
| void AutofillWalletSyncableService::InjectStartSyncFlare( |
| const syncer::SyncableService::StartSyncFlare& flare) { |
| flare_ = flare; |
| } |
| |
| // static |
| void AutofillWalletSyncableService::PopulateWalletTypesFromSyncData( |
| const syncer::SyncDataList& data_list, |
| std::vector<CreditCard>* wallet_cards, |
| std::vector<AutofillProfile>* wallet_addresses, |
| std::vector<PaymentsCustomerData>* customer_data) { |
| std::map<std::string, std::string> ids; |
| |
| for (const syncer::SyncData& data : data_list) { |
| DCHECK_EQ(syncer::AUTOFILL_WALLET_DATA, data.GetDataType()); |
| const sync_pb::AutofillWalletSpecifics& autofill_specifics = |
| data.GetSpecifics().autofill_wallet(); |
| switch (autofill_specifics.type()) { |
| case sync_pb::AutofillWalletSpecifics::MASKED_CREDIT_CARD: |
| wallet_cards->push_back( |
| CardFromWalletCardSpecifics(autofill_specifics.masked_card())); |
| break; |
| case sync_pb::AutofillWalletSpecifics::POSTAL_ADDRESS: |
| wallet_addresses->push_back( |
| ProfileFromWalletCardSpecifics(autofill_specifics.address())); |
| |
| // Map the sync billing address id to the profile's id. |
| ids[autofill_specifics.address().id()] = |
| wallet_addresses->back().server_id(); |
| break; |
| case sync_pb::AutofillWalletSpecifics::CUSTOMER_DATA: |
| customer_data->push_back( |
| CustomerDataFromSyncSpecifics(autofill_specifics.customer_data())); |
| break; |
| case sync_pb::AutofillWalletSpecifics::UNKNOWN: |
| // Just ignore new entry types that the client doesn't know about. |
| break; |
| } |
| } |
| |
| // Set the billing address of the wallet cards to the id of the appropriate |
| // profile. |
| for (CreditCard& card : *wallet_cards) { |
| auto it = ids.find(card.billing_address_id()); |
| if (it != ids.end()) |
| card.set_billing_address_id(it->second); |
| } |
| } |
| |
| // static |
| void AutofillWalletSyncableService::CopyRelevantMetadataFromDisk( |
| const AutofillTable& table, |
| std::vector<CreditCard>* cards_from_server) { |
| std::vector<std::unique_ptr<CreditCard>> cards_on_disk; |
| table.GetServerCreditCards(&cards_on_disk); |
| |
| // The reasons behind brute-force search are explained in SetDataIfChanged. |
| for (const auto& saved_card : cards_on_disk) { |
| for (CreditCard& server_card : *cards_from_server) { |
| if (saved_card->server_id() == server_card.server_id()) { |
| // The wallet data doesn't have the use stats. Use the ones present on |
| // disk to not overwrite them with bad data. |
| server_card.set_use_count(saved_card->use_count()); |
| server_card.set_use_date(saved_card->use_date()); |
| |
| // Keep the billing address id of the saved cards only if it points to |
| // a local address. |
| if (saved_card->billing_address_id().length() == kLocalGuidSize) { |
| server_card.set_billing_address_id(saved_card->billing_address_id()); |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| syncer::SyncMergeResult AutofillWalletSyncableService::SetSyncData( |
| const syncer::SyncDataList& data_list, |
| bool is_initial_merge) { |
| std::vector<CreditCard> wallet_cards; |
| std::vector<AutofillProfile> wallet_addresses; |
| std::vector<PaymentsCustomerData> customer_data; |
| PopulateWalletTypesFromSyncData(data_list, &wallet_cards, &wallet_addresses, |
| &customer_data); |
| |
| // Users can set billing address of the server credit card locally, but that |
| // information does not propagate to either Chrome Sync or Google Payments |
| // server. To preserve user's preferred billing address and most recent use |
| // stats, copy them from disk into |wallet_cards|. |
| AutofillTable* table = |
| AutofillTable::FromWebDatabase(webdata_backend_->GetDatabase()); |
| CopyRelevantMetadataFromDisk(*table, &wallet_cards); |
| |
| // In the common case, the database won't have changed. Committing an update |
| // to the database will require at least one DB page write and will schedule |
| // a fsync. To avoid this I/O, it should be more efficient to do a read and |
| // only do the writes if something changed. |
| |
| std::vector<std::unique_ptr<CreditCard>> existing_cards; |
| table->GetServerCreditCards(&existing_cards); |
| Diff cards_diff = ComputeDiff(existing_cards, wallet_cards); |
| if (!cards_diff.IsEmpty()) |
| table->SetServerCreditCards(wallet_cards); |
| |
| std::vector<std::unique_ptr<AutofillProfile>> existing_addresses; |
| table->GetServerProfiles(&existing_addresses); |
| Diff addresses_diff = ComputeDiff(existing_addresses, wallet_addresses); |
| if (!addresses_diff.IsEmpty()) |
| table->SetServerProfiles(wallet_addresses); |
| |
| syncer::SyncMergeResult merge_result(syncer::AUTOFILL_WALLET_DATA); |
| merge_result.set_num_items_before_association( |
| static_cast<int>(existing_cards.size() + existing_addresses.size())); |
| merge_result.set_num_items_after_association( |
| static_cast<int>(wallet_cards.size() + wallet_addresses.size())); |
| |
| // We report the diff to metrics only if this is an incremental change where |
| // the user had sync set-up (having PaymentsCustomerData is a pre-requisite |
| // for having any other data) and continues to have sync set-up (continuing |
| // having a PaymentsCustomerData entity). As a side effect, this excludes |
| // reporting diffs for users that newly got a GPay account and sync |
| // PaymentsCustomerData for the first time but this is the best we can do to |
| // have the metrics consistent with USS implementation. |
| bool should_report_diff; |
| |
| if (customer_data.empty()) { |
| // Clears the data only. |
| table->SetPaymentsCustomerData(nullptr); |
| should_report_diff = false; |
| } else { |
| std::unique_ptr<PaymentsCustomerData> existing_entry; |
| table->GetPaymentsCustomerData(&existing_entry); |
| should_report_diff = existing_entry != nullptr; |
| |
| // In case there were multiple entries (and there shouldn't!), we take the |
| // first entry in the vector. |
| DCHECK_EQ(1u, customer_data.size()); |
| table->SetPaymentsCustomerData(&customer_data.front()); |
| } |
| |
| if (is_initial_merge && cards_diff.IsEmpty() && addresses_diff.IsEmpty()) { |
| // Skip reporting the diff on startup if there is no change. It can happen |
| // that new updates come into the directory before |
| // MergeDataAndStartSyncing() gets called; such updates should get reported. |
| // We cannot distinguish them precisely so we at least report the non-empty |
| // diffs which _must_ be caused by an update from the server. |
| should_report_diff = false; |
| } |
| |
| if (should_report_diff) { |
| UMA_HISTOGRAM_COUNTS_100("Autofill.WalletCards2.Added", |
| cards_diff.items_added); |
| UMA_HISTOGRAM_COUNTS_100("Autofill.WalletCards2.Removed", |
| cards_diff.items_removed); |
| UMA_HISTOGRAM_COUNTS_100("Autofill.WalletCards2.AddedOrRemoved", |
| cards_diff.items_added + cards_diff.items_removed); |
| |
| UMA_HISTOGRAM_COUNTS_100("Autofill.WalletAddresses2.Added", |
| addresses_diff.items_added); |
| UMA_HISTOGRAM_COUNTS_100("Autofill.WalletAddresses2.Removed", |
| addresses_diff.items_removed); |
| UMA_HISTOGRAM_COUNTS_100( |
| "Autofill.WalletAddresses2.AddedOrRemoved", |
| addresses_diff.items_added + addresses_diff.items_removed); |
| } |
| |
| if (webdata_backend_ && (!cards_diff.IsEmpty() || !addresses_diff.IsEmpty())) |
| webdata_backend_->NotifyOfMultipleAutofillChanges(); |
| |
| return merge_result; |
| } |
| |
| } // namespace autofill |