| // Copyright 2022 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/privacy/privacy_safe_browsing_mediator.h" |
| |
| #import "base/apple/foundation_util.h" |
| #import "base/auto_reset.h" |
| #import "base/metrics/user_metrics.h" |
| #import "base/metrics/user_metrics_action.h" |
| #import "base/notreached.h" |
| #import "components/prefs/pref_service.h" |
| #import "components/safe_browsing/core/common/features.h" |
| #import "components/safe_browsing/core/common/safe_browsing_prefs.h" |
| #import "ios/chrome/browser/policy/policy_util.h" |
| #import "ios/chrome/browser/settings/sync/utils/sync_util.h" |
| #import "ios/chrome/browser/shared/model/application_context/application_context.h" |
| #import "ios/chrome/browser/shared/model/prefs/pref_names.h" |
| #import "ios/chrome/browser/shared/public/features/features.h" |
| #import "ios/chrome/browser/shared/ui/list_model/list_model.h" |
| #import "ios/chrome/browser/shared/ui/symbols/symbols.h" |
| #import "ios/chrome/browser/shared/ui/table_view/cells/table_view_info_button_item.h" |
| #import "ios/chrome/browser/shared/ui/table_view/cells/table_view_info_button_item_delegate.h" |
| #import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h" |
| #import "ios/chrome/browser/ui/settings/cells/settings_image_detail_text_item.h" |
| #import "ios/chrome/browser/ui/settings/privacy/privacy_constants.h" |
| #import "ios/chrome/browser/ui/settings/privacy/privacy_safe_browsing_consumer.h" |
| #import "ios/chrome/browser/ui/settings/privacy/privacy_safe_browsing_navigation_commands.h" |
| #import "ios/chrome/browser/ui/settings/utils/observable_boolean.h" |
| #import "ios/chrome/browser/ui/settings/utils/pref_backed_boolean.h" |
| #import "ios/chrome/common/ui/colors/semantic_color_names.h" |
| #import "ios/chrome/grit/ios_chromium_strings.h" |
| #import "ios/chrome/grit/ios_strings.h" |
| #import "ui/base/l10n/l10n_util.h" |
| |
| using ItemArray = NSArray<TableViewItem*>*; |
| |
| namespace { |
| |
| // The size of the symbol image. |
| CGFloat kSymbolImagePointSize = 17.; |
| |
| // List of item types. |
| typedef NS_ENUM(NSInteger, ItemType) { |
| ItemTypeSafeBrowsingStandardProtection = kItemTypeEnumZero, |
| ItemTypeSafeBrowsingEnhancedProtection, |
| ItemTypeSafeBrowsingNoProtection, |
| }; |
| |
| } // namespace |
| |
| @interface PrivacySafeBrowsingMediator () <BooleanObserver, |
| TableViewInfoButtonItemDelegate> |
| |
| // Preference value for the enhanced safe browsing feature. |
| @property(nonatomic, strong, readonly) |
| PrefBackedBoolean* safeBrowsingEnhancedProtectionPreference; |
| |
| // Preference value for the standard safe browsing feature. |
| @property(nonatomic, strong, readonly) |
| PrefBackedBoolean* safeBrowsingStandardProtectionPreference; |
| |
| // All the items for the safe browsing section. |
| @property(nonatomic, strong, readonly) ItemArray safeBrowsingItems; |
| |
| // User pref service used to check if a specific pref is managed by enterprise |
| // policies. |
| @property(nonatomic, assign, readonly) PrefService* userPrefService; |
| |
| // Boolean to check if safe browsing is controlled by enterprise. |
| @property(nonatomic, readonly) BOOL enterpriseEnabled; |
| |
| @end |
| |
| @implementation PrivacySafeBrowsingMediator |
| |
| @synthesize safeBrowsingItems = _safeBrowsingItems; |
| |
| - (instancetype)initWithUserPrefService:(PrefService*)userPrefService { |
| self = [super init]; |
| if (self) { |
| DCHECK(userPrefService); |
| _userPrefService = userPrefService; |
| _safeBrowsingEnhancedProtectionPreference = [[PrefBackedBoolean alloc] |
| initWithPrefService:userPrefService |
| prefName:prefs::kSafeBrowsingEnhanced]; |
| _safeBrowsingEnhancedProtectionPreference.observer = self; |
| _safeBrowsingStandardProtectionPreference = [[PrefBackedBoolean alloc] |
| initWithPrefService:userPrefService |
| prefName:prefs::kSafeBrowsingEnabled]; |
| _safeBrowsingStandardProtectionPreference.observer = self; |
| } |
| return self; |
| } |
| |
| - (void)selectSettingItem:(TableViewItem*)item { |
| // If item is already selected or if we cancel turning off Safe Browsing, |
| // don't do anything and keep the current selected choice. |
| ItemType type = static_cast<ItemType>(item.type); |
| if (item == nil || [self shouldItemTypeHaveCheckmark:type]) { |
| [self updatePrivacySafeBrowsingSectionAndNotifyConsumer:YES]; |
| return; |
| } |
| |
| // Show checkmark for selected item and update associated preference value by |
| // setting the SafeBrowsingState. |
| safe_browsing::SafeBrowsingState safeBrowsingState = |
| safe_browsing::SafeBrowsingState::NO_SAFE_BROWSING; |
| switch (type) { |
| case ItemTypeSafeBrowsingEnhancedProtection: |
| safeBrowsingState = safe_browsing::SafeBrowsingState::ENHANCED_PROTECTION; |
| break; |
| case ItemTypeSafeBrowsingStandardProtection: |
| safeBrowsingState = safe_browsing::SafeBrowsingState::STANDARD_PROTECTION; |
| break; |
| case ItemTypeSafeBrowsingNoProtection: |
| safeBrowsingState = safe_browsing::SafeBrowsingState::NO_SAFE_BROWSING; |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| safe_browsing::SetSafeBrowsingState(self.userPrefService, safeBrowsingState); |
| |
| [self updatePrivacySafeBrowsingSectionAndNotifyConsumer:YES]; |
| } |
| |
| #pragma mark - Properties |
| |
| - (ItemArray)safeBrowsingItems { |
| if (!_safeBrowsingItems) { |
| NSMutableArray* items = [NSMutableArray array]; |
| NSInteger enhancedProtectionSummary; |
| if (base::FeatureList::IsEnabled( |
| safe_browsing::kFriendlierSafeBrowsingSettingsEnhancedProtection)) { |
| enhancedProtectionSummary = |
| IDS_IOS_PRIVACY_SAFE_BROWSING_ENHANCED_PROTECTION_FRIENDLIER_SUMMARY; |
| } else { |
| enhancedProtectionSummary = |
| IDS_IOS_PRIVACY_SAFE_BROWSING_ENHANCED_PROTECTION_SUMMARY; |
| } |
| NSInteger standardProtectionSummary; |
| if (base::FeatureList::IsEnabled( |
| safe_browsing::kFriendlierSafeBrowsingSettingsStandardProtection)) { |
| standardProtectionSummary = |
| IDS_IOS_PRIVACY_SAFE_BROWSING_STANDARD_PROTECTION_FRIENDLIER_SUMMARY; |
| } else { |
| standardProtectionSummary = |
| IDS_IOS_PRIVACY_SAFE_BROWSING_STANDARD_PROTECTION_SUMMARY; |
| } |
| TableViewInfoButtonItem* safeBrowsingEnhancedProtectionItem = [self |
| infoButtonItemType:ItemTypeSafeBrowsingEnhancedProtection |
| titleId: |
| IDS_IOS_PRIVACY_SAFE_BROWSING_ENHANCED_PROTECTION_TITLE |
| detailText:enhancedProtectionSummary |
| accessibilityIdentifier:kSettingsSafeBrowsingEnhancedProtectionCellId]; |
| [items addObject:safeBrowsingEnhancedProtectionItem]; |
| |
| TableViewInfoButtonItem* safeBrowsingStandardProtectionItem = [self |
| infoButtonItemType:ItemTypeSafeBrowsingStandardProtection |
| titleId: |
| IDS_IOS_PRIVACY_SAFE_BROWSING_STANDARD_PROTECTION_TITLE |
| detailText:standardProtectionSummary |
| accessibilityIdentifier:kSettingsSafeBrowsingStandardProtectionCellId]; |
| [items addObject:safeBrowsingStandardProtectionItem]; |
| NSInteger noProtectionSummary; |
| if (base::FeatureList::IsEnabled( |
| safe_browsing::kFriendlierSafeBrowsingSettingsEnhancedProtection)) { |
| noProtectionSummary = |
| IDS_IOS_PRIVACY_SAFE_BROWSING_NO_PROTECTION_FRIENDLIER_SUMMARY; |
| } else { |
| noProtectionSummary = IDS_IOS_PRIVACY_SAFE_BROWSING_NO_PROTECTION_SUMMARY; |
| } |
| if (self.enterpriseEnabled) { |
| TableViewInfoButtonItem* safeBrowsingNoProtectionItem = [self |
| infoButtonItemType:ItemTypeSafeBrowsingNoProtection |
| titleId: |
| IDS_IOS_PRIVACY_SAFE_BROWSING_NO_PROTECTION_TITLE |
| detailText:noProtectionSummary |
| accessibilityIdentifier:kSettingsSafeBrowsingNoProtectionCellId]; |
| [items addObject:safeBrowsingNoProtectionItem]; |
| } else { |
| TableViewInfoButtonItem* safeBrowsingNoProtectionItem = [self |
| infoButtonItemType:ItemTypeSafeBrowsingNoProtection |
| titleId: |
| IDS_IOS_PRIVACY_SAFE_BROWSING_NO_PROTECTION_TITLE |
| detailText:noProtectionSummary |
| accessibilityIdentifier:kSettingsSafeBrowsingNoProtectionCellId]; |
| safeBrowsingNoProtectionItem.infoButtonIsHidden = YES; |
| [items addObject:safeBrowsingNoProtectionItem]; |
| } |
| _safeBrowsingItems = items; |
| } |
| return _safeBrowsingItems; |
| } |
| |
| - (void)setConsumer:(id<PrivacySafeBrowsingConsumer>)consumer { |
| if (_consumer == consumer) |
| return; |
| _consumer = consumer; |
| [_consumer setSafeBrowsingItems:self.safeBrowsingItems]; |
| [_consumer setEnterpriseEnabled:self.enterpriseEnabled]; |
| } |
| |
| - (BOOL)enterpriseEnabled { |
| return self.userPrefService->IsManagedPreference(prefs::kSafeBrowsingEnabled); |
| } |
| |
| #pragma mark - Private |
| |
| // Creates item with an image checkmark and an info button. |
| - (TableViewInfoButtonItem*)infoButtonItemType:(NSInteger)type |
| titleId:(NSInteger)titleId |
| detailText:(NSInteger)detailText |
| accessibilityIdentifier: |
| (NSString*)accessibilityIdentifier { |
| TableViewInfoButtonItem* infoButtonItem = |
| [[TableViewInfoButtonItem alloc] initWithType:type]; |
| infoButtonItem.text = l10n_util::GetNSString(titleId); |
| infoButtonItem.detailText = l10n_util::GetNSString(detailText); |
| // If Safe Browsing is controlled by enterprise, make non-selected options |
| // greyed out. |
| if (self.enterpriseEnabled && ![self shouldItemTypeHaveCheckmark:type]) { |
| infoButtonItem.textColor = |
| [[UIColor colorNamed:kTextPrimaryColor] colorWithAlphaComponent:0.4f]; |
| infoButtonItem.detailTextColor = |
| [[UIColor colorNamed:kTextSecondaryColor] colorWithAlphaComponent:0.4f]; |
| infoButtonItem.accessibilityHint = l10n_util::GetNSString( |
| IDS_IOS_TOGGLE_SETTING_MANAGED_ACCESSIBILITY_HINT); |
| } else { |
| infoButtonItem.accessibilityActivationPointOnButton = NO; |
| infoButtonItem.accessibilityHint = l10n_util::GetNSString( |
| IDS_IOS_TABLE_VIEW_INFO_BUTTON_ITEM_ACCESSIBILITY_TAP); |
| } |
| UIImageConfiguration* configuration = [UIImageSymbolConfiguration |
| configurationWithPointSize:kSymbolImagePointSize |
| weight:UIImageSymbolWeightSemibold |
| scale:UIImageSymbolScaleMedium]; |
| infoButtonItem.iconImage = |
| DefaultSymbolWithConfiguration(kCheckmarkSymbol, configuration); |
| infoButtonItem.iconTintColor = [self shouldItemTypeHaveCheckmark:type] |
| ? [UIColor colorNamed:kBlueColor] |
| : [UIColor clearColor]; |
| infoButtonItem.accessibilityIdentifier = accessibilityIdentifier; |
| infoButtonItem.accessibilityDelegate = self; |
| |
| return infoButtonItem; |
| } |
| |
| // Returns whether an ItemType should have a checkmark based on its |
| // SafeBrowsingState. |
| - (BOOL)shouldItemTypeHaveCheckmark:(NSInteger)itemType { |
| ItemType type = static_cast<ItemType>(itemType); |
| safe_browsing::SafeBrowsingState safeBrowsingState = |
| safe_browsing::GetSafeBrowsingState(*self.userPrefService); |
| switch (type) { |
| case ItemTypeSafeBrowsingEnhancedProtection: |
| return safeBrowsingState == |
| safe_browsing::SafeBrowsingState::ENHANCED_PROTECTION; |
| case ItemTypeSafeBrowsingStandardProtection: |
| return safeBrowsingState == |
| safe_browsing::SafeBrowsingState::STANDARD_PROTECTION; |
| case ItemTypeSafeBrowsingNoProtection: |
| return safeBrowsingState == |
| safe_browsing::SafeBrowsingState::NO_SAFE_BROWSING; |
| default: |
| NOTREACHED(); |
| return NO; |
| } |
| } |
| |
| // Updates the privacy safe browsing section according to the user consent. If |
| // `notifyConsumer` is YES, the consumer is notified about model changes. |
| - (void)updatePrivacySafeBrowsingSectionAndNotifyConsumer:(BOOL)notifyConsumer { |
| for (TableViewItem* item in self.safeBrowsingItems) { |
| TableViewInfoButtonItem* infoButtonItem = |
| base::apple::ObjCCast<TableViewInfoButtonItem>(item); |
| ItemType type = static_cast<ItemType>(item.type); |
| infoButtonItem.iconTintColor = [self shouldItemTypeHaveCheckmark:type] |
| ? [UIColor colorNamed:kBlueColor] |
| : [UIColor clearColor]; |
| } |
| |
| if (notifyConsumer) { |
| [self.consumer reloadCellsForItems]; |
| [self selectItem]; |
| } |
| } |
| |
| // Check if selected row should display enterprise popover. |
| - (BOOL)shouldEnterprisePopOverDisplay:(TableViewItem*)item { |
| ItemType type = static_cast<ItemType>(item.type); |
| return self.enterpriseEnabled && (![self shouldItemTypeHaveCheckmark:type] || |
| type == ItemTypeSafeBrowsingNoProtection); |
| } |
| |
| #pragma mark - SafeBrowsingViewControllerDelegate |
| |
| - (void)didSelectItem:(TableViewItem*)item { |
| ItemType type = static_cast<ItemType>(item.type); |
| if (type == ItemTypeSafeBrowsingEnhancedProtection) { |
| if ([self shouldItemTypeHaveCheckmark:type]) { |
| base::RecordAction(base::UserMetricsAction( |
| "SafeBrowsing.Settings.EnhancedProtectionExpandArrowClicked")); |
| [self.handler showSafeBrowsingEnhancedProtection]; |
| } else { |
| base::RecordAction(base::UserMetricsAction( |
| "SafeBrowsing.Settings.EnhancedProtectionClicked")); |
| [self selectSettingItem:item]; |
| } |
| } |
| |
| if (type == ItemTypeSafeBrowsingStandardProtection) { |
| if ([self shouldItemTypeHaveCheckmark:type]) { |
| base::RecordAction(base::UserMetricsAction( |
| "SafeBrowsing.Settings.StandardProtectionExpandArrowClicked")); |
| [self.handler showSafeBrowsingStandardProtection]; |
| } else { |
| base::RecordAction(base::UserMetricsAction( |
| "SafeBrowsing.Settings.StandardProtectionClicked")); |
| [self selectSettingItem:item]; |
| } |
| } |
| |
| if (type == ItemTypeSafeBrowsingNoProtection && |
| ![self shouldItemTypeHaveCheckmark:type]) { |
| base::RecordAction(base::UserMetricsAction( |
| "SafeBrowsing.Settings.DisableSafeBrowsingClicked")); |
| [self.handler showSafeBrowsingNoProtectionPopUp:item]; |
| } |
| } |
| |
| - (void)didTapInfoButton:(UIButton*)button onItem:(TableViewItem*)item { |
| if ([self shouldEnterprisePopOverDisplay:item]) { |
| [self.consumer showEnterprisePopUp:button]; |
| return; |
| } |
| |
| // Info button tap logic when not in enterprise mode. |
| ItemType type = static_cast<ItemType>(item.type); |
| switch (type) { |
| case ItemTypeSafeBrowsingEnhancedProtection: |
| base::RecordAction(base::UserMetricsAction( |
| "SafeBrowsing.Settings.EnhancedProtectionExpandArrowClicked")); |
| [self.handler showSafeBrowsingEnhancedProtection]; |
| break; |
| case ItemTypeSafeBrowsingStandardProtection: |
| base::RecordAction(base::UserMetricsAction( |
| "SafeBrowsing.Settings.StandardProtectionExpandArrowClicked")); |
| [self.handler showSafeBrowsingStandardProtection]; |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| } |
| |
| - (void)selectItem { |
| for (TableViewItem* item in self.safeBrowsingItems) { |
| ItemType type = static_cast<ItemType>(item.type); |
| if ([self shouldItemTypeHaveCheckmark:type]) { |
| [self.consumer selectItem:item]; |
| break; |
| } |
| } |
| } |
| |
| #pragma mark - TableViewInfoButtonItemDelegate |
| |
| - (void)handleTappedInfoButtonForItem:(TableViewItem*)item { |
| [self didTapInfoButton:nil onItem:item]; |
| } |
| |
| #pragma mark - BooleanObserver |
| |
| - (void)booleanDidChange:(id<ObservableBoolean>)observableBoolean { |
| [self updatePrivacySafeBrowsingSectionAndNotifyConsumer:YES]; |
| } |
| |
| @end |