blob: c1ac98c2b01fb2bbf04dcb319619e9b2d0ef8960 [file] [log] [blame]
// Copyright 2018 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.
#import "ios/chrome/browser/ui/settings/google_services_settings_mediator.h"
#include "base/auto_reset.h"
#include "base/mac/foundation_util.h"
#include "components/autofill/core/common/autofill_prefs.h"
#include "components/metrics/metrics_pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/unified_consent/pref_names.h"
#include "ios/chrome/browser/pref_names.h"
#import "ios/chrome/browser/signin/authentication_service.h"
#import "ios/chrome/browser/signin/authentication_service_factory.h"
#import "ios/chrome/browser/ui/settings/cells/settings_image_detail_text_item.h"
#import "ios/chrome/browser/ui/settings/cells/sync_switch_item.h"
#import "ios/chrome/browser/ui/settings/google_services_settings_command_handler.h"
#import "ios/chrome/browser/ui/settings/sync_utils/sync_util.h"
#import "ios/chrome/browser/ui/settings/utils/observable_boolean.h"
#import "ios/chrome/browser/ui/settings/utils/pref_backed_boolean.h"
#include "ios/chrome/grit/ios_chromium_strings.h"
#include "ios/chrome/grit/ios_strings.h"
#include "ui/base/l10n/l10n_util.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
using l10n_util::GetNSString;
typedef NSArray<TableViewItem*>* ItemArray;
namespace {
// List of sections.
typedef NS_ENUM(NSInteger, SectionIdentifier) {
SyncFeedbackSectionIdentifier = kSectionIdentifierEnumZero,
NonPersonalizedSectionIdentifier,
};
// List of items.
typedef NS_ENUM(NSInteger, ItemType) {
// SyncErrorSectionIdentifier,
RestartAuthenticationFlowErrorItemType = kItemTypeEnumZero,
ReauthDialogAsSyncIsInAuthErrorItemType,
ShowPassphraseDialogErrorItemType,
// NonPersonalizedSectionIdentifier section.
AutocompleteSearchesAndURLsItemType,
PreloadPagesItemType,
ImproveChromeItemType,
BetterSearchAndBrowsingItemType,
};
} // namespace
@interface GoogleServicesSettingsMediator () <BooleanObserver>
// Unified consent service.
@property(nonatomic, assign)
unified_consent::UnifiedConsentService* unifiedConsentService;
// Returns YES if the user is authenticated.
@property(nonatomic, assign, readonly) BOOL isAuthenticated;
// Sync setup service.
@property(nonatomic, assign, readonly) SyncSetupService* syncSetupService;
// Preference value for the autocomplete wallet feature.
@property(nonatomic, strong, readonly)
PrefBackedBoolean* autocompleteWalletPreference;
// Preference value for the "Autocomplete searches and URLs" feature.
@property(nonatomic, strong, readonly)
PrefBackedBoolean* autocompleteSearchPreference;
// Preference value for the "Preload pages for faster browsing" feature.
@property(nonatomic, strong, readonly)
PrefBackedBoolean* preloadPagesPreference;
// Preference value for the "Preload pages for faster browsing" for Wifi-Only.
// TODO(crbug.com/872101): Needs to create the UI to change from Wifi-Only to
// always
@property(nonatomic, strong, readonly)
PrefBackedBoolean* preloadPagesWifiOnlyPreference;
// Preference value for the "Help improve Chromium's features" feature.
@property(nonatomic, strong, readonly)
PrefBackedBoolean* sendDataUsagePreference;
// Preference value for the "Help improve Chromium's features" for Wifi-Only.
// TODO(crbug.com/872101): Needs to create the UI to change from Wifi-Only to
// always
@property(nonatomic, strong, readonly)
PrefBackedBoolean* sendDataUsageWifiOnlyPreference;
// Preference value for the "Make searches and browsing better" feature.
@property(nonatomic, strong, readonly)
PrefBackedBoolean* anonymizedDataCollectionPreference;
// YES if at least one switch in the personalized section is currently animating
// from one state to another.
@property(nonatomic, assign) BOOL personalizedSectionBeingAnimated;
// All the items for the personalized section.
@property(nonatomic, strong, readonly) ItemArray personalizedItems;
// Item for the autocomplete wallet feature.
@property(nonatomic, strong, readonly) SyncSwitchItem* autocompleteWalletItem;
// All the items for the non-personalized section.
@property(nonatomic, strong, readonly) ItemArray nonPersonalizedItems;
@end
@implementation GoogleServicesSettingsMediator
@synthesize nonPersonalizedItems = _nonPersonalizedItems;
#pragma mark - Load model
- (instancetype)
initWithUserPrefService:(PrefService*)userPrefService
localPrefService:(PrefService*)localPrefService
syncSetupService:(SyncSetupService*)syncSetupService
unifiedConsentService:
(unified_consent::UnifiedConsentService*)unifiedConsentService {
self = [super init];
if (self) {
DCHECK(userPrefService);
DCHECK(localPrefService);
DCHECK(syncSetupService);
DCHECK(unifiedConsentService);
_syncSetupService = syncSetupService;
_unifiedConsentService = unifiedConsentService;
_autocompleteWalletPreference = [[PrefBackedBoolean alloc]
initWithPrefService:userPrefService
prefName:autofill::prefs::kAutofillWalletImportEnabled];
_autocompleteWalletPreference.observer = self;
_autocompleteSearchPreference = [[PrefBackedBoolean alloc]
initWithPrefService:userPrefService
prefName:prefs::kSearchSuggestEnabled];
_autocompleteSearchPreference.observer = self;
_preloadPagesPreference = [[PrefBackedBoolean alloc]
initWithPrefService:userPrefService
prefName:prefs::kNetworkPredictionEnabled];
_preloadPagesPreference.observer = self;
_preloadPagesWifiOnlyPreference = [[PrefBackedBoolean alloc]
initWithPrefService:userPrefService
prefName:prefs::kNetworkPredictionWifiOnly];
_sendDataUsagePreference = [[PrefBackedBoolean alloc]
initWithPrefService:localPrefService
prefName:metrics::prefs::kMetricsReportingEnabled];
_sendDataUsagePreference.observer = self;
_sendDataUsageWifiOnlyPreference = [[PrefBackedBoolean alloc]
initWithPrefService:localPrefService
prefName:prefs::kMetricsReportingWifiOnly];
_anonymizedDataCollectionPreference = [[PrefBackedBoolean alloc]
initWithPrefService:userPrefService
prefName:unified_consent::prefs::
kUrlKeyedAnonymizedDataCollectionEnabled];
_anonymizedDataCollectionPreference.observer = self;
}
return self;
}
// Loads NonPersonalizedSectionIdentifier section.
- (void)loadNonPersonalizedSection {
TableViewModel* model = self.consumer.tableViewModel;
[model addSectionWithIdentifier:NonPersonalizedSectionIdentifier];
for (TableViewItem* item in self.nonPersonalizedItems) {
[model addItem:item
toSectionWithIdentifier:NonPersonalizedSectionIdentifier];
}
[self updateNonPersonalizedSection];
}
#pragma mark - Properties
- (BOOL)isAuthenticated {
return self.authService->IsAuthenticated();
}
- (ItemArray)nonPersonalizedItems {
if (!_nonPersonalizedItems) {
SyncSwitchItem* autocompleteSearchesAndURLsItem = [self
switchItemWithItemType:AutocompleteSearchesAndURLsItemType
textStringID:
IDS_IOS_GOOGLE_SERVICES_SETTINGS_AUTOCOMPLETE_SEARCHES_AND_URLS_TEXT
detailStringID:
IDS_IOS_GOOGLE_SERVICES_SETTINGS_AUTOCOMPLETE_SEARCHES_AND_URLS_DETAIL
dataType:0];
SyncSwitchItem* preloadPagesItem =
[self switchItemWithItemType:PreloadPagesItemType
textStringID:
IDS_IOS_GOOGLE_SERVICES_SETTINGS_PRELOAD_PAGES_TEXT
detailStringID:
IDS_IOS_GOOGLE_SERVICES_SETTINGS_PRELOAD_PAGES_DETAIL
dataType:0];
SyncSwitchItem* improveChromeItem =
[self switchItemWithItemType:ImproveChromeItemType
textStringID:
IDS_IOS_GOOGLE_SERVICES_SETTINGS_IMPROVE_CHROME_TEXT
detailStringID:
IDS_IOS_GOOGLE_SERVICES_SETTINGS_IMPROVE_CHROME_DETAIL
dataType:0];
SyncSwitchItem* betterSearchAndBrowsingItemType = [self
switchItemWithItemType:BetterSearchAndBrowsingItemType
textStringID:
IDS_IOS_GOOGLE_SERVICES_SETTINGS_BETTER_SEARCH_AND_BROWSING_TEXT
detailStringID:
IDS_IOS_GOOGLE_SERVICES_SETTINGS_BETTER_SEARCH_AND_BROWSING_DETAIL
dataType:0];
_nonPersonalizedItems = @[
autocompleteSearchesAndURLsItem, preloadPagesItem, improveChromeItem,
betterSearchAndBrowsingItemType
];
}
return _nonPersonalizedItems;
}
#pragma mark - Private
// Creates a SyncSwitchItem instance.
- (SyncSwitchItem*)switchItemWithItemType:(NSInteger)itemType
textStringID:(int)textStringID
detailStringID:(int)detailStringID
dataType:(NSInteger)dataType {
SyncSwitchItem* switchItem = [[SyncSwitchItem alloc] initWithType:itemType];
switchItem.text = GetNSString(textStringID);
if (detailStringID)
switchItem.detailText = GetNSString(detailStringID);
switchItem.dataType = dataType;
return switchItem;
}
// Creates a item to display the sync error.
- (SettingsImageDetailTextItem*)createSyncErrorItemWithItemType:
(NSInteger)itemType {
SettingsImageDetailTextItem* syncErrorItem =
[[SettingsImageDetailTextItem alloc] initWithType:itemType];
syncErrorItem.text = l10n_util::GetNSString(IDS_IOS_SYNC_ERROR_TITLE);
syncErrorItem.detailText =
GetSyncErrorDescriptionForSyncSetupService(self.syncSetupService);
{
// TODO(crbug.com/889470): Needs asset for the sync error.
CGSize size = CGSizeMake(40, 40);
UIGraphicsBeginImageContextWithOptions(size, YES, 0);
[[UIColor grayColor] setFill];
UIRectFill(CGRectMake(0, 0, size.width, size.height));
syncErrorItem.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
return syncErrorItem;
}
// Reloads the sync feedback section. If |notifyConsummer| is YES, the consomer
// is notified to add or remove the sync error section.
- (void)updateSyncErrorSectionAndNotifyConsumer:(BOOL)notifyConsummer {
TableViewModel* model = self.consumer.tableViewModel;
BOOL hasError = NO;
ItemType type;
if (self.isAuthenticated) {
switch (self.syncSetupService->GetSyncServiceState()) {
case SyncSetupService::kSyncServiceUnrecoverableError:
type = RestartAuthenticationFlowErrorItemType;
hasError = YES;
break;
case SyncSetupService::kSyncServiceSignInNeedsUpdate:
type = ReauthDialogAsSyncIsInAuthErrorItemType;
hasError = YES;
break;
case SyncSetupService::kSyncServiceNeedsPassphrase:
type = ShowPassphraseDialogErrorItemType;
hasError = YES;
break;
case SyncSetupService::kNoSyncServiceError:
case SyncSetupService::kSyncServiceCouldNotConnect:
case SyncSetupService::kSyncServiceServiceUnavailable:
break;
}
}
if (!hasError) {
// No action to do, therefore the sync error section should not be visibled.
if ([model hasSectionForSectionIdentifier:SyncFeedbackSectionIdentifier]) {
// Remove the sync error item if it exists.
NSUInteger sectionIndex =
[model sectionForSectionIdentifier:SyncFeedbackSectionIdentifier];
[model removeSectionWithIdentifier:SyncFeedbackSectionIdentifier];
if (notifyConsummer) {
NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:sectionIndex];
[self.consumer deleteSections:indexSet];
}
}
return;
}
// Add the sync error item and its section (if it doesn't already exist) and
// reload them.
BOOL sectionAdded = NO;
SettingsImageDetailTextItem* syncErrorItem =
[self createSyncErrorItemWithItemType:type];
if (![model hasSectionForSectionIdentifier:SyncFeedbackSectionIdentifier]) {
// Adding the sync error item and its section.
[model insertSectionWithIdentifier:SyncFeedbackSectionIdentifier atIndex:0];
[model addItem:syncErrorItem
toSectionWithIdentifier:SyncFeedbackSectionIdentifier];
sectionAdded = YES;
} else {
[model
deleteAllItemsFromSectionWithIdentifier:SyncFeedbackSectionIdentifier];
[model addItem:syncErrorItem
toSectionWithIdentifier:SyncFeedbackSectionIdentifier];
}
if (notifyConsummer) {
NSUInteger sectionIndex =
[model sectionForSectionIdentifier:SyncFeedbackSectionIdentifier];
NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:sectionIndex];
if (sectionAdded) {
[self.consumer insertSections:indexSet];
} else {
[self.consumer reloadSections:indexSet];
}
}
}
// Updates the non-personalized section according to the user consent.
- (void)updateNonPersonalizedSection {
for (TableViewItem* item in self.nonPersonalizedItems) {
ItemType type = static_cast<ItemType>(item.type);
SyncSwitchItem* switchItem =
base::mac::ObjCCastStrict<SyncSwitchItem>(item);
switch (type) {
case AutocompleteSearchesAndURLsItemType:
switchItem.on = self.autocompleteSearchPreference.value;
break;
case PreloadPagesItemType:
switchItem.on = self.preloadPagesPreference.value;
break;
case ImproveChromeItemType:
switchItem.on = self.sendDataUsagePreference.value;
break;
case BetterSearchAndBrowsingItemType:
switchItem.on = self.anonymizedDataCollectionPreference.value;
break;
case RestartAuthenticationFlowErrorItemType:
case ReauthDialogAsSyncIsInAuthErrorItemType:
case ShowPassphraseDialogErrorItemType:
NOTREACHED();
break;
}
}
}
#pragma mark - GoogleServicesSettingsViewControllerModelDelegate
- (void)googleServicesSettingsViewControllerLoadModel:
(GoogleServicesSettingsViewController*)controller {
DCHECK_EQ(self.consumer, controller);
[self loadNonPersonalizedSection];
[self updateSyncErrorSectionAndNotifyConsumer:NO];
}
#pragma mark - GoogleServicesSettingsServiceDelegate
- (void)toggleSwitchItem:(SyncSwitchItem*)switchItem withValue:(BOOL)value {
ItemType type = static_cast<ItemType>(switchItem.type);
switch (type) {
case AutocompleteSearchesAndURLsItemType:
self.autocompleteSearchPreference.value = value;
break;
case PreloadPagesItemType:
self.preloadPagesPreference.value = value;
if (value) {
// Should be wifi only, until https://crbug.com/872101 is fixed.
self.preloadPagesWifiOnlyPreference.value = YES;
}
break;
case ImproveChromeItemType:
self.sendDataUsagePreference.value = value;
if (value) {
// Should be wifi only, until https://crbug.com/872101 is fixed.
self.sendDataUsageWifiOnlyPreference.value = YES;
}
break;
case BetterSearchAndBrowsingItemType:
self.anonymizedDataCollectionPreference.value = value;
break;
case RestartAuthenticationFlowErrorItemType:
case ReauthDialogAsSyncIsInAuthErrorItemType:
case ShowPassphraseDialogErrorItemType:
NOTREACHED();
break;
}
}
- (void)didSelectItem:(TableViewItem*)item {
ItemType type = static_cast<ItemType>(item.type);
switch (type) {
case RestartAuthenticationFlowErrorItemType:
[self.commandHandler restartAuthenticationFlow];
break;
case ReauthDialogAsSyncIsInAuthErrorItemType:
[self.commandHandler openReauthDialogAsSyncIsInAuthError];
break;
case ShowPassphraseDialogErrorItemType:
[self.commandHandler openPassphraseDialog];
break;
case AutocompleteSearchesAndURLsItemType:
case PreloadPagesItemType:
case ImproveChromeItemType:
case BetterSearchAndBrowsingItemType:
break;
}
}
#pragma mark - BooleanObserver
- (void)booleanDidChange:(id<ObservableBoolean>)observableBoolean {
[self updateNonPersonalizedSection];
TableViewModel* model = self.consumer.tableViewModel;
NSUInteger index =
[model sectionForSectionIdentifier:NonPersonalizedSectionIdentifier];
NSIndexSet* sectionIndexToReload = [NSIndexSet indexSetWithIndex:index];
[self.consumer reloadSections:sectionIndexToReload];
}
@end