Include all dupe types (event when value is zero) in scan stats.
[chromium-blink-merge.git] / components / autofill / core / browser / personal_data_manager_mac.mm
blob77b06c9b1d2491ff6926ef1e0db2b1bb458aa568
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "components/autofill/core/browser/personal_data_manager.h"
7 #include <math.h>
9 #import <AddressBook/AddressBook.h>
11 #include "base/format_macros.h"
12 #include "base/guid.h"
13 #include "base/logging.h"
14 #import "base/mac/scoped_nsexception_enabler.h"
15 #include "base/memory/scoped_ptr.h"
16 #include "base/memory/scoped_vector.h"
17 #include "base/metrics/histogram.h"
18 #include "base/prefs/pref_service.h"
19 #include "base/strings/stringprintf.h"
20 #include "base/strings/sys_string_conversions.h"
21 #include "base/time/time.h"
22 #include "components/autofill/core/browser/autofill_country.h"
23 #include "components/autofill/core/browser/autofill_profile.h"
24 #include "components/autofill/core/browser/autofill_type.h"
25 #include "components/autofill/core/browser/form_structure.h"
26 #include "components/autofill/core/browser/phone_number.h"
27 #include "components/autofill/core/common/autofill_pref_names.h"
28 #include "components/autofill/core/common/form_data.h"
29 #include "grit/components_strings.h"
30 #include "ui/base/l10n/l10n_util_mac.h"
32 namespace autofill {
33 namespace {
35 // The maximum number of instances when the access Address Book prompt should
36 // be shown.
37 int kMaxTimesToShowMacAddressBook = 5;
39 // There is an uncommon sequence of events that causes the Address Book
40 // permissions dialog to appear more than once for a given install of Chrome.
41 //  1. Chrome has previously presented the Address Book permissions dialog.
42 //  2. Chrome is launched.
43 //  3. Chrome performs an auto-update, and changes its binary.
44 //  4. Chrome attempts to access the Address Book for the first time since (2).
45 // This sequence of events is rare because Chrome attempts to acess the Address
46 // Book when the user focuses most form fields, so (4) generally occurs before
47 // (3). For more details, see http://crbug.com/381763.
49 // When this sequence of events does occur, Chrome should not attempt to access
50 // the Address Book unless the user explicitly asks Chrome to do so. The
51 // jarring nature of the permissions dialog is worse than the potential benefit
52 // of pulling information from the Address Book.
54 // Set to true after the Address Book is accessed for the first time.
55 static bool g_accessed_address_book = false;
57 // Set to true after the Chrome binary has been changed.
58 static bool g_binary_changed = false;
60 const char kAddressBookOrigin[] = "OS X Address Book";
62 // Whether Chrome has attempted to access the Mac Address Book.
63 bool HasQueriedMacAddressBook(PrefService* pref_service) {
64   return pref_service->GetBoolean(prefs::kAutofillMacAddressBookQueried);
67 // Whether the user wants Chrome to use the AddressBook to populate Autofill
68 // entries.
69 bool ShouldUseAddressBook(PrefService* pref_service) {
70   // TODO(erikchen): After Address Book integration has been disabled for 6
71   // weeks, and there are no major problems, rip out all the code. Expected
72   // removal date: 07/15/2015. http://crbug.com/488146.
73   return false;
75   return pref_service->GetBoolean(prefs::kAutofillUseMacAddressBook);
78 // Records a UMA metric indicating whether an attempt to access the Address
79 // Book was skipped because doing so would cause the Address Book permissions
80 // prompt to incorrectly appear.
81 void RecordAccessSkipped(bool skipped) {
82   UMA_HISTOGRAM_BOOLEAN("Autofill.AddressBook.AccessSkipped", skipped);
85 // Emits a histogram indicating whether the Address Book contained a me-card.
86 void EmitAddressBookContainedMeCard(bool containedMeCard) {
87   UMA_HISTOGRAM_BOOLEAN("Autofill.MacAddressBook.ContainedMeCard",
88                         containedMeCard);
91 // Emits a histogram indicating whether the me-card had a name.
92 void EmitMeCardHadName(bool hadName) {
93   UMA_HISTOGRAM_BOOLEAN("Autofill.MacAddressBook.MeCard.HadName", hadName);
96 // Emits a histogram indicating whether the me-card had an address.
97 void EmitMeCardHadAddress(bool hadAddress) {
98   UMA_HISTOGRAM_BOOLEAN("Autofill.MacAddressBook.MeCard.HadAddress",
99                         hadAddress);
102 // Emits a histogram indicating whether the me-card had an email.
103 void EmitMeCardHadEmail(bool hadEmail) {
104   UMA_HISTOGRAM_BOOLEAN("Autofill.MacAddressBook.MeCard.HadEmail", hadEmail);
107 // Emits a histogram indicating whether the me-card had a phone number.
108 void EmitMeCardHadPhoneNumber(bool hadPhoneNumber) {
109   UMA_HISTOGRAM_BOOLEAN("Autofill.MacAddressBook.MeCard.HadPhoneNumber",
110                         hadPhoneNumber);
113 ABAddressBook* GetAddressBook(PrefService* pref_service) {
114   // TODO(erikchen): After Address Book integration has been disabled for 6
115   // weeks, and there are no major problems, rip out all the code. Expected
116   // removal date: 07/15/2015. http://crbug.com/488146.
117   return nil;
119   bool first_access = !HasQueriedMacAddressBook(pref_service);
121   base::Time start_time = base::Time::Now();
122   // +[ABAddressBook sharedAddressBook] throws an exception internally in
123   // circumstances that aren't clear. The exceptions are only observed in crash
124   // reports, so it is unknown whether they would be caught by AppKit and nil
125   // returned, or if they would take down the app. In either case, avoid
126   // crashing. http://crbug.com/129022
127   ABAddressBook* addressBook = base::mac::RunBlockIgnoringExceptions(
128       ^{ return [ABAddressBook sharedAddressBook]; });
129   UMA_HISTOGRAM_BOOLEAN("Autofill.AddressBookAvailable", addressBook != nil);
131   if (!g_accessed_address_book) {
132     // The amount of time that the access takes gives a good indication as to
133     // whether the user was shown a prompt.
134     UMA_HISTOGRAM_TIMES("Autofill.MacAddressBook.AccessTime",
135                         base::Time::Now() - start_time);
136   }
138   if (first_access) {
139     UMA_HISTOGRAM_BOOLEAN("Autofill.AddressBookAvailableOnFirstAttempt",
140                           addressBook != nil);
141   }
143   g_accessed_address_book = true;
144   pref_service->SetBoolean(prefs::kAutofillMacAddressBookQueried, true);
145   return addressBook;
148 // This implementation makes use of the Address Book API.  Profiles are
149 // generated that correspond to addresses in the "me" card that reside in the
150 // user's Address Book.  The caller passes a vector of profiles into the
151 // the constructer and then initiate the fetch from the Mac Address Book "me"
152 // card using the main |GetAddressBookMeCard()| method.  This clears any
153 // existing addresses and populates new addresses derived from the data found
154 // in the "me" card.
155 class AuxiliaryProfilesImpl {
156  public:
157   // Constructor takes a reference to the |profiles| that will be filled in
158   // by the subsequent call to |GetAddressBookMeCard()|.  |profiles| may not
159   // be NULL.
160   explicit AuxiliaryProfilesImpl(ScopedVector<AutofillProfile>* profiles)
161       : profiles_(*profiles) {
162   }
163   virtual ~AuxiliaryProfilesImpl() {}
165   // Import the "me" card from the Mac Address Book and fill in |profiles_|.
166   void GetAddressBookMeCard(const std::string& app_locale,
167                             PrefService* pref_service,
168                             bool record_metrics);
170  private:
171   bool GetAddressBookNames(ABPerson* me,
172                            NSString* addressLabelRaw,
173                            AutofillProfile* profile);
174   bool GetAddressBookAddress(const std::string& app_locale,
175                              NSDictionary* address,
176                              AutofillProfile* profile);
177   bool GetAddressBookEmail(ABPerson* me,
178                            NSString* addressLabelRaw,
179                            AutofillProfile* profile);
180   bool GetAddressBookPhoneNumbers(ABPerson* me,
181                                   NSString* addressLabelRaw,
182                                   AutofillProfile* profile);
184  private:
185   // A reference to the profiles this class populates.
186   ScopedVector<AutofillProfile>& profiles_;
188   DISALLOW_COPY_AND_ASSIGN(AuxiliaryProfilesImpl);
191 // This method uses the |ABAddressBook| system service to fetch the "me" card
192 // from the active user's address book.  It looks for the user address
193 // information and translates it to the internal list of |AutofillProfile| data
194 // structures.
195 void AuxiliaryProfilesImpl::GetAddressBookMeCard(const std::string& app_locale,
196                                                  PrefService* pref_service,
197                                                  bool record_metrics) {
198   profiles_.clear();
200   // The user does not want Chrome to use the AddressBook to populate Autofill
201   // entries.
202   if (!ShouldUseAddressBook(pref_service))
203     return;
205   // See the comment at the definition of g_accessed_address_book for an
206   // explanation of this logic.
207   if (g_binary_changed && !g_accessed_address_book) {
208     if (record_metrics)
209       RecordAccessSkipped(true);
210     return;
211   }
213   if (record_metrics)
214     RecordAccessSkipped(false);
216   ABAddressBook* addressBook = GetAddressBook(pref_service);
218   ABPerson* me = [addressBook me];
219   if (!me) {
220     EmitAddressBookContainedMeCard(false);
221     return;
222   }
224   EmitAddressBookContainedMeCard(true);
225   ABMultiValue* addresses = [me valueForProperty:kABAddressProperty];
227   // The number of characters at the end of the GUID to reserve for
228   // distinguishing addresses within the "me" card.  Cap the number of addresses
229   // we will fetch to the number that can be distinguished by this fragment of
230   // the GUID.
231   const size_t kNumAddressGUIDChars = 2;
232   const size_t kNumHexDigits = 16;
233   const size_t kMaxAddressCount = pow(kNumHexDigits, kNumAddressGUIDChars);
234   NSUInteger count = MIN([addresses count], kMaxAddressCount);
236   bool hasName = false;
237   bool hasEmail = false;
238   bool hasAddress = false;
239   bool hasPhoneNumber = false;
240   for (NSUInteger i = 0; i < count; i++) {
241     NSDictionary* address = [addresses valueAtIndex:i];
242     NSString* addressLabelRaw = [addresses labelAtIndex:i];
244     // Create a new profile where the guid is set to the guid portion of the
245     // |kABUIDProperty| taken from from the "me" address.  The format of
246     // the |kABUIDProperty| is "<guid>:ABPerson", so we're stripping off the
247     // raw guid here and using it directly, with one modification: we update the
248     // last |kNumAddressGUIDChars| characters in the GUID to reflect the address
249     // variant.  Note that we capped the number of addresses above, so this is
250     // safe.
251     const size_t kGUIDLength = 36U;
252     const size_t kTrimmedGUIDLength = kGUIDLength - kNumAddressGUIDChars;
253     std::string guid = base::SysNSStringToUTF8(
254         [me valueForProperty:kABUIDProperty]).substr(0, kTrimmedGUIDLength);
256     // The format string to print |kNumAddressGUIDChars| hexadecimal characters,
257     // left-padded with 0's.
258     const std::string kAddressGUIDFormat =
259         base::StringPrintf("%%0%" PRIuS "X", kNumAddressGUIDChars);
260     guid += base::StringPrintf(kAddressGUIDFormat.c_str(), i);
261     DCHECK_EQ(kGUIDLength, guid.size());
263     scoped_ptr<AutofillProfile> profile(
264         new AutofillProfile(guid, kAddressBookOrigin));
265     profile->set_record_type(AutofillProfile::AUXILIARY_PROFILE);
266     DCHECK(base::IsValidGUID(profile->guid()));
268     // Fill in name and company information.
269     hasName |= GetAddressBookNames(me, addressLabelRaw, profile.get());
271     // Fill in address information.
272     hasEmail |= GetAddressBookAddress(app_locale, address, profile.get());
274     // Fill in email information.
275     hasAddress |= GetAddressBookEmail(me, addressLabelRaw, profile.get());
277     // Fill in phone number information.
278     hasPhoneNumber |=
279         GetAddressBookPhoneNumbers(me, addressLabelRaw, profile.get());
281     profiles_.push_back(profile.release());
282   }
284   EmitMeCardHadName(hasName);
285   EmitMeCardHadEmail(hasEmail);
286   EmitMeCardHadAddress(hasAddress);
287   EmitMeCardHadPhoneNumber(hasPhoneNumber);
290 // Name and company information is stored once in the Address Book against
291 // multiple addresses.  We replicate that information for each profile.
292 // We only propagate the company name to work profiles.
293 // Returns whether |me| contains any name information.
294 bool AuxiliaryProfilesImpl::GetAddressBookNames(ABPerson* me,
295                                                 NSString* addressLabelRaw,
296                                                 AutofillProfile* profile) {
297   NSString* firstName = [me valueForProperty:kABFirstNameProperty];
298   NSString* middleName = [me valueForProperty:kABMiddleNameProperty];
299   NSString* lastName = [me valueForProperty:kABLastNameProperty];
300   NSString* companyName = [me valueForProperty:kABOrganizationProperty];
302   profile->SetRawInfo(NAME_FIRST, base::SysNSStringToUTF16(firstName));
303   profile->SetRawInfo(NAME_MIDDLE, base::SysNSStringToUTF16(middleName));
304   profile->SetRawInfo(NAME_LAST, base::SysNSStringToUTF16(lastName));
305   if ([addressLabelRaw isEqualToString:kABAddressWorkLabel])
306     profile->SetRawInfo(COMPANY_NAME, base::SysNSStringToUTF16(companyName));
308   return [firstName length] > 0 || [middleName length] > 0 ||
309          [lastName length] > 0;
312 // Addresss information from the Address Book may span multiple lines.
313 // If it does then we represent the address with two lines in the profile.  The
314 // second line we join with commas.
315 // For example:  "c/o John Doe\n1122 Other Avenue\nApt #7" translates to
316 // line 1: "c/o John Doe", line 2: "1122 Other Avenue, Apt #7".
317 // Returns whether |address| contains a complete address.
318 bool AuxiliaryProfilesImpl::GetAddressBookAddress(const std::string& app_locale,
319                                                   NSDictionary* address,
320                                                   AutofillProfile* profile) {
321   bool hasStreetAddress = false;
322   bool hasCityOrZip = false;
323   if (NSString* addressField = [address objectForKey:kABAddressStreetKey]) {
324     hasStreetAddress |= [addressField length] > 0;
326     // If there are newlines in the address, split into two lines.
327     if ([addressField rangeOfCharacterFromSet:
328             [NSCharacterSet newlineCharacterSet]].location != NSNotFound) {
329       NSArray* chunks = [addressField componentsSeparatedByCharactersInSet:
330           [NSCharacterSet newlineCharacterSet]];
331       DCHECK([chunks count] > 1);
333       NSString* separator =
334           l10n_util::GetNSString(IDS_AUTOFILL_ADDRESS_LINE_SEPARATOR);
336       NSString* addressField1 = [chunks objectAtIndex:0];
337       NSString* addressField2 =
338           [[chunks subarrayWithRange:NSMakeRange(1, [chunks count] - 1)]
339               componentsJoinedByString:separator];
340       profile->SetRawInfo(ADDRESS_HOME_LINE1,
341                           base::SysNSStringToUTF16(addressField1));
342       profile->SetRawInfo(ADDRESS_HOME_LINE2,
343                           base::SysNSStringToUTF16(addressField2));
344     } else {
345       profile->SetRawInfo(ADDRESS_HOME_LINE1,
346                           base::SysNSStringToUTF16(addressField));
347     }
348   }
350   if (NSString* city = [address objectForKey:kABAddressCityKey]) {
351     hasCityOrZip |= [city length] > 0;
352     profile->SetRawInfo(ADDRESS_HOME_CITY, base::SysNSStringToUTF16(city));
353   }
355   if (NSString* state = [address objectForKey:kABAddressStateKey])
356     profile->SetRawInfo(ADDRESS_HOME_STATE, base::SysNSStringToUTF16(state));
358   if (NSString* zip = [address objectForKey:kABAddressZIPKey]) {
359     hasCityOrZip |= [zip length] > 0;
360     profile->SetRawInfo(ADDRESS_HOME_ZIP, base::SysNSStringToUTF16(zip));
361   }
363   if (NSString* country = [address objectForKey:kABAddressCountryKey])
364     profile->SetInfo(AutofillType(ADDRESS_HOME_COUNTRY),
365                      base::SysNSStringToUTF16(country),
366                      app_locale);
368   return hasStreetAddress && hasCityOrZip;
371 // Fills in email address matching current address label.  Note that there may
372 // be multiple matching email addresses for a given label.  We take the
373 // first we find (topmost) as preferred.
374 // Returns whether |me| contains an email.
375 bool AuxiliaryProfilesImpl::GetAddressBookEmail(ABPerson* me,
376                                                 NSString* addressLabelRaw,
377                                                 AutofillProfile* profile) {
378   ABMultiValue* emailAddresses = [me valueForProperty:kABEmailProperty];
379   NSString* emailAddress = nil;
380   for (NSUInteger j = 0, emailCount = [emailAddresses count];
381        j < emailCount; j++) {
382     NSString* emailAddressLabelRaw = [emailAddresses labelAtIndex:j];
383     if ([emailAddressLabelRaw isEqualToString:addressLabelRaw]) {
384       emailAddress = [emailAddresses valueAtIndex:j];
385       break;
386     }
387   }
388   profile->SetRawInfo(EMAIL_ADDRESS, base::SysNSStringToUTF16(emailAddress));
389   return [emailAddress length] > 0;
392 // Fills in telephone numbers.  Each of these are special cases.
393 // We match two cases: home/tel, work/tel.
394 // Note, we traverse in reverse order so that top values in address book
395 // take priority.
396 // Returns whether |me| contains a phone number.
397 bool AuxiliaryProfilesImpl::GetAddressBookPhoneNumbers(
398     ABPerson* me,
399     NSString* addressLabelRaw,
400     AutofillProfile* profile) {
401   bool hasPhoneNumber = false;
402   ABMultiValue* phoneNumbers = [me valueForProperty:kABPhoneProperty];
403   for (NSUInteger k = 0, phoneCount = [phoneNumbers count];
404        k < phoneCount; k++) {
405     NSUInteger reverseK = phoneCount - k - 1;
406     NSString* phoneLabelRaw = [phoneNumbers labelAtIndex:reverseK];
407     if ([addressLabelRaw isEqualToString:kABAddressHomeLabel] &&
408         [phoneLabelRaw isEqualToString:kABPhoneHomeLabel]) {
409       base::string16 homePhone = base::SysNSStringToUTF16(
410           [phoneNumbers valueAtIndex:reverseK]);
411       profile->SetRawInfo(PHONE_HOME_WHOLE_NUMBER, homePhone);
412       hasPhoneNumber |= !homePhone.empty();
413     } else if ([addressLabelRaw isEqualToString:kABAddressWorkLabel] &&
414                [phoneLabelRaw isEqualToString:kABPhoneWorkLabel]) {
415       base::string16 workPhone = base::SysNSStringToUTF16(
416           [phoneNumbers valueAtIndex:reverseK]);
417       profile->SetRawInfo(PHONE_HOME_WHOLE_NUMBER, workPhone);
418       hasPhoneNumber |= !workPhone.empty();
419     } else if ([phoneLabelRaw isEqualToString:kABPhoneMobileLabel] ||
420                [phoneLabelRaw isEqualToString:kABPhoneMainLabel]) {
421       base::string16 phone = base::SysNSStringToUTF16(
422           [phoneNumbers valueAtIndex:reverseK]);
423       profile->SetRawInfo(PHONE_HOME_WHOLE_NUMBER, phone);
424       hasPhoneNumber |= !phone.empty();
425     }
426   }
427   return hasPhoneNumber;
430 }  // namespace
432 // Populate |auxiliary_profiles_| with the Address Book data.
433 void PersonalDataManager::LoadAuxiliaryProfiles(bool record_metrics) const {
434   AuxiliaryProfilesImpl impl(&auxiliary_profiles_);
435   impl.GetAddressBookMeCard(app_locale_, pref_service_, record_metrics);
438 bool PersonalDataManager::AccessAddressBook() {
439   // The user is attempting to give Chrome access to the user's Address Book.
440   // This implicitly acknowledges that the user wants to use auxiliary
441   // profiles.
442   pref_service_->SetBoolean(prefs::kAutofillUseMacAddressBook, true);
444   // Request permissions.
445   GetAddressBook(pref_service_);
446   return true;
449 bool PersonalDataManager::ShouldShowAccessAddressBookSuggestion(
450     AutofillType type) {
451   // TODO(erikchen): After Address Book integration has been disabled for 6
452   // weeks, and there are no major problems, rip out all the code. Expected
453   // removal date: 07/15/2015. http://crbug.com/488146.
454   return false;
456   // Don't show the access Address Book prompt if the user has built up any
457   // Autofill state.
458   if (!web_profiles_.empty())
459     return false;
461   if (!enabled_pref_->GetValue())
462     return false;
464   if (HasQueriedMacAddressBook(pref_service_))
465     return false;
467   if (AccessAddressBookPromptCount() >= kMaxTimesToShowMacAddressBook)
468     return false;
470   switch (type.group()) {
471     case ADDRESS_BILLING:
472     case ADDRESS_HOME:
473     case EMAIL:
474     case NAME:
475     case NAME_BILLING:
476     case PHONE_BILLING:
477     case PHONE_HOME:
478       return true;
479     case NO_GROUP:
480     case COMPANY:
481     case CREDIT_CARD:
482     case PASSWORD_FIELD:
483     case TRANSACTION:
484     case USERNAME_FIELD:
485       return false;
486   }
488   return false;
491 void PersonalDataManager::ShowedAccessAddressBookPrompt() {
492   pref_service_->SetInteger(prefs::kAutofillMacAddressBookShowedCount,
493                             AccessAddressBookPromptCount() + 1);
496 int PersonalDataManager::AccessAddressBookPromptCount() {
497   return pref_service_->GetInteger(prefs::kAutofillMacAddressBookShowedCount);
500 void PersonalDataManager::BinaryChanging() {
501   g_binary_changed = true;
504 }  // namespace autofill