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"
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 "components/autofill/core/browser/autofill_country.h"
22 #include "components/autofill/core/browser/autofill_profile.h"
23 #include "components/autofill/core/browser/autofill_type.h"
24 #include "components/autofill/core/browser/form_structure.h"
25 #include "components/autofill/core/browser/phone_number.h"
26 #include "components/autofill/core/common/autofill_pref_names.h"
27 #include "components/autofill/core/common/form_data.h"
28 #include "grit/components_strings.h"
29 #include "ui/base/l10n/l10n_util_mac.h"
34 // The maximum number of instances when the access Address Book prompt should
36 int kMaxTimesToShowMacAddressBook = 5;
38 // There is an uncommon sequence of events that causes the Address Book
39 // permissions dialog to appear more than once for a given install of Chrome.
40 // 1. Chrome has previously presented the Address Book permissions dialog.
41 // 2. Chrome is launched.
42 // 3. Chrome performs an auto-update, and changes its binary.
43 // 4. Chrome attempts to access the Address Book for the first time since (2).
44 // This sequence of events is rare because Chrome attempts to acess the Address
45 // Book when the user focuses most form fields, so (4) generally occurs before
46 // (3). For more details, see http://crbug.com/381763.
48 // When this sequence of events does occur, Chrome should not attempt to access
49 // the Address Book unless the user explicitly asks Chrome to do so. The
50 // jarring nature of the permissions dialog is worse than the potential benefit
51 // of pulling information from the Address Book.
53 // Set to true after the Address Book is accessed for the first time.
54 static bool g_accessed_address_book = false;
56 // Set to true after the Chrome binary has been changed.
57 static bool g_binary_changed = false;
59 const char kAddressBookOrigin[] = "OS X Address Book";
61 // Whether Chrome has attempted to access the Mac Address Book.
62 bool HasQueriedMacAddressBook(PrefService* pref_service) {
63 return pref_service->GetBoolean(prefs::kAutofillMacAddressBookQueried);
66 // Whether the user wants Chrome to use the AddressBook to populate Autofill
68 bool ShouldUseAddressBook(PrefService* pref_service) {
69 return pref_service->GetBoolean(prefs::kAutofillUseMacAddressBook);
72 // Records a UMA metric indicating whether an attempt to access the Address
73 // Book was skipped because doing so would cause the Address Book permissions
74 // prompt to incorrectly appear.
75 void RecordAccessSkipped(bool skipped) {
76 UMA_HISTOGRAM_BOOLEAN("Autofill.AddressBook.AccessSkipped", skipped);
79 ABAddressBook* GetAddressBook(PrefService* pref_service) {
80 bool first_access = !HasQueriedMacAddressBook(pref_service);
82 // +[ABAddressBook sharedAddressBook] throws an exception internally in
83 // circumstances that aren't clear. The exceptions are only observed in crash
84 // reports, so it is unknown whether they would be caught by AppKit and nil
85 // returned, or if they would take down the app. In either case, avoid
86 // crashing. http://crbug.com/129022
87 ABAddressBook* addressBook = base::mac::RunBlockIgnoringExceptions(
88 ^{ return [ABAddressBook sharedAddressBook]; });
89 UMA_HISTOGRAM_BOOLEAN("Autofill.AddressBookAvailable", addressBook != nil);
92 UMA_HISTOGRAM_BOOLEAN("Autofill.AddressBookAvailableOnFirstAttempt",
96 g_accessed_address_book = true;
97 pref_service->SetBoolean(prefs::kAutofillMacAddressBookQueried, true);
101 // This implementation makes use of the Address Book API. Profiles are
102 // generated that correspond to addresses in the "me" card that reside in the
103 // user's Address Book. The caller passes a vector of profiles into the
104 // the constructer and then initiate the fetch from the Mac Address Book "me"
105 // card using the main |GetAddressBookMeCard()| method. This clears any
106 // existing addresses and populates new addresses derived from the data found
108 class AuxiliaryProfilesImpl {
110 // Constructor takes a reference to the |profiles| that will be filled in
111 // by the subsequent call to |GetAddressBookMeCard()|. |profiles| may not
113 explicit AuxiliaryProfilesImpl(ScopedVector<AutofillProfile>* profiles)
114 : profiles_(*profiles) {
116 virtual ~AuxiliaryProfilesImpl() {}
118 // Import the "me" card from the Mac Address Book and fill in |profiles_|.
119 void GetAddressBookMeCard(const std::string& app_locale,
120 PrefService* pref_service,
121 bool record_metrics);
124 void GetAddressBookNames(ABPerson* me,
125 NSString* addressLabelRaw,
126 AutofillProfile* profile);
127 void GetAddressBookAddress(const std::string& app_locale,
128 NSDictionary* address,
129 AutofillProfile* profile);
130 void GetAddressBookEmail(ABPerson* me,
131 NSString* addressLabelRaw,
132 AutofillProfile* profile);
133 void GetAddressBookPhoneNumbers(ABPerson* me,
134 NSString* addressLabelRaw,
135 AutofillProfile* profile);
138 // A reference to the profiles this class populates.
139 ScopedVector<AutofillProfile>& profiles_;
141 DISALLOW_COPY_AND_ASSIGN(AuxiliaryProfilesImpl);
144 // This method uses the |ABAddressBook| system service to fetch the "me" card
145 // from the active user's address book. It looks for the user address
146 // information and translates it to the internal list of |AutofillProfile| data
148 void AuxiliaryProfilesImpl::GetAddressBookMeCard(const std::string& app_locale,
149 PrefService* pref_service,
150 bool record_metrics) {
153 // The user does not want Chrome to use the AddressBook to populate Autofill
155 if (!ShouldUseAddressBook(pref_service))
158 // See the comment at the definition of g_accessed_address_book for an
159 // explanation of this logic.
160 if (g_binary_changed && !g_accessed_address_book) {
162 RecordAccessSkipped(true);
167 RecordAccessSkipped(false);
169 ABAddressBook* addressBook = GetAddressBook(pref_service);
171 ABPerson* me = [addressBook me];
175 ABMultiValue* addresses = [me valueForProperty:kABAddressProperty];
177 // The number of characters at the end of the GUID to reserve for
178 // distinguishing addresses within the "me" card. Cap the number of addresses
179 // we will fetch to the number that can be distinguished by this fragment of
181 const size_t kNumAddressGUIDChars = 2;
182 const size_t kNumHexDigits = 16;
183 const size_t kMaxAddressCount = pow(kNumHexDigits, kNumAddressGUIDChars);
184 NSUInteger count = MIN([addresses count], kMaxAddressCount);
185 for (NSUInteger i = 0; i < count; i++) {
186 NSDictionary* address = [addresses valueAtIndex:i];
187 NSString* addressLabelRaw = [addresses labelAtIndex:i];
189 // Create a new profile where the guid is set to the guid portion of the
190 // |kABUIDProperty| taken from from the "me" address. The format of
191 // the |kABUIDProperty| is "<guid>:ABPerson", so we're stripping off the
192 // raw guid here and using it directly, with one modification: we update the
193 // last |kNumAddressGUIDChars| characters in the GUID to reflect the address
194 // variant. Note that we capped the number of addresses above, so this is
196 const size_t kGUIDLength = 36U;
197 const size_t kTrimmedGUIDLength = kGUIDLength - kNumAddressGUIDChars;
198 std::string guid = base::SysNSStringToUTF8(
199 [me valueForProperty:kABUIDProperty]).substr(0, kTrimmedGUIDLength);
201 // The format string to print |kNumAddressGUIDChars| hexadecimal characters,
202 // left-padded with 0's.
203 const std::string kAddressGUIDFormat =
204 base::StringPrintf("%%0%" PRIuS "X", kNumAddressGUIDChars);
205 guid += base::StringPrintf(kAddressGUIDFormat.c_str(), i);
206 DCHECK_EQ(kGUIDLength, guid.size());
208 scoped_ptr<AutofillProfile> profile(
209 new AutofillProfile(guid, kAddressBookOrigin));
210 DCHECK(base::IsValidGUID(profile->guid()));
212 // Fill in name and company information.
213 GetAddressBookNames(me, addressLabelRaw, profile.get());
215 // Fill in address information.
216 GetAddressBookAddress(app_locale, address, profile.get());
218 // Fill in email information.
219 GetAddressBookEmail(me, addressLabelRaw, profile.get());
221 // Fill in phone number information.
222 GetAddressBookPhoneNumbers(me, addressLabelRaw, profile.get());
224 profiles_.push_back(profile.release());
228 // Name and company information is stored once in the Address Book against
229 // multiple addresses. We replicate that information for each profile.
230 // We only propagate the company name to work profiles.
231 void AuxiliaryProfilesImpl::GetAddressBookNames(
233 NSString* addressLabelRaw,
234 AutofillProfile* profile) {
235 NSString* firstName = [me valueForProperty:kABFirstNameProperty];
236 NSString* middleName = [me valueForProperty:kABMiddleNameProperty];
237 NSString* lastName = [me valueForProperty:kABLastNameProperty];
238 NSString* companyName = [me valueForProperty:kABOrganizationProperty];
240 profile->SetRawInfo(NAME_FIRST, base::SysNSStringToUTF16(firstName));
241 profile->SetRawInfo(NAME_MIDDLE, base::SysNSStringToUTF16(middleName));
242 profile->SetRawInfo(NAME_LAST, base::SysNSStringToUTF16(lastName));
243 if ([addressLabelRaw isEqualToString:kABAddressWorkLabel])
244 profile->SetRawInfo(COMPANY_NAME, base::SysNSStringToUTF16(companyName));
247 // Addresss information from the Address Book may span multiple lines.
248 // If it does then we represent the address with two lines in the profile. The
249 // second line we join with commas.
250 // For example: "c/o John Doe\n1122 Other Avenue\nApt #7" translates to
251 // line 1: "c/o John Doe", line 2: "1122 Other Avenue, Apt #7".
252 void AuxiliaryProfilesImpl::GetAddressBookAddress(const std::string& app_locale,
253 NSDictionary* address,
254 AutofillProfile* profile) {
255 if (NSString* addressField = [address objectForKey:kABAddressStreetKey]) {
256 // If there are newlines in the address, split into two lines.
257 if ([addressField rangeOfCharacterFromSet:
258 [NSCharacterSet newlineCharacterSet]].location != NSNotFound) {
259 NSArray* chunks = [addressField componentsSeparatedByCharactersInSet:
260 [NSCharacterSet newlineCharacterSet]];
261 DCHECK([chunks count] > 1);
263 NSString* separator =
264 l10n_util::GetNSString(IDS_AUTOFILL_ADDRESS_LINE_SEPARATOR);
266 NSString* addressField1 = [chunks objectAtIndex:0];
267 NSString* addressField2 =
268 [[chunks subarrayWithRange:NSMakeRange(1, [chunks count] - 1)]
269 componentsJoinedByString:separator];
270 profile->SetRawInfo(ADDRESS_HOME_LINE1,
271 base::SysNSStringToUTF16(addressField1));
272 profile->SetRawInfo(ADDRESS_HOME_LINE2,
273 base::SysNSStringToUTF16(addressField2));
275 profile->SetRawInfo(ADDRESS_HOME_LINE1,
276 base::SysNSStringToUTF16(addressField));
280 if (NSString* city = [address objectForKey:kABAddressCityKey])
281 profile->SetRawInfo(ADDRESS_HOME_CITY, base::SysNSStringToUTF16(city));
283 if (NSString* state = [address objectForKey:kABAddressStateKey])
284 profile->SetRawInfo(ADDRESS_HOME_STATE, base::SysNSStringToUTF16(state));
286 if (NSString* zip = [address objectForKey:kABAddressZIPKey])
287 profile->SetRawInfo(ADDRESS_HOME_ZIP, base::SysNSStringToUTF16(zip));
289 if (NSString* country = [address objectForKey:kABAddressCountryKey]) {
290 profile->SetInfo(AutofillType(ADDRESS_HOME_COUNTRY),
291 base::SysNSStringToUTF16(country),
296 // Fills in email address matching current address label. Note that there may
297 // be multiple matching email addresses for a given label. We take the
298 // first we find (topmost) as preferred.
299 void AuxiliaryProfilesImpl::GetAddressBookEmail(
301 NSString* addressLabelRaw,
302 AutofillProfile* profile) {
303 ABMultiValue* emailAddresses = [me valueForProperty:kABEmailProperty];
304 NSString* emailAddress = nil;
305 for (NSUInteger j = 0, emailCount = [emailAddresses count];
306 j < emailCount; j++) {
307 NSString* emailAddressLabelRaw = [emailAddresses labelAtIndex:j];
308 if ([emailAddressLabelRaw isEqualToString:addressLabelRaw]) {
309 emailAddress = [emailAddresses valueAtIndex:j];
313 profile->SetRawInfo(EMAIL_ADDRESS, base::SysNSStringToUTF16(emailAddress));
316 // Fills in telephone numbers. Each of these are special cases.
317 // We match two cases: home/tel, work/tel.
318 // Note, we traverse in reverse order so that top values in address book
320 void AuxiliaryProfilesImpl::GetAddressBookPhoneNumbers(
322 NSString* addressLabelRaw,
323 AutofillProfile* profile) {
324 ABMultiValue* phoneNumbers = [me valueForProperty:kABPhoneProperty];
325 for (NSUInteger k = 0, phoneCount = [phoneNumbers count];
326 k < phoneCount; k++) {
327 NSUInteger reverseK = phoneCount - k - 1;
328 NSString* phoneLabelRaw = [phoneNumbers labelAtIndex:reverseK];
329 if ([addressLabelRaw isEqualToString:kABAddressHomeLabel] &&
330 [phoneLabelRaw isEqualToString:kABPhoneHomeLabel]) {
331 base::string16 homePhone = base::SysNSStringToUTF16(
332 [phoneNumbers valueAtIndex:reverseK]);
333 profile->SetRawInfo(PHONE_HOME_WHOLE_NUMBER, homePhone);
334 } else if ([addressLabelRaw isEqualToString:kABAddressWorkLabel] &&
335 [phoneLabelRaw isEqualToString:kABPhoneWorkLabel]) {
336 base::string16 workPhone = base::SysNSStringToUTF16(
337 [phoneNumbers valueAtIndex:reverseK]);
338 profile->SetRawInfo(PHONE_HOME_WHOLE_NUMBER, workPhone);
339 } else if ([phoneLabelRaw isEqualToString:kABPhoneMobileLabel] ||
340 [phoneLabelRaw isEqualToString:kABPhoneMainLabel]) {
341 base::string16 phone = base::SysNSStringToUTF16(
342 [phoneNumbers valueAtIndex:reverseK]);
343 profile->SetRawInfo(PHONE_HOME_WHOLE_NUMBER, phone);
350 // Populate |auxiliary_profiles_| with the Address Book data.
351 void PersonalDataManager::LoadAuxiliaryProfiles(bool record_metrics) const {
352 AuxiliaryProfilesImpl impl(&auxiliary_profiles_);
353 impl.GetAddressBookMeCard(app_locale_, pref_service_, record_metrics);
356 bool PersonalDataManager::AccessAddressBook() {
357 // The user is attempting to give Chrome access to the user's Address Book.
358 // This implicitly acknowledges that the user wants to use auxiliary
360 pref_service_->SetBoolean(prefs::kAutofillUseMacAddressBook, true);
362 // Request permissions.
363 GetAddressBook(pref_service_);
367 bool PersonalDataManager::ShouldShowAccessAddressBookSuggestion(
369 // Don't show the access Address Book prompt if the user has built up any
371 if (!web_profiles_.empty())
374 if (!enabled_pref_->GetValue())
377 if (HasQueriedMacAddressBook(pref_service_))
380 if (AccessAddressBookPromptCount() >= kMaxTimesToShowMacAddressBook)
383 switch (type.group()) {
384 case ADDRESS_BILLING:
403 void PersonalDataManager::ShowedAccessAddressBookPrompt() {
404 pref_service_->SetInteger(prefs::kAutofillMacAddressBookShowedCount,
405 AccessAddressBookPromptCount() + 1);
408 int PersonalDataManager::AccessAddressBookPromptCount() {
409 return pref_service_->GetInteger(prefs::kAutofillMacAddressBookShowedCount);
412 void PersonalDataManager::BinaryChanging() {
413 g_binary_changed = true;
416 } // namespace autofill