// Copyright 2015 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/ui/settings/search_engine_table_view_controller.h"
#import <memory>
#import "base/apple/foundation_util.h"
#import "base/memory/raw_ptr.h"
#import "base/metrics/histogram_macros.h"
#import "base/metrics/user_metrics.h"
#import "base/metrics/user_metrics_action.h"
#import "base/ranges/algorithm.h"
#import "base/strings/sys_string_conversions.h"
#import "components/password_manager/core/common/password_manager_features.h"
#import "components/prefs/pref_service.h"
#import "components/search_engines/search_engine_choice/search_engine_choice_service.h"
#import "components/search_engines/search_engine_choice/search_engine_choice_utils.h"
#import "components/search_engines/search_engines_pref_names.h"
#import "components/search_engines/template_url_service.h"
#import "components/search_engines/template_url_service_observer.h"
#import "components/signin/public/base/signin_switches.h"
#import "components/strings/grit/components_strings.h"
#import "ios/chrome/browser/favicon/model/ios_chrome_favicon_loader_factory.h"
#import "ios/chrome/browser/search_engines/model/search_engine_choice_service_factory.h"
#import "ios/chrome/browser/search_engines/model/search_engine_observer_bridge.h"
#import "ios/chrome/browser/search_engines/model/template_url_service_factory.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_text_header_footer_item.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_url_item.h"
#import "ios/chrome/browser/shared/ui/table_view/table_view_utils.h"
#import "ios/chrome/browser/ui/search_engine_choice/search_engine_choice_ui_util.h"
#import "ios/chrome/browser/ui/settings/cells/settings_search_engine_item.h"
#import "ios/chrome/browser/ui/settings/settings_table_view_controller_constants.h"
#import "ios/chrome/grit/ios_strings.h"
#import "net/base/apple/url_conversions.h"
#import "ui/base/l10n/l10n_util_mac.h"
namespace {
typedef NS_ENUM(NSInteger, SectionIdentifier) {
SectionIdentifierFirstList = kSectionIdentifierEnumZero,
SectionIdentifierSecondList,
};
typedef NS_ENUM(NSInteger, ItemType) {
ItemTypeHeader = kItemTypeEnumZero,
// Used for prepopulated search engine.
ItemTypePrepopulatedEngine,
// Used for custom search engine.
ItemTypeCustomEngine,
};
const CGFloat kTableViewSeparatorLeadingInset = 56;
constexpr base::TimeDelta kMaxVisitAge = base::Days(2);
const size_t kMaxcustomSearchEngines = 3;
const char kUmaSelectDefaultSearchEngine[] =
"Search.iOS.SelectDefaultSearchEngine";
// Version of the search engine settings.
enum class SearchEngineSettingVersion {
// Search engine settings for EEA countries.
kEEASettings,
// Search engine settings for non-EEA countries with the updated settings.
kUpdatedSettings,
// Search engine settings with kSearchEngineChoiceTrigger disabled.
kDeprecatedSettings,
};
} // namespace
@interface SearchEngineTableViewController () <SearchEngineObserving> {
// Whether Settings have been dismissed.
BOOL _settingsAreDismissed;
}
// Prevent unnecessary notifications when we write to the setting.
@property(nonatomic, assign) BOOL updatingBackend;
// Whether the search engines have changed while the backend was being updated.
@property(nonatomic, assign) BOOL searchEngineChangedInBackground;
@end
@implementation SearchEngineTableViewController {
raw_ptr<TemplateURLService> _templateURLService; // weak
raw_ptr<PrefService> _prefService; // weak
raw_ptr<search_engines::SearchEngineChoiceService>
_searchEngineChoiceService; // weak
std::unique_ptr<SearchEngineObserverBridge> _observer;
// The list of choice screen search engines retrieved from the
// TemplateURLService.
std::vector<std::unique_ptr<TemplateURL>> _choiceScreenTemplateURLs;
// The first list in the page which contains prepopulated search engines and
// search engines that are created by policy, and possibly one custom search
// engine if it's selected as default search engine.
// Note that `TemplateURL` pointers should not be freed. They either come from
// `TemplateURLService::GetTemplateURLs()`, or they are owned by
// `_choiceScreenTemplateUrls`.
std::vector<raw_ptr<TemplateURL>> _firstList;
// The second list in the page which contains all remaining custom search
// engines.
// Note that `TemplateURL` pointers should not be freed. They either come from
// `TemplateURLService::GetTemplateURLs()`, or they are owned by
// `_choiceScreenTemplateUrls`.
std::vector<raw_ptr<TemplateURL>> _secondList;
// FaviconLoader is a keyed service that uses LargeIconService to retrieve
// favicon images.
raw_ptr<FaviconLoader> _faviconLoader;
// Determines which version of the settings UI should be displayed.
SearchEngineSettingVersion _searchEngineSettingVersion;
}
#pragma mark - Initialization
- (instancetype)initWithBrowserState:(ChromeBrowserState*)browserState {
DCHECK(browserState);
self = [super initWithStyle:ChromeTableViewStyle()];
if (self) {
_templateURLService =
ios::TemplateURLServiceFactory::GetForBrowserState(browserState);
_observer =
std::make_unique<SearchEngineObserverBridge>(self, _templateURLService);
_templateURLService->Load();
_faviconLoader =
IOSChromeFaviconLoaderFactory::GetForBrowserState(browserState);
_prefService = browserState->GetPrefs();
_searchEngineChoiceService =
ios::SearchEngineChoiceServiceFactory::GetForBrowserState(browserState);
BOOL shouldShowUpdatedSettings =
_searchEngineChoiceService->ShouldShowUpdatedSettings();
if (search_engines::IsEeaChoiceCountry(
_searchEngineChoiceService->GetCountryId()) &&
shouldShowUpdatedSettings) {
_searchEngineSettingVersion = SearchEngineSettingVersion::kEEASettings;
} else if (shouldShowUpdatedSettings) {
_searchEngineSettingVersion =
SearchEngineSettingVersion::kUpdatedSettings;
} else {
_searchEngineSettingVersion =
SearchEngineSettingVersion::kDeprecatedSettings;
}
[self setTitle:l10n_util::GetNSString(IDS_IOS_SEARCH_ENGINE_SETTING_TITLE)];
self.shouldDisableDoneButtonOnEdit = YES;
}
return self;
}
#pragma mark - Properties
- (void)setUpdatingBackend:(BOOL)updatingBackend {
if (_updatingBackend == updatingBackend)
return;
_updatingBackend = updatingBackend;
if (!self.searchEngineChangedInBackground) {
return;
}
[self loadSearchEngines];
BOOL hasSecondSection = [self.tableViewModel
hasSectionForSectionIdentifier:SectionIdentifierSecondList];
BOOL secondSectionExistenceChanged = hasSecondSection == _secondList.empty();
BOOL numberOfCustomItemDifferent =
secondSectionExistenceChanged ||
(hasSecondSection &&
[self.tableViewModel
itemsInSectionWithIdentifier:SectionIdentifierSecondList]
.count != _secondList.size());
BOOL numberOfPrepopulatedItemDifferent =
[self.tableViewModel
itemsInSectionWithIdentifier:SectionIdentifierFirstList]
.count != _firstList.size();
if (numberOfPrepopulatedItemDifferent || numberOfCustomItemDifferent) {
// The number of items has changed.
[self reloadData];
return;
}
NSArray* firstListItem = [self.tableViewModel
itemsInSectionWithIdentifier:SectionIdentifierFirstList];
for (NSUInteger index = 0; index < firstListItem.count; index++) {
if (![self isItem:firstListItem[index]
equalForTemplateURL:_firstList[index]]) {
// Item has changed, reload the TableView.
[self reloadData];
return;
}
}
if (hasSecondSection) {
NSArray* secondListItem = [self.tableViewModel
itemsInSectionWithIdentifier:SectionIdentifierSecondList];
for (NSUInteger index = 0; index < secondListItem.count; index++) {
if (![self isItem:secondListItem[index]
equalForTemplateURL:_secondList[index]]) {
// Item has changed, reload the TableView.
[self reloadData];
return;
}
}
}
}
#pragma mark - UIViewController
- (void)viewDidLoad {
[super viewDidLoad];
// With no header on first appearance, UITableView adds a 35 points space at
// the beginning of the table view. This space remains after this table view
// reloads with headers. Setting a small tableHeaderView avoids this.
self.tableView.tableHeaderView =
[[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, CGFLOAT_MIN)];
self.tableView.allowsMultipleSelectionDuringEditing = YES;
self.tableView.separatorInset =
UIEdgeInsetsMake(0, kTableViewSeparatorLeadingInset, 0, 0);
[self updateUIForEditState];
[self loadModel];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.navigationController.toolbarHidden = NO;
}
- (void)setEditing:(BOOL)editing animated:(BOOL)animated {
if (editing) {
base::RecordAction(
base::UserMetricsAction("IOS.SearchEngines.RecentlyViewed.Edit"));
}
[super setEditing:editing animated:animated];
// Disable prepopulated engines and remove the checkmark in editing mode, and
// recover them in normal mode.
[self updatePrepopulatedEnginesForEditing:editing];
[self updateUIForEditState];
}
#pragma mark - LegacyChromeTableViewController
- (void)loadModel {
[super loadModel];
if (_settingsAreDismissed)
return;
self.tableView.accessibilityIdentifier = kSearchEngineTableViewControllerId;
TableViewModel* model = self.tableViewModel;
[self loadSearchEngines];
// Add prior search engines.
if (_firstList.size() > 0) {
[model addSectionWithIdentifier:SectionIdentifierFirstList];
if (_searchEngineSettingVersion ==
SearchEngineSettingVersion::kEEASettings) {
TableViewTextHeaderFooterItem* header =
[[TableViewTextHeaderFooterItem alloc] initWithType:ItemTypeHeader];
header.subtitle =
l10n_util::GetNSString(IDS_SEARCH_ENGINE_CHOICE_SETTINGS_SUBTITLE);
[model setHeader:header
forSectionWithIdentifier:SectionIdentifierFirstList];
}
for (const TemplateURL* templateURL : _firstList) {
TableViewItem* item = nil;
item = [self createSettingsSearchEngineItemFromTemplateURL:templateURL];
[model addItem:item toSectionWithIdentifier:SectionIdentifierFirstList];
}
}
// Add custom search engines.
if (_secondList.size() > 0) {
[model addSectionWithIdentifier:SectionIdentifierSecondList];
TableViewTextHeaderFooterItem* header =
[[TableViewTextHeaderFooterItem alloc] initWithType:ItemTypeHeader];
header.text = l10n_util::GetNSString(
IDS_IOS_SEARCH_ENGINE_SETTING_CUSTOM_SECTION_HEADER);
[model setHeader:header
forSectionWithIdentifier:SectionIdentifierSecondList];
for (const TemplateURL* templateURL : _secondList) {
DCHECK(templateURL->prepopulate_id() == 0);
TableViewItem* item = nil;
item = [self createSettingsSearchEngineItemFromTemplateURL:templateURL];
[model addItem:item toSectionWithIdentifier:SectionIdentifierSecondList];
}
}
// The toolbar edit button's state needs to be updated.
[self updatedToolbarForEditState];
}
#pragma mark - SettingsControllerProtocol
- (void)reportDismissalUserAction {
base::RecordAction(
base::UserMetricsAction("MobileSearchEngineSettingsClose"));
}
- (void)reportBackUserAction {
base::RecordAction(base::UserMetricsAction("MobileSearchEngineSettingsBack"));
}
- (void)settingsWillBeDismissed {
DCHECK(!_settingsAreDismissed);
// Remove observer bridges.
_observer.reset();
// Clear C++ ivars.
_templateURLService = nullptr;
_prefService = nullptr;
_searchEngineChoiceService = nullptr;
_faviconLoader = nullptr;
_settingsAreDismissed = YES;
}
#pragma mark - SettingsRootTableViewController
- (void)deleteItems:(NSArray<NSIndexPath*>*)indexPaths {
base::RecordAction(
base::UserMetricsAction("IOS.SearchEngines.RecentlyViewed.Delete"));
// Do not call super as this also deletes the section if it is empty.
[self deleteItemAtIndexPaths:indexPaths];
}
- (BOOL)shouldHideToolbar {
return NO;
}
- (BOOL)shouldShowEditDoneButton {
return NO;
}
- (BOOL)editButtonEnabled {
switch (_searchEngineSettingVersion) {
case SearchEngineSettingVersion::kEEASettings:
case SearchEngineSettingVersion::kUpdatedSettings:
// With the updated settings, custom search engine can only be deleted
// when they are not selected (so in the second section).
return
[self.tableViewModel hasItemForItemType:ItemTypeCustomEngine
sectionIdentifier:SectionIdentifierSecondList];
case SearchEngineSettingVersion::kDeprecatedSettings:
// Custom search engine can only be deleted even when being selected.
return
[self.tableViewModel hasItemForItemType:ItemTypeCustomEngine
sectionIdentifier:SectionIdentifierFirstList] ||
[self.tableViewModel hasItemForItemType:ItemTypeCustomEngine
sectionIdentifier:SectionIdentifierSecondList];
}
NOTREACHED();
}
- (void)updateUIForEditState {
[super updateUIForEditState];
[self updatedToolbarForEditState];
}
#pragma mark - UITableViewDelegate
- (void)tableView:(UITableView*)tableView
didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
[super tableView:tableView didSelectRowAtIndexPath:indexPath];
if (_settingsAreDismissed)
return;
// Keep selection in editing mode.
if (self.editing) {
self.deleteButton.enabled = YES;
return;
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
TableViewModel* model = self.tableViewModel;
TableViewItem* selectedItem = [model itemAtIndexPath:indexPath];
// Only search engine items can be selected.
DCHECK(selectedItem.type == ItemTypePrepopulatedEngine ||
selectedItem.type == ItemTypeCustomEngine);
// Do nothing if the tapped engine was already the default.
if (selectedItem.accessoryType == UITableViewCellAccessoryCheckmark) {
return;
}
// Iterate through the engines and remove the checkmark from any that have it.
if ([model hasSectionForSectionIdentifier:SectionIdentifierFirstList]) {
for (TableViewItem* item in
[model itemsInSectionWithIdentifier:SectionIdentifierFirstList]) {
if (item.accessoryType == UITableViewCellAccessoryCheckmark) {
item.accessoryType = UITableViewCellAccessoryNone;
UITableViewCell* cell =
[tableView cellForRowAtIndexPath:[model indexPathForItem:item]];
cell.accessoryType = UITableViewCellAccessoryNone;
}
}
}
if ([model hasSectionForSectionIdentifier:SectionIdentifierSecondList]) {
for (TableViewItem* item in
[model itemsInSectionWithIdentifier:SectionIdentifierSecondList]) {
DCHECK(item.type == ItemTypeCustomEngine);
if (item.accessoryType == UITableViewCellAccessoryCheckmark) {
item.accessoryType = UITableViewCellAccessoryNone;
UITableViewCell* cell =
[tableView cellForRowAtIndexPath:[model indexPathForItem:item]];
cell.accessoryType = UITableViewCellAccessoryNone;
}
}
}
// Show the checkmark on the new default engine.
TableViewItem* newDefaultEngine = [model itemAtIndexPath:indexPath];
newDefaultEngine.accessoryType = UITableViewCellAccessoryCheckmark;
UITableViewCell* cell = [tableView cellForRowAtIndexPath:indexPath];
cell.accessoryType = UITableViewCellAccessoryCheckmark;
// Set the new engine as the default.
self.updatingBackend = YES;
if (indexPath.section ==
[model sectionForSectionIdentifier:SectionIdentifierFirstList]) {
_templateURLService->SetUserSelectedDefaultSearchProvider(
_firstList[indexPath.row],
search_engines::ChoiceMadeLocation::kSearchEngineSettings);
} else {
_templateURLService->SetUserSelectedDefaultSearchProvider(
_secondList[indexPath.row],
search_engines::ChoiceMadeLocation::kSearchEngineSettings);
}
[self recordUmaOfDefaultSearchEngine];
self.updatingBackend = NO;
}
- (void)tableView:(UITableView*)tableView
didDeselectRowAtIndexPath:(NSIndexPath*)indexPath {
[super tableView:tableView didDeselectRowAtIndexPath:indexPath];
if (!self.tableView.editing)
return;
if (self.tableView.indexPathsForSelectedRows.count == 0)
self.deleteButton.enabled = NO;
}
#pragma mark - UITableViewDataSource
- (BOOL)tableView:(UITableView*)tableView
canEditRowAtIndexPath:(NSIndexPath*)indexPath {
switch (_searchEngineSettingVersion) {
case SearchEngineSettingVersion::kDeprecatedSettings: {
// With the deprecated search engine settings, all custom search engines
// can be deleted, even the selected one.
TableViewItem* item = [self.tableViewModel itemAtIndexPath:indexPath];
return item.type == ItemTypeCustomEngine;
}
case SearchEngineSettingVersion::kEEASettings:
case SearchEngineSettingVersion::kUpdatedSettings: {
// Only the search engines from the second section can be removed.
// In the first section, search engines are either prepopulated or
// selected custom search engines.
TableViewModel* model = self.tableViewModel;
NSInteger sectionIdentifier =
[model sectionIdentifierForSectionIndex:indexPath.section];
return sectionIdentifier == SectionIdentifierSecondList;
}
}
NOTREACHED();
}
- (void)tableView:(UITableView*)tableView
commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
forRowAtIndexPath:(NSIndexPath*)indexPath {
DCHECK(editingStyle == UITableViewCellEditingStyleDelete);
[self deleteItemAtIndexPaths:@[ indexPath ]];
}
#pragma mark - SearchEngineObserving
- (void)searchEngineChanged {
if (!self.updatingBackend) {
[self reloadData];
} else {
self.searchEngineChangedInBackground = YES;
}
}
#pragma mark - Private methods
// Loads all TemplateURLs from TemplateURLService and classifies them into
// `_firstList` and `_secondList`. If a TemplateURL is
// prepopulated, created by policy or the default search engine, it will get
// into the first list, otherwise the second list.
- (void)loadSearchEngines {
if (_settingsAreDismissed)
return;
// TODO(b/280753739) Update this method to return the correct list of search
// engines directly (for both choice-screen-eligible users and
// non-choice-screen-eligible users). This way we don't have to worry about
// calling two different methods anymore.
std::vector<raw_ptr<TemplateURL, VectorExperimental>> urls =
_templateURLService->GetTemplateURLs();
_firstList.clear();
_firstList.reserve(urls.size());
_secondList.clear();
_secondList.reserve(urls.size());
// Classify TemplateURLs.
for (TemplateURL* url : urls) {
if ([self isPrepopulatedOrDefaultSearchEngine:url]) {
_firstList.push_back(url);
} else {
_secondList.push_back(url);
}
}
// Do not sort prepopulated search engines, they are already sorted by
// locale use.
// Partially sort `_secondList` by TemplateURL's last_visited time.
auto begin = _secondList.begin();
auto end = _secondList.end();
auto pivot = begin + std::min(kMaxcustomSearchEngines, _secondList.size());
std::partial_sort(begin, pivot, end,
[](const TemplateURL* lhs, const TemplateURL* rhs) {
return lhs->last_visited() > rhs->last_visited();
});
// Keep the search engines visited within `kMaxVisitAge` and erase others.
auto cutBegin = base::ranges::lower_bound(
begin, pivot, base::Time::Now() - kMaxVisitAge,
base::ranges::greater_equal(), &TemplateURL::last_visited);
_secondList.erase(cutBegin, end);
}
// Creates a SettingsSearchEngineItem for `templateURL`.
- (TableViewItem*)createSettingsSearchEngineItemFromTemplateURL:
(const TemplateURL*)templateURL {
if (_settingsAreDismissed)
return nil;
SettingsSearchEngineItem* item = nil;
if (templateURL->prepopulate_id() > 0) {
item = [[SettingsSearchEngineItem alloc]
initWithType:ItemTypePrepopulatedEngine];
} else {
item = [[SettingsSearchEngineItem alloc] initWithType:ItemTypeCustomEngine];
}
item.templateURL = templateURL;
item.text = base::SysUTF16ToNSString(templateURL->short_name());
item.detailText = base::SysUTF16ToNSString(templateURL->keyword());
if ([self isItem:item
equalForTemplateURL:_templateURLService
->GetDefaultSearchProvider()]) {
[item setAccessoryType:UITableViewCellAccessoryCheckmark];
}
__weak __typeof(self) weakSelf = self;
GetSearchEngineFavicon(
*templateURL, _searchEngineChoiceService, _templateURLService,
_faviconLoader, ^(FaviconAttributes* attributes) {
[weakSelf faviconReceivedFor:item faviconAttributes:attributes];
});
return item;
}
- (void)faviconReceivedFor:(SettingsSearchEngineItem*)item
faviconAttributes:(FaviconAttributes*)attributes {
item.faviconAttributes = attributes;
[self reconfigureCellsForItems:@[ item ]];
}
// Records the type of the selected default search engine.
- (void)recordUmaOfDefaultSearchEngine {
UMA_HISTOGRAM_ENUMERATION(
kUmaSelectDefaultSearchEngine,
_templateURLService->GetDefaultSearchProvider()->GetEngineType(
_templateURLService->search_terms_data()),
SEARCH_ENGINE_MAX);
}
// Deletes custom search engines at `indexPaths`.
// When `_searchEngineSettingVersion` is either `kEEASettings` or
// `kUpdatedSettings`:
// The selected custom search engine cannot be removed.
// When `_searchEngineSettingVersion` is `kDeprecatedSettings`:
// If a custom engine is selected as the default engine, resets default engine
// to the first prepopulated engine.
- (void)deleteItemAtIndexPaths:(NSArray<NSIndexPath*>*)indexPaths {
if (_settingsAreDismissed) {
return;
}
// Update `_templateURLService`, `_firstList` and `_secondList`.
_updatingBackend = YES;
NSInteger firstSection = [self.tableViewModel
sectionForSectionIdentifier:SectionIdentifierFirstList];
bool resetDefaultEngine = false;
// Remove search engines from `_firstList`, `_secondList` and
// `_templateURLService`.
for (NSIndexPath* path : indexPaths) {
TableViewItem* item = [self.tableViewModel itemAtIndexPath:path];
SettingsSearchEngineItem* engineItem =
base::apple::ObjCCastStrict<SettingsSearchEngineItem>(item);
if (path.section == firstSection) {
// Only custom search engine can be deleted.
CHECK(item.type == ItemTypeCustomEngine, base::NotFatalUntil::M127);
// It should not be possible to remove a search engine from the first
// section, when showing the updated settings. The updated settings should
// either contains a selected custom search engine (which cannot be
// removed as long as it is selected), or prepopulated search engine.
CHECK_EQ(_searchEngineSettingVersion,
SearchEngineSettingVersion::kDeprecatedSettings,
base::NotFatalUntil::M127);
// The custom search engine in the first section should be the last one.
DCHECK(path.row == static_cast<int>(_firstList.size()) - 1);
std::erase(_firstList, engineItem.templateURL);
} else {
std::erase(_secondList, engineItem.templateURL);
}
// If `engine` is selected as default search engine, reset the default
// engine to the first prepopulated engine.
if (engineItem.templateURL ==
_templateURLService->GetDefaultSearchProvider()) {
CHECK_EQ(_searchEngineSettingVersion,
SearchEngineSettingVersion::kDeprecatedSettings,
base::NotFatalUntil::M127);
CHECK(_firstList.size() > 0, base::NotFatalUntil::M127);
_templateURLService->SetUserSelectedDefaultSearchProvider(_firstList[0]);
resetDefaultEngine = true;
}
_templateURLService->Remove(engineItem.templateURL);
}
// Update UI.
__weak SearchEngineTableViewController* weakSelf = self;
[self.tableView
performBatchUpdates:^{
SearchEngineTableViewController* strongSelf = weakSelf;
if (!strongSelf)
return;
TableViewModel* model = strongSelf.tableViewModel;
[strongSelf removeFromModelItemAtIndexPaths:indexPaths];
[strongSelf.tableView
deleteRowsAtIndexPaths:indexPaths
withRowAnimation:UITableViewRowAnimationAutomatic];
// Update the first prepopulated engine if it's reset as default.
if (resetDefaultEngine) {
NSIndexPath* indexPath = [NSIndexPath indexPathForRow:0
inSection:firstSection];
TableViewItem* item = [model itemAtIndexPath:indexPath];
item.accessoryType = UITableViewCellAccessoryCheckmark;
[strongSelf.tableView
reloadRowsAtIndexPaths:@[ indexPath ]
withRowAnimation:UITableViewRowAnimationAutomatic];
}
// Remove second section if it's empty.
if (strongSelf->_secondList.empty() &&
[model
hasSectionForSectionIdentifier:SectionIdentifierSecondList]) {
NSInteger section =
[model sectionForSectionIdentifier:SectionIdentifierSecondList];
[model removeSectionWithIdentifier:SectionIdentifierSecondList];
[strongSelf.tableView
deleteSections:[NSIndexSet indexSetWithIndex:section]
withRowAnimation:UITableViewRowAnimationFade];
}
_updatingBackend = NO;
}
completion:^(BOOL finished) {
SearchEngineTableViewController* strongSelf = weakSelf;
if (!strongSelf)
return;
// Update editing status.
if (![strongSelf editButtonEnabled]) {
[strongSelf setEditing:NO animated:YES];
}
[strongSelf updateUIForEditState];
}];
}
// Disables prepopulated engines and removes the checkmark in editing mode.
// Enables engines and recovers the checkmark in normal mode.
- (void)updatePrepopulatedEnginesForEditing:(BOOL)editing {
if (_settingsAreDismissed)
return;
// All prepopulated items in the first section should be updated.
// When `_searchEngineSettingVersion` is not `kDeprecatedSettings`,
// the selected custom item should be updated since the user is not allowed
// to remove it.
NSArray<TableViewItem*>* items = [self.tableViewModel
itemsInSectionWithIdentifier:SectionIdentifierFirstList];
for (TableViewItem* item in items) {
SettingsSearchEngineItem* engineItem =
base::apple::ObjCCastStrict<SettingsSearchEngineItem>(item);
if (engineItem.type == ItemTypePrepopulatedEngine ||
_searchEngineSettingVersion !=
SearchEngineSettingVersion::kDeprecatedSettings) {
engineItem.enabled = !editing;
}
if (!editing && [self isItem:engineItem
equalForTemplateURL:_templateURLService
->GetDefaultSearchProvider()]) {
engineItem.accessoryType = UITableViewCellAccessoryCheckmark;
} else {
engineItem.accessoryType = UITableViewCellAccessoryNone;
}
}
[self reloadCellsForItems:items
withRowAnimation:UITableViewRowAnimationAutomatic];
}
// Returns whether the `item` is the same as an item that would be created
// from `templateURL`.
- (BOOL)isItem:(SettingsSearchEngineItem*)item
equalForTemplateURL:(const TemplateURL*)templateURL {
if (!templateURL) {
return NO;
}
NSString* name = base::SysUTF16ToNSString(templateURL->short_name());
NSString* keyword = base::SysUTF16ToNSString(templateURL->keyword());
return [item.text isEqualToString:name] &&
[item.detailText isEqualToString:keyword];
}
- (BOOL)isPrepopulatedOrDefaultSearchEngine:(const TemplateURL*)templateURL {
return _templateURLService->IsPrepopulatedOrDefaultProviderByPolicy(
templateURL) ||
templateURL == _templateURLService->GetDefaultSearchProvider();
}
@end